Dead Simple HTTP Server On macOS Using launchd
home // page // Dead Simple HTTP Server On macOS Using launchd

Dead Simple HTTP Server On macOS Using launchd

Recently I’ve been reading a lot of the Fossil and SQLite code bases. I’ve been very impressed by their cleanliness and the interesting ways in which they break from conventional wisdom in their construction. One thing I stumbled across while perusing a post on the Fossil mailing list was that inetd could be used to create a simple and efficient HTTP server with nothing but some basic C code.

My interest was piqued and I began researching inetd on macOS. What I found was that several years ago macOS had merged the functionality of inetd with that of several other services into something called launchd. This new service has its own XML configuration file format that differs from inetd. I immediately wondered if and how it would be possible to create an HTTP server using this new launchd system.

After searching the internet to see if someone else had done this I was disappointed to find that, while there was a lot of helpful information, no one had really tried to do this with launchd. So I set about reading man-pages, looking at inetd examples, and trying to cobble together my own working HTTP server under launchd. Two of the most useful sources of information were the following:

  • man launchd – explains how to use the launchd/launchctl command-line programs.
  • man launchd.plist – explains the XML plist format for launchd services.

What follows is the method by which I was able to successfully create a simple launchd HTTP server.

The HTTP Server

I wanted to write the HTTP server in C, mostly because I thought this would be a fun exercise and produce something that would run pretty fast. The below is my dead-simple HTTP server in C. This is based heavily and shamelessly on the SQLite docsrc server, but has been drastically simplified.

The HTTP request is fed to the service through standard input (stdin) and the response is fed back out to the requester via standard output (stdout). It logs additional information to standard error (stderr) which can be redirected by launchd into a log file as we will demonstrate later.

The plist Service

Now that we possess this simple HTTP server, we can turn it into a service that will serve requests over a socket by installing it in launchd. What is really nice is that launchd will handle all of the socket work for us, all we have to do is provide it with a program that reads and writes standard input/output/error.

This defines our new service called com.example.srv. This service invokes the program /path/to/compiled/server (which should obviously be revised to point to the compiled server program). We inform launchd that we want to serve this on port 8888 and process incoming TCP requests on that port. Finally, we also configure a log file that stores the output written to standard error (stderr).

Whenever someone hits localhost:8888 a new process of your server will be launched and run from beginning to end. So you get one process per request.

Installing The Service

Now that we have our simple server program and our plist file we need to install everything. We can do this by copying the plist file to ~/Library/LaunchAgents.

We then invoke launchctl as follows to load our program:

As a note, we can unload our service effectively stopping it with the following command:

Conclusion

Once the service is loaded we can point our browser or the curl command localhost:8888 and see what happens. The following is a screenshot from my browser:

And that’s it, a simple HTTP server that can be expanded to perform more sophisticated tasks right on our mac, and without the need to install Apache or configure nginx.

For more information on launchd and its configuration see the launchd.info website. I also highly recommend checking out the man pages listed at the beginning of this article.