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 intactDon'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:3000Console
<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:3000Console
abcA 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('가나다')
9Set 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 nodemonUse 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