Bulit-in http module which provides HTTP server and client functionality
Let's get serious about servers with Events and Streams in mind!
Here's an example of an HTTP server that simply responds to any request with "Hello, LTCS!".
An anonymous function is provided as an argument to createServer
, acting as a callback that defines how each HTTP request should be handled.
var http = require('http');
http.createServer((req, res) => {
res.end('Hello, LTCS!');
}).listen(3000);
Whenever a request happens, the arrow function is fired and "Helo, LTCS!" is written out as the response.
Here's another way to write this same sever to mkaie the request
event even more explict:
var http = require('http');
var server = http.createServer();
server.on('request', (req, res) => {
res.end('Hello, LTCS!');
});
server.listen(3000);
A HTTP message consists of headers and body.
$ curl http://localhost:3000 -v
* Rebuilt URL to: http://localhost:3000/
* Trying ::1...
* Connected to localhost (::1) port 3000 (#0)
> GET / HTTP/1.1
> Host: localhost:3000
> User-Agent: curl/7.43.0
> Accept: */*
>
< HTTP/1.1 200 OK
< Date: Mon, 09 Jan 2017 13:00:45 GMT
< Connection: keep-alive
< Content-Length: 12
<
Hello, LTCS!
* Connection #0 to host localhost left intact
Don't worry. You don't have to parse these messages manually. Node.js provides an abtraction to represent requests and responses.
A request message consist of Request Line(Method, Path, Version), Request Headers, Request Body.
You can check which HTTP method is being used by reading the req.method
proeprty.
console.log(req.method)
The requested URL can be accessed with req.url
property, which may contain several components depending on the request.
To parse these sections, Node.js provides the url
module, and specifically the .parse()
function.
var url = require('url');
var parsedUrl = url.parse(req.url);
console.log(parsedUrl);
When Node.js HTTP parser reads in and parses request data, it makes that data available in the form of data
events that contains chunks of parsed data ready to be handled by the callback funtion.
req.on('data', (data) => {
console.log(data);
});
Test
curl -d 'abc' http://localhost:3000
Console
<Buffer 61 62 63>
By default, the data
events provide Buffer
objects, which are a sort of byte arrays. In case that you need to handle textual data not binary data, set the stream encoding to utf8
then the data
events will instead emit strings.
req.setEncoding('utf8'); // Data is now a utf8 string instead of a Buffer
req.on('data', (data) => {
console.log(data);
});
Test
curl -d 'abc' http://localhost:3000
Console
abc
A response message consist of Status Line, Response Headers, Response Body.
First, call the res.write()
method, which writes response data, and then use the res.end()
method to end the response.
res.write('Hello, LTCS!\n');
res.write('Bye, LTCS!');
res.end();
As shorthand, res.write()
and res.end()
can be conbined into one statement, which can be nice for small responses.
res.end('Hello, LTCS!\nBye, LTCS!');
You should add headers in any order, but only up to the first res.write()
or res.end()
. After the first part of the response body is written, HTTP headers that thave been set will be flushed.
- text/plain
res.setHeader('Content-Type', 'text/plain');
res.end('Hello, LTCS!');
- text/html
res.setHeader('Content-Type', 'text/html');
res.end('<html><body><h1>Hello, LTCS!</h1></body></html>');
When serving files via HTTP, it's usually not enough to just send the contents of a file;
You also should include the type of file being sent.
This is done by setting the Content-Type
HTTP header with the proper MIME type for the file.
MIME types are dicussed in detail in the Wikipedica article.
To provide a performance boost, the Content-Length
header should be sent with your response when possible.
var body = 'I love Learn Teach Code Seoul';
res.setHeader('Content-Length', Buffer.byteLength(body));
res.end(body);
You may be tempted to use the body.length
value for the Content-Length
, but the Content-Length
value should represent the byte length, not character length, and the two will be different if the string contains multibyte characters. To avoid this problem, Node.js provides the Buffer.byteLength()
method.
$ node
> 'abc'.length
3
> '가나다'.length
3
> Buffer.byteLength('가나다')
9
Set res.statusCode
property. This property also should be assigned before the first call to res.write()
or res.end()
.
The default HTTP status code is 200.
res.statusCode = 404; // Not Found
- 2xx Success : 200 OK, 201 Created
- 3xx Redirection : 301 Moved Permanently, 302 Found, 303 See Other
- 4xx Client Error : 400 Bad Request, 401 Unauthorized, 404 Not Found
- 5xx Server Error : 500 Internal Server Error, 503 Service Unavailable
HTTP status codes are listed in the Wikipedia article.
Let's build an application which serves static files such as HTML, CSS and JavaScript(Client-side) files.
The fs
(filesystem) module we've convered in Lesson 3 is necessary for serving static files.
Streams an image to a client.
var http = require('http');
var fs = require('fs');
http.createServer((req, res) => {
res.writeHead(200, {'Content-type': 'image/png'});
fs.createReadStream('ltcs.png').pipe(res);
}).listen(3000);
In this one-liner, the data is read in from the file and it sent out to the client as it comes in.
We can simply join the base directory path(root
) and the URL's pathname
using the paht
module's join()
method to form the absolute path.
var http = require('http');
var fs = require('fs');
var parse = require('url').parse;
var join = require('path').join;
var root = __dirname;
http.createServer((req, res) => {
var url = parse(req.url);
var path = join(root, url.pathname);
var stream = fs.createReadStream(path);
stream.on('data', (data) => {
res.write(data); // write file data to response
});
stream.on('end', () => {
res.end(); // end response when file is complete
});
}).listen(3000);
__dirname
is a magic variable provided by Node.js that's assinged the directory apth to the file.
The server will be serving static files relative to the same directory as this script, but you could configure root
to specify any directory path.
Test
$ curl http://localhost:3000/test.js
var http = require('http');
...
The preceding curl
command reqeusts the server's script itself, which is sent back as the response body.
- On the client side
No reply from the server
$ curl http://localhost:3000/test1.js
curl: (52) Empty reply from server
- On the server side
Our server's just stopped with a stack trace printed!!! 😱
events.js:160
throw er; // Unhandled 'error' event
^
Error: ENOENT: no such file or directory, open '/Users/1002139/test/test1.js'
We should make our file server more robust!
Errors will be thrown in the current server if you access a file that doen't exist, access a forbidden file, or run into any file I/O-related problem.
A stream, like fs.ReadStream
, is simply a specialized EventEmitter
that contains predefined events such as data
and end
, which we've already looked at. In addition, error
events will be thrown when something gets wrong. This means that if you don't listen for these errors, they'll crash your server.
To prevent errors from killing our server, we need to listen for errors by registering an error
event handler on the fs.ReadSteram
, which responds with the 500 response status indication an internal server error.
stream.on('error', (err) => {
res.statusCode = 500;
res.end('Sorry, Something went wrong!');
});
Registering an errer
event helps us catch any forseen or unforseen errors and enables us to respond more gracefully to the client.
Test
$ curl -i http://localhost:3000/test1.js
HTTP/1.1 500 Internal Server Error
Date: Wed, 11 Jan 2017 13:00:24 GMT
Connection: keep-alive
Content-Length: 28
Sorry, Something went wrong!
fs.stat()
retrives information about a file. If the named file doesn't exist, fs.stat()
will respond with a value of ENOENT
in the error.code
field, and you can return the error code 404, indicating that the file is not found. If you receive other errors from fs.stat()
, you can return a generic 500 error code.
fs.stat(path, (err, stat) => {
if (err) {
if (err.code == 'ENOENT') {
res.statusCode = 404;
res.end('Not Found');
} else {
res.statusCode = 500;
res.end('Internal Server Error');
}
} else {
var stream = fs.createReadStream(path);
stream.on('data', (data) => {
res.setHeader('Content-Length', stat.size);
res.write(data);
});
stream.on('end', () => {
res.end();
});
stream.on('error', (err) => {
res.statusCode = 500;
res.end('Sorry, Something went wrong!');
});
}
});
Keep your server running even while developing it!
Nodemon will watch the files in the directory in which Nodemon was started, and if any files change, nodemon will automatically restart your node application.
$ npm install -g nodemon
Use nodemon
instead of node
when you start your app.
$ nodemon app.js
- Build a RESTful web service for a to-do app
$ curl -d 'buy groceries' http://localhost:3000
Created
$ curl -d 'buy node in action' http://localhost:3000
Created
$ curl http://localhost:3000
0) buy groceries
1) buy node in action
$ curl -X DELETE http://localhost:3000/0
Deleted
curl http://localhost:3000
0) buy node in action