In this post we will build a HTTP Server using Nodejs net module which will help you understand HTTP basics. The server will be very simple and serve html files in a directory.
First, let's create a tcp server that greets clients using net module so that we can understand how HTTP works.
const net = require('net');
const server = net.createServer();
server.on('connection', (socket) => {
console.log('client connected');
socket.on('data', (data) => {
console.log(data.toString());
socket.write('hello');
socket.end();
});
});
server.listen(9090, () => {
console.log('Listening 9090');
});
Here we created a server that listens PORT 9090. After the client makes a connection, it prints client connected
. When the client sends data, it prints the data and sends hello
to client. When it is done socket.end();
closes the connection.
Let's try with netcat. We will use netcat as a TCP client. If you do not have netcat you can use anything you want.
Run the server. Open up new terminal tab for netcat. nc localhost 9090
will create a connection and you will immediately see client connected
message on the server. Go to netcat terminal tab and write TEST
and press enter. You will see hello
message that coming from server. Anytime you send a message from client to server you will see hello
message.
Now, go to server terminal tab and you should see the message you sent from client.
Let's test this on a browser. You can use your default browser but I will use safari. Open localhost:9090 on the browser.
We see client made a connection and sent some data but on the browser we see can not connect to server
error.
Why 🤔. Because the message that server is returning is not a valid HTTP response yet. We should apply Hypertext Transfer Protocol (HTTP) so browser can understand the message and render it.
Basically, what HTTP saying is;
message consist of a start-line, zero or more header fields (also known as "headers"), an empty line (i.e., a line with nothing preceding the CRLF) indicating the end of the header fields, and possibly a message-body.
So our response message should look like this:
- Status line. It consists of three items:
- HTTP version number.
HTTP/1.1
- Status code.
200
- Reason phrase.
OK
- HTTP version number.
- Headers
- Body
const net = require('net');
const server = net.createServer();
server.on('connection', (socket) => {
console.log('client connected');
socket.on('data', (data) => {
console.log(data.toString());
const statusLine = 'HTTP/1.1 200 OK\n';
socket.write(statusLine);
const header = 'Server: SimpleHTTPServer Nodejs\nContent-type: text/html\n\n';
socket.write(header);
const body = 'Hello World';
socket.write(body);
socket.end();
});
});
server.listen(9090, () => {
console.log('Listening 9090');
});
Notice that there is one \n
at the end of the statusLine
and between the headers (Server:, Content-type:), but there are two \n
after the headers. This indicates that the body begins.
Let's open localhost:9090
on a browser one more time.
Tadaa 🎉.
Serve HTML Files
Now we can create a program that will serve HTML files. It will understand which HTML file client wants from request header. For example if the client goes to localhost:9090/contact, we should see GET /contact HTTP/1.1
in the request header. So we will parse the request header, try to find that file (eg contact.html) and respond to client. If client goes to the homepage we will respond with index.html.
Like HTTP response, HTTP request follows the same rules. Each HTTP header is followed by a carriage return line feed (CRLF). After the last of the HTTP headers, an additional CRLF is used (to give an empty line), and then any message body begins.
socket.on('data', (data) => {
const [requestHeader] = data.toString().split('\n\n');
const [requestLine] = requestHeader.split('\n');
const [method, path, httpVersion] = requestLine.split(' ');
const header = 'HTTP/1.1 200 OK\nServer: SimpleHTTPServer Nodejs\n\n';
socket.write(header);
const body = `${method} ${path} ${httpVersion}`;
socket.write(body);
socket.end();
});
data.toString().split('\n\n')
splits the header and the body from request. We get first element of array after split (which is the header) and assign it to requestHeader
We know that headers are divided by new lines. .split('\n')
splits all headers and put each header into an array, but we only get the first header which tells us the method, path and version (GET /contact HTTP/1.1
) assign it to requestLine
.
We are splitting requestLine
by empty spaces, and getting method, path, httpVersion
Lastly we are returning those values to client. If you go to the browser you should see GET /contact HTTP/1.1
on the screen.
Now, let's create two HTML files, index.html and contact.html. You can put anything you want in them!
Before connection listener, create a new function called handleRequest
. It will get the path as a parameter.
const handleRequest = async (path) => {
let requestedFile = `${path}.html`;
if (path === '/') {
requestedFile = '/index.html';
}
const fileData = await fs.promises.readFile(`.${requestedFile}`);
return fileData;
};
If client requested homepage, requested file is index.html, if not, requested file is the path (eg: localhost:9090/contact). It will read the requested file and assign data to fileData
with promises (We could also use .pipe()
instead promises).
// ...
const fileData = await handleRequest(path);
const header = 'HTTP/1.1 200 OK\nServer: SimpleHTTPServer Nodejs\n\n';
socket.write(header);
socket.write(fileData);
socket.end();
//...
We are sending the HTML file data that returned from handleRequest
with socket.write
to client.
To understand better, we can use netcat again. Open a terminal. While server is running, write nc localhost 9090
. This will connect to the server. Now, we should send a message to the server. We want the contact.html file. So if we send /contact path in header, the server should respond with contact.html. To do that, write GET /contact HTTP/1.1\r\n
.
Now you have built a simple HTTP server using nodejs net module.
Thank you for reading. You can find the source code on Github
Top comments (0)