http-server.c

Basic HTTP server and router in C
git clone git://git.finwo.net/lib/http-server.c
Log | Files | Refs | README

http-server.c (6427B)


      1 #if defined(_WIN32) || defined(_WIN64)
      2 #include <windows.h>
      3 #else
      4 #include <time.h>
      5 #include <unistd.h>
      6 #endif
      7 
      8 #include <stdbool.h>
      9 #include <stdio.h>
     10 #include <stdlib.h>
     11 #include <string.h>
     12 
     13 #include "finwo/http-parser.h"
     14 #include "finwo/fnet.h"
     15 
     16 #include "http-server.h"
     17 
     18 static void sleep_ms(long ms) {
     19 #if defined(__APPLE__)
     20     usleep(ms * 1000);
     21 #elif defined(_WIN32) || defined(_WIN64)
     22     Sleep(ms);
     23 #else
     24     time_t sec = (int)(ms / 1000);
     25     const long t = ms -(sec * 1000);
     26     struct timespec req;
     27     req.tv_sec = sec;
     28     req.tv_nsec = t * 1000000L;
     29     while(-1 == nanosleep(&req, &req));
     30 #endif
     31 }
     32 
     33 struct fnet_udata {
     34   struct http_server_opts *opts;
     35   struct fnet_options_t   *fnet_opts;
     36 };
     37 
     38 struct hs_route {
     39   void *next;
     40   const char *method;
     41   char **path;
     42   void (*fn)(struct http_server_reqdata*);
     43 };
     44 
     45 struct hs_route *registered_routes = NULL;
     46 
     47 char ** _hs_pathTokens(const char *path) {
     48   char **output = calloc(strlen(path), sizeof(char*));
     49 
     50   int token_count = 0;
     51   char *dupped = strdup(path);
     52   char *token = strtok(dupped, "/");
     53   while(token != NULL) {
     54     output[token_count++] = strdup(token);
     55     token = strtok(NULL, "/");
     56   }
     57   free(dupped);
     58 
     59   return output;
     60 }
     61 
     62 void _hs_onServing(struct fnet_ev *ev) {
     63   struct fnet_udata *ludata = ev->udata;
     64 
     65   if (ludata->opts->evs && ludata->opts->evs->serving) {
     66     ludata->opts->evs->serving(ludata->opts->addr, ludata->opts->port, ludata->opts->udata);
     67   }
     68 }
     69 
     70 void _hs_onTick(struct fnet_ev *ev) {
     71   struct fnet_udata *ludata = ev->udata;
     72 
     73   if (ludata->opts->evs && ludata->opts->evs->tick) {
     74     ludata->opts->evs->tick(ludata->opts->udata);
     75   }
     76 }
     77 
     78 static void _hs_onRequest(struct http_parser_event *ev) {
     79   struct http_server_reqdata *reqdata = ev->udata;
     80   struct hs_route *route              = registered_routes;
     81   struct hs_route *selected_route     = NULL;
     82 
     83   // Tokenize the given path only once
     84   char **pathTokens = _hs_pathTokens(ev->request->path);
     85   char **routeTokens;
     86   char *meta = calloc(strlen(ev->request->path), sizeof(char));
     87   int i;
     88 
     89   // Method/path matching
     90   while(route) {
     91 
     92     // Skip route if the method doesn't match
     93     if (strcmp(ev->request->method, route->method)) {
     94       route = route->next;
     95       continue; // Continues while(route)
     96     }
     97 
     98     // Checking if the path matches
     99     routeTokens = route->path;
    100     i = 0;
    101 
    102     while((pathTokens[i] && routeTokens[i])) {
    103       if (routeTokens[i][0] == ':') { i++; continue; }
    104       if (strcmp(pathTokens[i], routeTokens[i])) {
    105         i = -1;
    106         break; // Breaks token-checking
    107       }
    108       i++;
    109     }
    110 
    111     // Content mismatch
    112     if (i == -1) {
    113       route = route->next;
    114       continue; // Continues while(route)
    115     }
    116 
    117     // Length mismatch
    118     if (pathTokens[i] || routeTokens[i]) {
    119       route = route->next;
    120       continue;
    121     }
    122 
    123     // Here = route match
    124 
    125     // Store url params as meta 'param:<name>' = 'path[i]
    126     i = 0;
    127     while((pathTokens[i] && routeTokens[i])) {
    128       if (routeTokens[i][0] != ':') { i++; continue; }
    129       meta[0] = '\0';
    130       strcat(meta, "param:");
    131       strcat(meta, routeTokens[i]+1);
    132       http_parser_meta_set(ev->request, meta, pathTokens[i]);
    133       i++;
    134     }
    135 
    136     selected_route = route;
    137     break;
    138   }
    139 
    140   if (!selected_route) {
    141     if (reqdata->evs && reqdata->evs->notFound) {
    142       reqdata->evs->notFound(reqdata);
    143       return;
    144     } else {
    145       fnet_close(reqdata->connection);
    146       return;
    147     }
    148   }
    149 
    150   // Call the route handler
    151   selected_route->fn(reqdata);
    152 }
    153 
    154 void _hs_onData(struct fnet_ev *ev) {
    155   struct http_server_reqdata *reqdata = ev->udata;
    156   http_parser_pair_request_data(reqdata->reqres, ev->buffer);
    157 }
    158 
    159 void _hs_onClose(struct fnet_ev *ev) {
    160   struct http_server_reqdata *reqdata = ev->udata;
    161 
    162   if (reqdata->evs && reqdata->evs->close) {
    163     reqdata->evs->close(reqdata);
    164   }
    165 
    166   http_parser_pair_free(reqdata->reqres);
    167   free(reqdata);
    168 }
    169 
    170 void _hs_onConnect(struct fnet_ev *ev) {
    171   struct fnet_t     *conn   = ev->connection;
    172   struct fnet_udata *ludata = ev->udata;
    173 
    174   // Setup new request/response pair
    175   struct http_server_reqdata *reqdata = malloc(sizeof(struct http_server_reqdata));
    176   reqdata->connection        = conn;
    177   reqdata->reqres            = http_parser_pair_init(reqdata);
    178   reqdata->reqres->onRequest = _hs_onRequest;
    179   reqdata->evs               = ludata->opts->evs;
    180   reqdata->udata             = ludata->opts->udata;
    181   ev->connection->udata      = reqdata;
    182 
    183   // Setup data flowing from connection into reqres
    184   ev->connection->onData  = _hs_onData;
    185   ev->connection->onClose = _hs_onClose;
    186 }
    187 
    188 void http_server_response_send(struct http_server_reqdata *reqdata, bool close) {
    189   struct buf *response_buffer = http_parser_sprint_response(reqdata->reqres->response);
    190   fnet_write(reqdata->connection, response_buffer);
    191   buf_clear(response_buffer);
    192   free(response_buffer);
    193   if (close) {
    194     fnet_close(reqdata->connection);
    195   }
    196 }
    197 
    198 void http_server_route(const char *method, const char *path, void (*fn)(struct http_server_reqdata*)) {
    199   struct hs_route *route = malloc(sizeof(struct hs_route));
    200   route->next   = registered_routes;
    201   route->method = method;
    202   route->fn     = fn;
    203   route->path   = _hs_pathTokens(path);
    204   registered_routes = route;
    205 }
    206 
    207 void _hs_onListenClose(struct fnet_ev *ev) {
    208   struct fnet_udata *ludata = ev->udata;
    209   if (!ludata->opts->shutdown) {
    210     ludata->opts->listen_connection = fnet_listen(ludata->opts->addr, ludata->opts->port, ludata->fnet_opts);
    211   }
    212 }
    213 
    214 void http_server_main(struct http_server_opts *opts) {
    215   int ret;
    216   if (!opts) exit(1);
    217   opts->shutdown = false;
    218 
    219   // Prepare http context
    220   struct fnet_udata *ludata = calloc(1, sizeof(struct fnet_udata));
    221   ludata->opts = opts;
    222 
    223   // Prepare network options
    224   struct fnet_options_t fnet_opts = {
    225     .proto     = FNET_PROTO_TCP,
    226     .flags     = 0,
    227     .onListen  = _hs_onServing,
    228     .onConnect = _hs_onConnect,
    229     .onData    = NULL,
    230     .onTick    = _hs_onTick,
    231     .onClose   = _hs_onListenClose,
    232     .udata     = ludata,
    233   };
    234 
    235   // Track network options in http context
    236   ludata->fnet_opts   = &fnet_opts;
    237 
    238   // Signal that we want our port
    239   ludata->opts->listen_connection = fnet_listen(ludata->opts->addr, ludata->opts->port, ludata->fnet_opts);
    240   if (!(ludata->opts->listen_connection)) {
    241     exit(1);
    242   }
    243 
    244   // This is a forever function, controlled by network thread
    245   while(!opts->shutdown) {
    246     ret = fnet_main();
    247     if (ret) exit(ret);
    248     sleep_ms(100);
    249   }
    250 }