The Modern Web Application Framework for C
Ys is a modern, minimal web application framework for the C programming language. With Ys, you can bootstrap functional APIs with minimal dev effort using a rich feature-set of high-level utilities.
With Ys you can do something like this
#include "libys.h"
// ...
ys_response *handler(ys_request *req, ys_response *res) {
ys_set_header(res, "Content-Type", "text/plain");
ys_cookie *c = ys_cookie_init(COOKIE_ID, sid);
ys_cookie_set_expires(c, ys_n_minutes_from_now(10));
ys_set_cookie(res, c);
ys_set_body(res, "Hello World!");
ys_set_status(res, YS_STATUS_OK);
return res;
}
int main() {
ys_router_attr *attr = ys_router_attr_init();
ys_use_cors(attr, ys_cors_allow_all());
ys_router *router = ys_router_init(attr);
ys_router_register(router, "/", handler, YS_METHOD_GET);
ys_server *server = ys_server_init(router, PORT);
ys_server_start(server);
return EXIT_SUCCESS;
}
Auth with Cookies:
#include <pthread.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <uuid/uuid.h>
#include "deps/jsob/jsob.h"
#include "libhash/libhash.h"
#include "libys.h"
#define SESSION_TIMEOUT_MINUTES 10
static pthread_mutex_t session_mutex = PTHREAD_MUTEX_INITIALIZER;
static pthread_mutex_t user_mutex = PTHREAD_MUTEX_INITIALIZER;
hash_table *session_store;
hash_table *user_store;
char *COOKIE_ID = "DemoCookie";
const int PASSWORD_HASH_KEY = 7;
typedef struct {
char *username;
char *password;
} user;
bool eq(const char *s1, const char *s2) { return strcmp(s1, s2) == 0; }
char *create_sid(void) {
char uuid[UUID_STR_LEN];
uuid_t bin_uuid;
uuid_generate_random(bin_uuid);
uuid_unparse(bin_uuid, uuid);
return strdup(uuid);
}
user *create_user(char *username, char *password) {
user *u = malloc(sizeof(user));
u->username = username;
u->password = password;
return u;
}
bool user_exists(char *username) {
pthread_mutex_lock(&user_mutex);
bool ret = ht_search(user_store, username) != NULL;
pthread_mutex_unlock(&user_mutex);
return ret;
}
// This is for the demo ONLY! Do NOT use this for actual auth - it is NOT
// secure!!!
char *hash_password(char *pw) {
char ret[sizeof(pw) + 1];
int i = 0;
for (; i < strlen(pw); i++) {
ret[i] = (pw[i] ^ PASSWORD_HASH_KEY);
}
ret[i] = '\0';
return strdup(ret);
}
ys_response *index_handler(ys_request *req, ys_response *res) {
ys_set_status(res, YS_STATUS_OK);
ys_set_header(res, "Content-Type", YS_MIME_TYPE_HTML);
ys_set_body(res, ys_from_file("./index.html"));
return res;
}
ys_response *css_handler(ys_request *req, ys_response *res) {
ys_set_status(res, YS_STATUS_OK);
ys_set_header(res, "Content-Type", YS_MIME_TYPE_CSS);
ys_set_body(res, ys_from_file("./style.css"));
return res;
}
ys_response *data_handler(ys_request *req, ys_response *res) {
ys_cookie *c = ys_get_cookie(req, COOKIE_ID);
char *sid = ys_cookie_get_value(c);
pthread_mutex_lock(&session_mutex);
char *username = ht_get(session_store, sid);
pthread_mutex_unlock(&session_mutex);
ys_set_body(res, "{ \"data\": \"Hello, %s!\" }", username);
ys_set_header(res, "Content-Type", YS_MIME_TYPE_JSON);
ys_set_status(res, YS_STATUS_OK);
return res;
}
ys_response *register_handler(ys_request *req, ys_response *res) {
char *username = jsob_getstr(ys_req_get_body(req), "username");
if (!username) {
ys_set_status(res, YS_STATUS_BAD_REQUEST);
ys_set_body(res, "Must provide a username");
return res;
}
char *password = jsob_getstr(ys_req_get_body(req), "password");
if (!password) {
ys_set_status(res, YS_STATUS_BAD_REQUEST);
ys_set_body(res, "Must provide a password");
return res;
}
if (user_exists(username)) {
ys_set_status(res, YS_STATUS_BAD_REQUEST);
ys_set_body(res, "Username %s exists", username);
return res;
}
user *u = create_user(username, hash_password(password));
pthread_mutex_lock(&user_mutex);
ht_insert(user_store, username, u);
pthread_mutex_unlock(&user_mutex);
char *sid = create_sid();
pthread_mutex_lock(&session_mutex);
ht_insert(session_store, sid, username);
pthread_mutex_unlock(&session_mutex);
ys_cookie *c = ys_cookie_init(COOKIE_ID, sid);
ys_cookie_set_expires(c, ys_n_minutes_from_now(SESSION_TIMEOUT_MINUTES));
ys_set_cookie(res, c);
ys_set_status(res, YS_STATUS_CREATED);
return res;
}
ys_response *login_handler(ys_request *req, ys_response *res) {
char *username = jsob_getstr(ys_req_get_body(req), "username");
if (!username) {
ys_set_status(res, YS_STATUS_BAD_REQUEST);
ys_set_body(res, "Must provide a username");
return res;
}
char *password = jsob_getstr(ys_req_get_body(req), "password");
if (!password) {
ys_set_status(res, YS_STATUS_BAD_REQUEST);
ys_set_body(res, "Must provide a password");
return res;
}
if (!user_exists(username)) {
ys_set_status(res, YS_STATUS_BAD_REQUEST);
ys_set_body(res, "invalid credentials");
return res;
}
pthread_mutex_lock(&user_mutex);
user *u = (char *)ht_get(user_store, username);
pthread_mutex_unlock(&user_mutex);
if (!u || !s_equals(hash_password(u->password), password)) {
ys_set_status(res, YS_STATUS_BAD_REQUEST);
ys_set_body(res, "invalid credentials");
return res;
}
char *session_id = create_sid();
pthread_mutex_lock(&session_mutex);
ht_insert(session_store, session_id, username);
pthread_mutex_unlock(&session_mutex);
ys_cookie *c = ys_cookie_init(COOKIE_ID, session_id);
ys_cookie_set_expires(c, ys_n_minutes_from_now(SESSION_TIMEOUT_MINUTES));
ys_cookie_set_path(c, "/");
ys_set_cookie(res, c);
ys_set_status(res, YS_STATUS_OK);
return res;
}
ys_response *logout_handler(ys_request *req, ys_response *res) {
ys_cookie *c = ys_get_cookie(req, COOKIE_ID);
if (!c) {
ys_set_status(res, YS_STATUS_UNAUTHORIZED);
ys_set_done(res);
return res;
}
char *sid = ys_cookie_get_value(c);
if (!sid) {
ys_set_status(res, YS_STATUS_UNAUTHORIZED);
ys_set_done(res);
return res;
}
pthread_mutex_lock(&session_mutex);
ht_delete(session_store, sid);
pthread_mutex_unlock(&session_mutex);
ys_cookie_set_max_age(c, -1);
ys_set_cookie(res, c);
return res;
}
ys_response *auth_middleware(ys_request *req, ys_response *res) {
ys_set_header(res, "X-Authorized-By", "TheDemoApp");
ys_cookie *c = ys_get_cookie(req, COOKIE_ID);
if (!c) {
ys_set_status(res, YS_STATUS_UNAUTHORIZED);
ys_set_done(res);
return res;
}
char *sid = ys_cookie_get_value(c);
pthread_mutex_lock(&session_mutex);
char *username = ht_get(session_store, sid);
pthread_mutex_unlock(&session_mutex);
if (!username || !user_exists(username)) {
ys_set_status(res, YS_STATUS_UNAUTHORIZED);
ys_set_done(res);
return res;
}
ys_cookie_set_expires(c, ys_n_minutes_from_now(SESSION_TIMEOUT_MINUTES));
ys_set_cookie(res, c);
return res;
}
int main() {
session_store = ht_init(0);
user_store = ht_init(0);
ys_router_attr *attr = ys_router_attr_init();
ys_add_middleware_with_opts(attr, auth_middleware, "^/$", "^/style.css$",
"^/login$", "^/register$");
ys_router *router = ys_router_init(attr);
ys_router_register(router, "/register", register_handler, YS_METHOD_POST);
ys_router_register(router, "/login", login_handler, YS_METHOD_POST);
ys_router_register(router, "/logout", logout_handler, YS_METHOD_POST);
ys_router_register(router, "/data", data_handler, YS_METHOD_GET);
ys_router_register(router, "/", index_handler, YS_METHOD_GET);
ys_router_register(router, "/style.css", css_handler, YS_METHOD_GET);
ys_server *server = ys_server_init(ys_server_attr_init(router));
ys_server_start(server);
return EXIT_SUCCESS;
}