Hey anon 👋 Welcome to my blog!
In the last blog, we dived into Reverse Proxies and also created one ourselves! So, in this blog, we'll be building something much simpler than that. We will be building a very basic HTTP Server with Node.js, specifically a HTTP/0.9 Server.
In this blog, we'll step back to 1991 and build something simple yet foundational — a very basic HTTP/0.9 server with Node.js. HTTP/0.9 was the first version of the HTTP Protocol released back in 1991. It was an extremely simple protocol, with very basic functionality. Unlike modern HTTP versions, there were no headers, no status codes like 404 Not Found, and no complex request methods like POST, PUT OR DELETE.
A typical HTTP/0.9 request would simply be:
And the server would respond with the raw HTML file, without any extra metadata. Something like this:
It's BUILD Time!
Step 1: Prerequisites
First, ensure you have Node.js and npm installed on your system. If not, you can download them from nodejs.org.
Step 2: Set Up Your Project
Next, create a directory, cd
into it and initialize a node project:
For this project, we don't need any external dependencies since we will be utilizing the built-in net and fs modules that come with Node.js.
1. Importing the required modules.
We are importing the net module for creating a TCP server and the fs/promises module to interact with the file system asynchronously.
2. Creating the TCP Server
- The first line creates a TCP server, which listens for incoming connections.
- The second line sets the path to the directory where our server will look for HTML files. By restricting the file access to the specified directory (via htmlFilePath), we prevent path traversal attacks. These attacks attempt to access sensitive files outside the intended directory, like /etc/passwd in UNIX-based systems. By ensuring the server only reads files within the designated directory, we mitigate this risk. For example, a request like:
would be blocked, as the server is restricted to the defined HTML file path.
3. Handling Server Events
- The first line listens for new client connections. Whenever a client connects to the server, the
handleNewConnection
function is triggered to handle the interaction, such as processing requests and sending responses. - The second line listens for any errors that occur during the server's operation. If an error is thrown, it will be caught here, and logged.
4. Writing the handleNewConnection
function
The function takes the socket
object as a parameter, which is automatically passed to it whenever a new client connection is established, triggering the function. This socket
object represents the communication channel between the server and the connected client, allowing us to read data from and send responses to the client.
Let's go through the function step-by-step.
1. New Connection
logs "New Connection!", whenever a new client connects to the server.
2. Listening for Data
This adds a listener to the socket object. So, whenever the clients sends data to the server we can handle it properly using a callback function that takes data as a parameter, which is the actual data in the form of raw bytes sent by the client.
Now, inside the callback...
Here, we convert data from raw bytes to string and split the request with space
as a separator. As you might remember, an HTTP/0.9 Request is just a single line with Request Type and a Path separated by space.
Then, we check if the length of the array is 2, this is necessary. Because if someone made an invalid request such as this:
OR
The server will return an error indicating it's an Incomplete Request.
3. Handling Request
In the first condition, we check if the path is / and if it is, we serve the index.html file else we server the requested file based on the path.
In the second condition, we check whether the Request Type is GET or not, and if not we return an Error. Because in HTTP/0.9, the protocol only supports GET requests, so if we receive anything else, the server will respond with an error indicating that only GET requests are supported.
4. Sending the Response
Remember, we set a default path for html files? This is where we use it. So, if htmlFilePath is "./html" and htmlFile (the requested file path) is "/index.html", then the final path will be "./html/index.html".
Then we read the file from the system, and send it back to the client. If the requested file does not exist, we send and error. Though, the protocol originally ends the connection without any error messages.
Finally, we end the connection. As in HTTP/0.9, a new connection has to be made for every request. So, every connection will be closed after the response is sent.
5. End Connection listener
This is listener for when the connection is ended. It simply logs "Connection closed!" after a connection is closed.
6. Starting the Server
The listen method is used to bind the server to a specific network interface and port. It ensures that the server starts accepting incoming client connections.
- host - This binds the server to the loopback address (127.0.0.1), restricting access to local clients only.
- port - This is the port number on which the server listens for incoming connections.
Demo
Connecting with the server using the netcat nc tool and send a simple request.
Request
Response
Conclusion
And, that's it! The HTTP Server is done. Of course, there's so much you can do with it. you could extend this server to support HTTP/1.0 or even add logging to track requests.
The full codebase is available on my GitHub Repo.
Stay tuned for the next blog!
🎉 HAPPY NEW YEAR!!! 🎉