# Ulfius: an HTTP and websocket framework for C programs

> A C framework for building REST APIs and websockets on top of GNU Libmicrohttpd, Jansson and Libcurl, aimed at services with a small memory footprint such as embedded systems. The main risk is not the code but the release history, since the newest published tag is 2.7.15 from 2023-11-14.

**babelouest/ulfius** — Web Framework to build REST APIs, Webservices or any HTTP endpoint in C language. Can stream large amount of data, integrate JSON data with Jansson, and create websocket services

- Repository: https://github.com/babelouest/ulfius
- Website: https://babelouest.github.io/ulfius
- Stars: 1,146 · Forks: 188
- Language: C
- License: LGPL-2.1
- Published: 2026-10-19 · Updated: 2026-10-19 · Language: en
- Canonical page: https://hysenlabs.com/projects/babelouest-ulfius

## What Ulfius is for

Ulfius is an HTTP framework for REST applications written in C. Its stated purpose is to facilitate the creation of web applications in C programs with a small memory footprint, and the README gives embedded systems as the example case.

The framework is not written from scratch. It sits on GNU Libmicrohttpd for the backend web server, Jansson for JSON manipulation, and Libcurl for the HTTP and SMTP client API. That stack is the first thing to understand about the project, because it determines both the capabilities and the cost. You inherit Libmicrohttpd's threading model, Jansson's JSON API, and Libcurl's client behaviour, and you inherit their dependencies as well.

The feature set covers the usual HTTP server duties plus two less common ones. You can create a webservice in a separate thread, stream large amounts of data with a reduced memory footprint, and implement server-side websockets with the message exchange handled in dedicated callback functions. On the client side there are HTTP and HTTPS and SMTP requests with the response parsed into a dedicated structure, plus client websocket requests, also with dedicated callbacks.

There is also a command line tool for talking to a remote websocket, `uwsc`, which lives in the `tools/uwsc` directory of the repository. That tool is a small but telling sign of how the project is used in practice: debugging a websocket endpoint with a script rather than a browser.

The licence is LGPL-2.1, which is a meaningful difference from the MIT and Apache licences most comparable projects use. The LICENSE file sits at the repository root and the Makefile header repeats the GNU Lesser General Public License version 2.1 terms.

## The hello world callback shows the whole API shape

The README's complete example is the best documentation of the API, because the framework's programming model fits in one screen. The compile line is in the file's own comment:

```C
/**
 * test.c
 * Small Hello World! example
 * to compile with gcc, run the following command
 * gcc -o test test.c -lulfius
 */
```

The callback signature takes a request and a response and returns a status:

```C
int callback_hello_world (const struct _u_request * request, struct _u_response * response, void * user_data) {
  ulfius_set_string_body_response(response, 200, "Hello World!");
  return U_CALLBACK_CONTINUE;
}
```

The request is a pointer to const, so the callback cannot mutate it. The response is mutable. The `user_data` pointer is your channel for per-endpoint state. Returning `U_CALLBACK_CONTINUE` tells the framework the callback is done, and the returned HTTP status is set through the helper rather than by writing the response directly.

The endpoint is registered by value, with a method, a path pattern, a format string, a format argument count, the callback and the user data:

```C
ulfius_add_endpoint_by_val(&instance, "GET", "/helloworld", NULL, 0, &callback_hello_world, NULL);
```

The framework is then started, the program waits for input, and the instance is stopped and cleaned up. The `getchar()` call in the example is a deliberate way to keep the process alive without a signal handler, which is worth copying only if you are writing a toy; a real service needs its own shutdown path.

## Path parameters and the endpoint registration model

Endpoints are identified by their method and their URL path, and the README is explicit that the path may carry optional parameters, with `/api/doc/@id` given as the example. That syntax is the framework's own, and it is a plain path segment rather than a regular expression, which keeps matching cheap and predictable.

Registration is done with `ulfius_add_endpoint_by_val`, which takes the values directly, as the hello world shows. 

The two free arguments after the path are the format string and the number of format arguments, both `NULL` and `0` in the hello world. This is how the framework knows how to bind path parameters such as `@id` into the callback, and it is the mechanism a reader has to understand before adding a parameterized route.

