HTTP responses
HTTP responses are formed through the httpctx_t->response object (httpresponse_t*). As with the request, response methods are function pointers attached to the object, so the first argument is always the object itself: ctx->response->send_data(ctx->response, "...").
#include "http.h"
void my_handler(httpctx_t* ctx) {
ctx->response->send_data(ctx->response, "Hello World");
}A response holds a status code, headers, and a body. The body is produced by one of the send_* methods — nothing is sent to the client until one of them is called.
Status code
The status_code field holds the current code (200 by default). Change it by direct assignment before sending the body:
void handler(httpctx_t* ctx) {
ctx->response->status_code = 201; // Created
ctx->response->send_data(ctx->response, "Resource created");
}Standard response
send_default() sends a standard response with the given code and a generated HTML page — handy for errors:
void handler(httpctx_t* ctx) {
if (error) {
ctx->response->send_default(ctx->response, 500); // Internal Server Error
return;
}
if (!found) {
ctx->response->send_default(ctx->response, 404); // Not Found
return;
}
ctx->response->send_data(ctx->response, "Success");
}Full list of codes: HTTP codes.
HTTP headers
Headers are added before the body is sent. add_header takes null-terminated strings, the n variant takes explicit lengths, and add_headeru adds a header only if it is not already set (unique):
void handler(httpctx_t* ctx) {
ctx->response->add_header(ctx->response, "X-Custom-Header", "value");
ctx->response->send_data(ctx->response, "ok");
}Related methods:
| Method | Description |
|---|---|
add_header(key, value) | Add a header (null-terminated) |
add_headern(key, key_len, value, value_len) | Add with explicit lengths |
add_headeru(key, key_len, value, value_len) | Add only if not already set |
add_content_length(length) | Add Content-Length |
get_header(key) | Get a header node by name (case-insensitive) or NULL |
remove_header(key) | Remove a header |
Content-Type is set by the send method
The send_* methods set Content-Type, Connection, and Cache-Control themselves via add_headeru. To override Content-Type, add your own header before calling send_* — the default value will not be duplicated.
Sending data
send_data sends a null-terminated string, send_datan sends a buffer with an explicit length (binary-safe). Both set Content-Type: text/html; charset=utf-8:
void handler(httpctx_t* ctx) {
ctx->response->send_data(ctx->response, "Hello World!");
}
void raw(httpctx_t* ctx) {
const char data[] = { 0x00, 0x01, 0x02, 0x03 };
ctx->response->send_datan(ctx->response, data, sizeof(data));
}JSON
send_json serializes a ready json_doc_t* and sets Content-Type: application/json. The caller frees the document:
#include "json.h"
void handler(httpctx_t* ctx) {
json_doc_t* doc = json_init();
json_token_t* root = json_create_object(doc);
json_object_set(root, "status", json_create_string(doc, "success"));
json_object_set(root, "code", json_create_int(doc, 200));
ctx->response->send_json(ctx->response, doc);
json_free(doc);
}More on building documents: JSON.
Models
An ORM model is serialized to JSON directly via send_model, an array of models via send_models. The set of fields to display is set with the display_fields(...) macro from mparams.h; NULL means all model fields.
#include "user.h"
#include "mparams.h"
void get_user(httpctx_t* ctx) {
user_t* user = user_get(ctx); // example of fetching a model
if (user == NULL) {
ctx->response->send_default(ctx->response, 404);
return;
}
// Only the specified fields
ctx->response->send_model(ctx->response, user,
display_fields("id", "email", "name"));
}An array of models (for listings, for example):
void list_users(httpctx_t* ctx) {
array_t* users = user_list(ctx); // returns array_t*
if (users == NULL) {
ctx->response->send_default(ctx->response, 500);
return;
}
ctx->response->send_models(ctx->response, users,
display_fields("id", "email", "name"));
}The display_fields macro
display_fields(...) expands to a char*[] with a terminating NULL, so it accepts any number of field names: display_fields("id", "name"), or a single NULL for all fields.
Model definitions and CRUD wrappers: Models.
Views
send_view renders a template (view) from a named storage. The first argument is a json_doc_t* with data for the template (can be NULL), followed by the storage name and a path with printf-style formatting:
void index(httpctx_t* ctx) {
json_doc_t* document = json_init();
json_token_t* root = json_create_object(document);
json_object_set(root, "title", json_create_string(document, "Home"));
ctx->response->send_view(ctx->response, document, "views", "/index.tpl");
json_free(document);
}On template syntax: Views.
Redirects
redirect adds the Location header and sets the code:
void handler(httpctx_t* ctx) {
ctx->response->redirect(ctx->response, "/new-location", 301); // permanent
}
void temp(httpctx_t* ctx) {
ctx->response->redirect(ctx->response, "/temporary", 302); // temporary
}Codes 301, 302, 307, 308 are supported. External links (http:///https://) are detected automatically; Connection: Close is added for them.
Files
send_file sends a file by path relative to the server root, send_filen with an explicit path length. Content-Type is detected by extension and range requests (Range) are supported:
void download(httpctx_t* ctx) {
ctx->response->send_file(ctx->response, "files/report.pdf");
}send_filef reads a file from a named storage with printf-style path formatting (see storages in config.json):
void from_storage(httpctx_t* ctx) {
ctx->response->send_filef(ctx->response, "local", "/uploads/%s", "document.pdf");
}If the file is missing or not accessible, the methods send 404/403 themselves. More on storages: Storage.
Cookies
add_cookie adds a Set-Cookie from a cookie_t structure:
typedef struct {
const char* name; /* name (required) */
const char* value; /* value (required) */
int seconds; /* lifetime: >0 — Expires, 0 — Max-Age=0 (deletion) */
const char* path; /* Path (NULL to skip) */
const char* domain; /* Domain (NULL to skip) */
int secure; /* 1 — HTTPS only */
int http_only; /* 1 — not accessible from JavaScript */
const char* same_site; /* "Strict" | "Lax" | "None" (NULL to skip) */
} cookie_t;Setting a cookie on login:
void login(httpctx_t* ctx) {
// ... validate credentials ...
ctx->response->add_cookie(ctx->response, (cookie_t){
.name = "session_token",
.value = "abc123xyz",
.seconds = 3600,
.path = "/",
.domain = ".example.com",
.secure = 1,
.http_only = 1,
.same_site = "Lax"
});
ctx->response->send_data(ctx->response, "Logged in");
}Deleting a cookie — empty value and seconds = 0 (sets Max-Age=0):
void logout(httpctx_t* ctx) {
ctx->response->add_cookie(ctx->response, (cookie_t){
.name = "session_token",
.value = "",
.seconds = 0,
.path = "/"
});
ctx->response->redirect(ctx->response, "/login", 302);
}Reading cookies from the request: Cookie.
CORS
CORS headers are added as ordinary headers via add_header:
void api_handler(httpctx_t* ctx) {
ctx->response->add_header(ctx->response, "Access-Control-Allow-Origin", "*");
ctx->response->add_header(ctx->response, "Access-Control-Allow-Methods", "GET, POST, PUT, DELETE");
ctx->response->add_header(ctx->response, "Access-Control-Allow-Headers", "Content-Type, Authorization");
if (ctx->request->method == ROUTE_OPTIONS) {
ctx->response->status_code = 204;
ctx->response->send_data(ctx->response, "");
return;
}
// main logic...
}Important
- Nothing is sent to the client until one of
send_*(send_data,send_datan,send_json,send_model,send_models,send_view,send_file,send_filef,send_default) orredirectis called. - After sending a response, the handler must terminate (
return) — sending again produces an incorrect response. send_default,send_json, andsend_model*setContent-Typethemselves; do not duplicate it.send_file/send_filefreturn a standard code when a file is missing, so no separate check is needed.