Implementing an HTTP server in JavaScript from scratch (1)

This article illustrates how HTTP servers for Javascript web apps (such as Node.JS) are implemented (in a very simplified way). The code uses JavaScriptCore as its script engine and Mojo, a dialect of Python compiled into native code, for the event-based runtime.

We want to be able to run web apps written in JavaScript. To make things as simple as possible, let's assume that there's always a function called http_handler that takes one argument, the request, and returns a value as the response (such as a JSON object).

function http_handler(req) {
    return {
        "message": "Hello, world!",
        "uri": req.uri
    }
}

The main function will just catch exceptions raised in the run_server function and write the error message to stderr.

fn main():
    try:
        run_server()
    except e:
        print(e, file=stderr)
        exit(1)

(Technically, there are no exceptions in Mojo since the language doesn't have stack unwinding. Errors are returned as values, which is transparently taken care of by the compiler.)

The following symbols need be imported. We use Libevent to implement the event-based HTTP server. A low-level API is provided by libevent. The javascript package is a wrapper around JavaScriptCore.

from libevent import Libevent
from sys import argv, stderr, exit
from utils import StringRef
from javascript import JSGlobalContext, JSContext, JSValue, JSObject, js_evaluate
from memory import UnsafePointer

To make things simple, we'll use global variables for the Libevent instance and for the low-level pointers to a JavaScript context and the JavaScript handler.

var libevent = Libevent()
var ctx_ptr = UnsafePointer[NoneType]()
var handler_ptr = UnsafePointer[NoneType]()

The request handler in Mojo looks as follows:

fn request_handler(req: UnsafePointer[NoneType], arg: UnsafePointer[NoneType]):
    ctx = JSContext(ctx_ptr)
    handler = JSObject(handler_ptr)
    uri = libevent.evhttp_request_get_uri(req)
    request = JSObject(ctx)
    request.set_property(ctx, "uri", JSValue(ctx, uri))
    var response_data: String
    var response_status: Int
    try:
        response = handler.call(ctx, request)
        response_data = response.as_json_string(ctx)
        response_status = 200
    except e:
        response_data = "handler exception: " + str(e)
        response_status = 500
    outbuf = libevent.evhttp_request_get_output_buffer(req)
    _ = libevent.evbuffer_add(outbuf, response_data)
    libevent.evhttp_send_reply(req, response_status, "", outbuf)

The req argument is the pointer to the libevent request. For the JavaScript handler, we create an object instance called request and set its uri property (there should be more data such as HTTP headers). We then evaluate the script by using handler.call(...). JavaScript exceptions are translated into Mojo "exceptions" so this needs to be done in a try-except block.

The response is converted into a string using the as_json_string method, which is then sent as the reply to the request using libevent's evhttp_send_reply function. Errors are returned with the status code 500 (internal server error).

(Once Mojo has support for writing HTTP servers in its standard library, it should be used instead of libevent.)

In the next post, we'll see the code for the run_server function.

MojoJavaScript
Avatar for Petr Homola

Written by Petr Homola

Studied physics & CS; PhD in NLP; interested in AI, HPC & PLT

Loading

Fetching comments

Hey! 👋

Got something to say?

or to leave a comment.