The README also lists an `example_callbacks/` directory with two ready-made callbacks. One is a static file server, described as providing static files of a specific folder. The other is an OAuth2 bearer callback that checks the validity of an OAuth2 bearer JWT token, and it requires the libjwt library. The second one is the model to follow if you need authentication on an endpoint: the framework does not provide one, it provides a working example that depends on another project.

The `example_programs/` directory holds the fuller sample applications and their documentation, and the `doc/` directory holds the Doxygen configuration, with a `doxygen` target in the Makefile to build the HTML output.

## Building from source and the Makefile targets

Installation details live in `INSTALL.md` and the API reference in `API.md`, both at the repository root, and the online documentation is a Doxygen rendering of the API. There is no package manager instruction in the README itself, so the honest way to install it is to follow `INSTALL.md`.

The top level Makefile is a dispatcher. The `all` target descends into `./src` to build the library and into `./tools/uwsc` to build the command line tool. The `examples` target builds the `example_programs` directory, `check` descends into `./test` to run the test suite, `install` and `uninstall` install the library and the tool, and `doxygen` runs `doxygen doc/doxygen.cfg` to produce `doc/html`.

There is also a `debug` target that recurses with the `debug` flag into the same two directories, and a `clean` target that descends into the library, examples, tool and tests, including a `test/autobahn` subdirectory, and removes `doc/html`.

The Autobahn test suite reference in the clean target is worth a second look, because the WebSocket Test Suite is the standard conformance check for a websocket implementation and its presence in the tree tells you the project tests against it.

The `UWSCFLAG` conditional guards the tool build, and `libulfius.pc.in` at the root is the pkg-config template, which means the library is meant to be consumed through pkg-config with a standard `-lulfius` link flag, exactly as the hello world comment shows.

## Streaming, threads and the small memory footprint claim

The streaming capability is the feature that distinguishes Ulfius from a plain HTTP server wrapper, and it is stated twice in the README: you can stream large amounts of data with a reduced memory footprint, and you can create a webservice in a separate thread.

The mechanism is not documented in the README itself. There is no section explaining how the streaming response API works, no chunking example, and no mention of backpressure or of what happens when a client disconnects mid-stream. The API reference in `API.md` and the doxygen documentation are where that lives.

The thread model inherits from Libmicrohttpd rather than being defined by Ulfius, and the README does not describe the threading configuration, so a service with many concurrent long-lived streams needs to read the Libmicrohttpd options and the Ulfius instance setup in `API.md` before sizing the thread pool.

The small memory footprint claim is directional rather than measured here: the README states the purpose without giving a figure, and there is no benchmark in the repository to check it against. Treat it as a design intent that is plausible given the C implementation and a small dependency footprint, not as a published measurement.

For the websocket side, the same pattern applies. The message exchange runs in dedicated callback functions, and the `tools/uwsc` client exists to drive a websocket endpoint by hand, which is the kind of tool you want when a handshake fails in a browser and you need to see the raw frames.

## What is not in the box, and how the ecosystem fills the gaps

The most useful thing about this project is the list of projects built on it, because it shows which gaps the framework leaves open and how a real user fills them.

Glewlwyd is described as a lightweight SSO server providing OAuth2 and OpenID Connect authentication. Le Biniou is a music visualization and VJing tool. Angharad is a house automation system for ZWave and other device types. Hutch is a safe locker for passwords and other secrets using JavaScript client side encryption only. Taliesin is a lightweight audio streaming server, and Taulas is a Raspberry Pi serial interface for Arduino devices implementing a house automation protocol for Angharad.

Read that list as a description of the framework's edges. Authentication, OAuth2 and OpenID Connect, session handling, secret storage and house automation protocols are not in Ulfius; they are built next to it, sometimes by the same author, who also maintains Glewlwyd, Angharad, Hutch and Taliesin.

So the real alternative is not another C HTTP library, it is the same author's stack. If you need SSO and OAuth2 in a C service, Glewlwyd is the piece that pairs with Ulfius, and the example OAuth2 bearer callback in `example_callbacks/` is the code that connects them, using libjwt for token validation.

