Creating an API with Handy-Httpd
Written on , by Andrew Lalis.
Introduction
When I started programming in the D language, I was coming fresh from the world of Java and Spring, where setting up a new web API for a project was pretty much as simple as creating a new method annotated with @GetMapping
. In the D world, the closest thing to an all-encompassing web framework is Vibe.d, which is quite convoluted (from a beginner's perspective), and is heavily focused on its own ecosystem and async implementations. That gives it a sort of "walled-garden" feel. There were other, smaller HTTP web server libraries, but none were well documented. So in the end, I set out to build my own HTTP server, which then evolved into more of an HTTP web framework. That's Handy-Httpd.
Handy-Httpd was built on the concept of an HttpRequestHandler
, a single unit that takes an HTTP request and does stuff with it.
With this request handler, and the principle of composition, we can build up a complete web framework from a few well-purposed handlers.
Setting Up
As with any web server, there's some startup and configuration that needs to be done to get everything working, so we'll get that out of the way first.
We need to create a new D project, and add handy-httpd
as a dependency. Do so with the dub
CLI tool, or however you prefer to create D projects. Then we'll set up the basic HTTP server in our project's main file.
The typical boilerplate consists of three main things:
- Configuration
- Building our request handler
- Starting the server
For illustrations' sake, I've configured this server to use 3 workers in its pool for handling requests. You might need more depending on your traffic. I've also created a new PathDelegatingHandler
which will serve as the basis for the API's set of endpoints. Check out the documentation on this handler for a detailed explanation of what it can do; in short, we can register new API endpoints to it.
Adding an Endpoint
Now that our server is set up, all we need to do is define some endpoints for users to interact with. This is as simple as creating an HttpRequestHandler
and registering it with our pathHandler
that we defined on line 7.
To keep things simple to start, we'll add a status endpoint that just returns the string "online". For something this basic, there's no need to create a whole new class; instead, we'll just define a function.
And then, we'll register it with our path handler so that GET requests to /status
will be directed to the handleStatus
function.
Done! We can now run our project and navigate to localhost:8080/status, and we should see the text "online". It's that simple.
Posting Data to Our API
A GET endpoint is easy enough, but making an endpoint that accepts the user's data isn't too hard either.
You're not limited to only JSON though; users can upload URL-encoded data, or XML, or literally anything else. It's just that D's standard library provides a JSON implementation, so Handy-Httpd gives you some help with it.
Under the hood, Handy-Httpd uses the streams library for the underlying data transfer, so if you're looking for a completely custom solution, you'll need to read from ctx.request.inputStream
, which is a InputStream!ubyte
. Also note that each request has a pre-allocated receiveBuffer
that you can use instead of creating your own separate buffer.
Adding Middleware with Filters
One of the buzz words these days in web programming is "middleware", which is just a fancy term for anything that sits between two systems and performs some limited set of functions on the data that is passed between the systems.
In Handy-Httpd, we've provided a convenient method of adding middleware to the HTTP request handling flow with the HttpRequestFilter
, FilterChain
, and the FilteredRequestHandler
.
Suppose we want to authenticate requests to certain endpoints. That's a pretty straightforward task that many web frameworks deal with. In Handy-Httpd, you'd create a filter that reads a JWT token from the request's header, decodes it, and adds user info to the request context's metadata
property.
Then to actually use your newly-created TokenFilter
to safeguard your endpoint, you'd use the FilteredRequestHandler
to wrap your endpoint and set the TokenFilter as one of the pre-request filters.
That's all there is to it! No runtime magic, just composing a handler that does exactly what you tell it to.
Additional Remarks
While I've done my best (which admittedly isn't that good) to keep Handy-Httpd lightweight and performant, that really isn't the number 1 goal. My goal was always to build a simple HTTP server with some nice-to-have conveniences that don't require months of diligent study to use effectively. I wanted something with sufficient documentation so that Handy-Httpd can be a D programmer's first introduction to HTTP servers.
We're now on version 7.10.4 as of writing this article, and I feel like Handy-Httpd is in a place where I can comfortably say that it's reached those goals. Thanks for reading all the way through, and if you do try out Handy-Httpd, please do let me know what you think!