Modern web framework designed to be full featured and powerful all while being extremely simple to use.
Below is a very simple demonstration of how to create a handler for a GET request. More examples can be found in the examples directory. Each example has a make target.
You can make all examples with make examples_all PAPAGO_USE_MAPLE=1.
make exampleA convenience script is included to generate certificates to be able to run the example below. generate_certs.sh
make example_sslmake example_websocket- RESTful Routing - GET, POST, PUT, DELETE, PATCH support
- Path Parameters - Dynamic routes like
/users/:id - Wildcard URIs - Example:
/api/v1/* - Query Parameters - Parse URL query strings
- HTML Templates - Easy dynamic content via server side templating
- Middleware System - Global and path-specific middleware
- File stream - video / audio / large files, zero-copy, automatic MIME type detection
- WebSocket Support - Real-time bidirectional communication
- WebSocket Client - included
- Embedded File Support - Embed HTML, JS, CSS, etc into the application
- JSON Responses - Built-in JSON helpers
- Static Files - Serve files from directories
- Thread-Safe - Built on proven concurrent architecture
- Low Dependencies - Only requires libmicrohttpd + libwebsockets
- Rate limiting by IP
- Compression with Gzip
- Metrics collection and exposure via Prometheus endpoint
- Simple HTTP Client
HTML, CSS, JS, JSON, XML, TXT PNG, JPG, GIF, SVG, ICO, WebP MP4, WebM, OGG MP3, WAV, M4A PDF, ZIP, TAR, GZ WOFF, WOFF2, TTF
- libmicrohttpd
- libwebsockets
- openssl
- libmaple - Maple Template Engine
Papago has the template engine (Maple) disabled by default. If this component is required, add PAPAGO_USE_MAPLE=1 to the make command when building.
makesudo make installaDefault server runs on port :8080.
#include <stdio.h>
#include <papago.h>
void
hello_handler(papago_request_t *req, papago_response_t *res, void *user_data)
{
PAPAGO_UNUSED(req);
PAPAGO_UNUSED(user_data);
papago_res_json(res, "{\"message\":\"Hello, World!\"}");
}
int
main(void)
{
papago_t *server = papago_new();
papago_route(server, PAPAGO_GET, "/hello", hello_handler, NULL);
papago_config_t config = papago_default_config();
papago_start(server, &config); // blocking
papago_destroy(server);
return 0;
}Build and run:
cc -o hello hello.c -lpapago
./helloTest:
curl http://localhost:8080/hello
# {"message":"Hello, World!"}// basic routes
papago_route(server, PAPAGO_GET, "/", index_handler, NULL);
papago_route(server, PAPAGO_POST, "/users", create_user, NULL);
papago_route(server, PAPAGO_PUT, "/users/:id", update_user, NULL);
papago_route(server, PAPAGO_DELETE, "/users/:id", delete_user, NULL);
// path parameters
void
user_handler(papago_request_t *req, papago_response_t *res, void *user_data)
{
const char *id = papago_req_param(req, "id");
// use id...
}
papago_route(server, PAPAGO_GET, "/users/:id", user_handler, NULL);
// query parameters
void
search_handler(papago_request_t *req, papago_response_t *res, void *user_data)
{
const char *q = papago_req_query(req, "q");
const char *page = papago_req_query(req, "page");
// use q and page...
}
papago_route(server, PAPAGO_GET, "/search", search_handler, NULL);Papago middleware consists of defining 2 functions, before and after and assigning them to the papago_middleware_t struct. The before function is ran on every request and is required to be present. The after function is optional.
static bool
mw_before(papago_request_t *req, papago_response_t *res, void *user_data);
static void
mw_after(papago_request_t *req, papago_response_t *res, void *user_data);papago_middleware_t middleware = {
.before = mw_before,
.after = mw_after,
.user_data = NULL,
};After defining the middleware and assigning the functions to the struct, Papago supports setting the middleware to run globally or on specific routes. This is especially helpful when writing loggers or authentication middleware.
// global middleware - runs on ALL routes
papago_middleware_add(server, middleware);
// path-specific - runs only on /api/* routes
papago_middleware_path_add(server, "/api", middleware);Examples of some common middlewares (logger, rate-limiting) can be found in the examples directory.
Below is example code of how to use the websocket functionality. For the available websocket client API, reference the Papago Websocket Client Header file.
void
ws_on_connect(papago_ws_connection_t *conn)
{
printf("Client connected: %s\n", papago_ws_get_client_ip(conn));
papago_ws_send(conn, "{\"type\":\"welcome\"}");
}
void
ws_on_message(papago_ws_connection_t *conn, const char *message,
size_t length, bool is_binary)
{
printf("Received: %s\n", message);
// echo back
papago_ws_send(conn, message);
// or broadcast to all
papago_ws_broadcast(papago_get_current_server(), message);
}
void
ws_on_close(papago_ws_connection_t *conn)
{
printf("client disconnected\n");
}
void
ws_on_error(papago_ws_connection_t *conn, const char *error)
{
fprintf(stderr, "error: %s\n", error);
}
// register websocket endpoint
papago_ws_endpoint(server, "/ws",
ws_on_connect, ws_on_message, ws_on_close, ws_on_error);Client-side JavaScript:
const ws = new WebSocket('ws://localhost:8081/ws');
ws.onopen = () => ws.send('Hello!');
ws.onmessage = (e) => console.log('Received:', e.data);void
handler(papago_request_t *req, papago_response_t *res, void *user_data)
{
// read request
const char *header = papago_req_header(req, "Content-Type");
const char *id = papago_req_param(req, "id");
const char *search = papago_req_query(req, "q");
const char *body = papago_req_body(req);
// send response
papago_res_status(res, PAPAGO_STATUS_OK);
papago_res_header(res, "X-Custom", "value");
papago_res_json(res, "{\"status\":\"ok\"}");
}To build the client along with the Papago framework, use: make PAPAGO_WITH_WSC and sudo make install PAPAGO_WITH_WSC=1.
To expose the Prometheus endpoint, register the metrics handler in your application.
papago_route(server, PAPAGO_GET, "/metrics", papago_metrics_handler, NULL);- HTTP: Thread-per-connection (libmicrohttpd)
- WebSocket: Event loop in separate thread
- Broadcast: Thread-safe with mutex protection
Please feel free to open a PR!
Brian Downs @bdowns328
BSD 2 Clause License.