The other alternative is to not use C at all for the HTTP layer, and to write the service in a language with a standard library and a maintained HTTP server. That is the right call when the deployment target is not actually memory constrained, and it is the decision the framework's premise pushes you toward accepting: Ulfius earns its complexity budget only where a general purpose runtime is not an option.

## Release history and the gap between the tag and the branch

The repository is not archived and the last push was on 2026-07-14, so work is landing on the default branch, which is master.

The published release history tells a different story. The most recent tags are v2.7.15 from 2023-11-14, v2.7.14 from 2023-09-16 and v2.7.13 from 2023-02-08. A `CHANGELOG.md` sits at the root, and it is the file to read to see what has changed between the last tag and the branch head.

That gap of roughly two and a half years between v2.7.15 and the current branch is the single most important practical fact about adopting Ulfius today. If you build from the tag, you are on 2023 code with 2023 upstream dependencies, and the fix for a Libcurl or Jansson advisory will not be there. If you build from the branch, you are on unreleased code with no version number to pin against.

A `CMakeLists.txt` and a `cmake-modules/` directory sit alongside the Makefile, so a CMake-based build is possible as well as the recursive Makefiles.

The CII Best Practices badge and the CodeQL workflow badge in the README header are the only published signals about process, and the `test/` directory with its Autobahn subdirectory is the concrete evidence of testing effort. The LGPL-2.1 terms are the constraint to review before shipping: a permissive Apache or MIT library can be linked into a proprietary product far more freely than an LGPL library can, and the `LICENSE` file at the root is where the actual obligations are stated.

## Conclusion

Ulfius fits C teams that need an HTTP or websocket endpoint inside a program with a small memory footprint, particularly on embedded targets, and that are comfortable with the GNU Libmicrohttpd, Jansson and Libcurl dependency chain. It does not fit a team that wants a maintained dependency on a fast-moving stack, since the newest published release is 2.7.15 from 2023-11-14 while the last push was on 2026-07-14, and it does not fit projects that need a router, an ORM or a templating layer, because the framework provides none of those. Before adopting it, read `INSTALL.md` and `API.md` in the repository, confirm that your build can link the three upstream libraries, and decide from the LGPL-2.1 terms in `LICENSE` whether dynamic linking suits your distribution model.

## FAQ

### How do I install Ulfius?

The README points to the `INSTALL.md` file in the repository root for installation details, and to `API.md` for the API reference. The library is built with the top level Makefile, whose `install` target installs the library and the `uwsc` tool.

### Which C libraries does Ulfius depend on?

Ulfius is based on GNU Libmicrohttpd for the backend web server, Jansson for JSON manipulation, and Libcurl for the HTTP and SMTP client API. The OAuth2 bearer example callback additionally requires libjwt.

### How do I add an endpoint with a path parameter in Ulfius?

Register the endpoint with `ulfius_add_endpoint_by_val`, giving the method and a path such as `/api/doc/@id`, plus the format string and format argument count. The `@` prefix is how Ulfius marks a parameter in the path.

### Does Ulfius provide authentication?

Not by itself. The `example_callbacks/` directory contains a static file server callback and an OAuth2 bearer callback that validates a token using libjwt, and the README's list of projects using Ulfius points to Glewlwyd as a separate OAuth2 and OpenID Connect server.

### Can Ulfius serve websocket connections and act as a websocket client?

Yes. Server websockets and client websocket requests are both supported, with the message exchange executed in dedicated callback functions. A command line client for connecting to a remote websocket is provided in `tools/uwsc`.

## Sources

- [babelouest/ulfius on GitHub](https://github.com/babelouest/ulfius)
- [License: LGPL-2.1](https://github.com/babelouest/ulfius/blob/master/LICENSE)
- [Project website](https://babelouest.github.io/ulfius)
- [README](https://github.com/babelouest/ulfius/blob/master/README.md)
- [Releases](https://github.com/babelouest/ulfius/releases)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/babelouest-ulfius
