Small C web toolkit for server-rendered apps.
- HTTP/1.1 server
- optional OpenSSL TLS
- HTML builder
- small JS helpers
- JSON-backed translations
- optional SQLite/PostgreSQL SQL helper
- CMake install package
./build.shTLS build:
./build.sh -tlsEncrypted login tokens:
./build.sh -cryptoSQLite database support:
./build.sh -sqlitePostgreSQL database support:
./build.sh -postgresSQLite and PostgreSQL:
./build.sh -dbInstall:
cmake --install build --prefix /your/prefixStress tool:
bin/server_stress --mode mixed --workers 4 --connections 1000 --requests 10000find_package(syphax_web CONFIG REQUIRED)
target_link_libraries(my_target PRIVATE syphax_web::syphax_web)#include "sw_html.h"
#include "sw_server.h"
static void handler(sw_connection* c, const sw_http_message* hm, void* user_data) {
(void)user_data;
if (!sw_http_is(hm, "GET", "/")) {
sw_http_replyf(c, 404, "text/plain; charset=utf-8", "Not Found");
return;
}
sw_buffer* h = sw_buffer_new();
sw_html(h, sw_attrs(sw_attr("lang", "en")), {
sw_body(h, sw_attrs(), {
sw_h1(h, sw_attrs(), {
sw_text(h, "Syphax Web");
});
});
});
sw_http_reply(c, 200, "text/html; charset=utf-8", sw_buffer_data(h), sw_buffer_len(h));
sw_buffer_free(h);
}
int main(void) {
return sw_server_listen("http://0.0.0.0:8000", NULL, handler, NULL);
}Open http://127.0.0.1:8000.
Pass NULL for defaults. Use sw_server_config_default() to override backlog, worker count, connection limits, buffer caps, keep-alive limits, and timeouts.
Long-running servers can be managed explicitly:
sw_server_config config = sw_server_config_default();
config.worker_count = 4;
sw_server* server = sw_server_create(&config);
sw_server_add_http(server, "http://0.0.0.0:8000", handler, NULL);
sw_server_run(server);
sw_server_destroy(server);char theme[32];
sw_sessions* sessions = sw_sessions_create(NULL);
sw_http_get_cookie(hm, "theme", theme, sizeof(theme));
sw_http_set_cookie(c, "theme", "dark", NULL);
sw_session* session = sw_sessions_start(sessions, c, hm);
sw_session_set(session, "user_id", "42");Default cookies use Path=/, HttpOnly, SameSite=Lax, and Secure on TLS connections.
Build with ./build.sh -crypto or ./build.sh -tls.
sw_tokens* tokens = sw_tokens_create(NULL);
sw_token* token = sw_tokens_login(tokens, c, hm, user_id);
sw_token_set(token, "role", "admin");
token = sw_tokens_current(tokens, c, hm);
const c8* user_id = sw_token_get(token, "user_id");
sw_tokens_logout(tokens, c, hm);Apps still check passwords and load users. Syphax-Web stores token data in memory and sends only an encrypted cookie.
Build with ./build.sh -sqlite, ./build.sh -postgres, or ./build.sh -db.
#include "sw_db.h"
sw_db* db = sw_db_open(NULL); /* sqlite://:memory: when SQLite is enabled */
sw_db_exec(db, "CREATE TABLE notes (title TEXT, hits INTEGER)");
sw_db_stmt* stmt = sw_db_prepare(db, "INSERT INTO notes(title, hits) VALUES(?, ?)");
sw_db_bind_text(stmt, 1, "Hello");
sw_db_bind_int(stmt, 2, 1);
sw_db_step(stmt);
sw_db_finalize(stmt);
stmt = sw_db_prepare(db, "SELECT title, hits FROM notes WHERE hits = ?");
sw_db_bind_int(stmt, 1, 1);
while (sw_db_step(stmt) == SW_DB_ROW) {
const c8* title = sw_db_column_text(stmt, 0);
i64 hits = sw_db_column_int(stmt, 1);
(void)title;
(void)hits;
}
sw_db_finalize(stmt);
sw_db_close(db);Use sqlite://path.db, sqlite://:memory:, postgres://..., or postgresql://....
bin/07_database uses SQLite by default; set SYPHAX_WEB_DB_URL for PostgreSQL-only runs.
Native HTTPS is enabled with ./build.sh -tls.
int main(void) {
sw_tls_config tls = sw_tls_config_default();
tls.certificate_file = "/etc/ssl/example/fullchain.pem";
tls.private_key_file = "/etc/ssl/example/privkey.pem";
return sw_server_listen_tls("https://0.0.0.0:8443", NULL, &tls, handler, NULL);
}Local example certs:
TRUST_STORES=system,nss mkcert -install
mkcert \
-cert-file examples/shared/localhost.local.crt \
-key-file examples/shared/localhost.local.key \
localhost 127.0.0.1 ::1
./build.sh -tls
./bin/02_httpsThe TLS examples also accept:
SYPHAX_WEB_TLS_CERT=/path/to/fullchain.pem \
SYPHAX_WEB_TLS_KEY=/path/to/privkey.pem \
./bin/02_httpsServe public assets from a fixed docroot:
if (sw_http_is(hm, "GET", "/style.css")) {
sw_http_serve_path(c, "resources", hm->uri);
return;
}sw_http_serve_path keeps requests inside the docroot.
Modal behavior uses the browser's native <dialog> element. Syphax-Web only binds events to dialog actions; markup, content, validation, and styling stay in the application.
#include "sw_js.h"
sw_button(h, sw_attrs(
sw_attr_no_translate("id", "open-settings"),
sw_attr_no_translate("type", "button")
), {
sw_text(h, "Open Settings");
});
sw_dialog(h, sw_attrs(
sw_attr_no_translate("id", "settings-dialog"),
sw_attr_no_translate("aria-labelledby", "settings-title")
), {
sw_h2(h, sw_attrs(sw_attr_no_translate("id", "settings-title")), {
sw_text(h, "Settings");
});
sw_p(h, sw_attrs(), {
sw_text(h, "Application-owned modal content.");
});
sw_div(h, sw_attrs(sw_attr_no_translate("class", "actions")), {
sw_form(h, sw_attrs(sw_attr_no_translate("method", "dialog")), {
sw_button(h, sw_attrs(
sw_attr_bool("autofocus", 1),
sw_attr_no_translate("type", "submit"),
sw_attr_no_translate("value", "done")
), {
sw_text(h, "Done");
});
});
sw_button(h, sw_attrs(
sw_attr_no_translate("id", "request-settings-close"),
sw_attr_no_translate("type", "button")
), {
sw_text(h, "Cancel");
});
sw_button(h, sw_attrs(
sw_attr_no_translate("id", "force-settings-close"),
sw_attr_no_translate("type", "button")
), {
sw_text(h, "Close Now");
});
});
});
sw_js_modal(h,
.trigger_id = "open-settings",
.target_id = "settings-dialog",
.close_on_backdrop = 1
);
sw_js_modal(h,
.trigger_id = "request-settings-close",
.target_id = "settings-dialog",
.return_value = "cancel",
.action = SW_JS_MODAL_REQUEST_CLOSE
);
sw_js_modal(h,
.trigger_id = "force-settings-close",
.target_id = "settings-dialog",
.return_value = "closed",
.action = SW_JS_MODAL_CLOSE
);SW_JS_MODAL_REQUEST_CLOSE fires the standard cancelable cancel event. SW_JS_MODAL_CLOSE closes immediately. Native method="dialog" forms need no JavaScript close binding, and their submitting button value becomes dialog.returnValue.
Use standard dialog events for application logic:
const dialog = document.getElementById('settings-dialog');
dialog.addEventListener('cancel', function (event) {
if (hasUnsavedChanges()) event.preventDefault();
});
dialog.addEventListener('close', function () {
console.log(dialog.returnValue);
});Every dialog needs an accessible name through aria-labelledby or aria-label and a visible close control. Use autofocus when the browser's default initial focus is unsuitable. Backdrop dismissal is disabled unless close_on_backdrop is enabled.
Styling is application-owned:
dialog {
max-width: 36rem;
border: 1px solid #444;
background: #222;
color: #fff;
}
dialog::backdrop {
background: rgb(0 0 0 / 65%);
}One JSON object per source string:
{
"Search": {
"ar": "بحث",
"ja": "検索"
},
"Language": {
"ar": "اللغة",
"ja": "言語"
}
}#include "sw_translator.h"
sw_translator* tr = sw_translator_create("resources/translations.json",
.code = "en",
.label = "English",
.direction = SW_LANGUAGE_DIRECTION_LTR
);
sw_translator_add(tr, .code = "ar", .label = "Arabic", .direction = SW_LANGUAGE_DIRECTION_RTL);
sw_translator_add(tr, .code = "ja", .label = "Japanese", .direction = SW_LANGUAGE_DIRECTION_LTR);
sw_translator_set_language(tr, "ja");English source text is the fallback. The installed file is share/syphax_web/translations.json.
Large multipart uploads save only to caller-provided paths; the caller owns those files and cleanup.
Run one example at a time. They all bind port 8000.
Open HTTP examples at http://127.0.0.1:8000 and HTTPS examples at https://127.0.0.1:8000.
For HTTPS examples, build with ./build.sh -tls.
For the database example, build with ./build.sh -sqlite, ./build.sh -postgres, or ./build.sh -db.
bin/01_http: basic HTTP app; HTTPS in a TLS buildbin/02_https: HTTPS status pagebin/03_static_site: static HTTPS sitebin/04_live_queue: live form demobin/05_folder_app: folder-backed appbin/06_session_login: HTTPS session login demobin/07_database: database-backed counter in database builds
Use Syphax-Web for embedded tools, local dashboards, internal services, and small server-rendered apps.
Use a front proxy for HTTP/2, compression, rate limits, and edge policy.
TLS listeners speak HTTP/1.1. Request bodies are bounded. Chunked request bodies are a future add.
MIT