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.