bunkum

an old and silly c99 web server with some fun features
Log | Files | Refs | README

http.c (8760B)


      1 #define _GNU_SOURCE
      2 
      3 #include <stdlib.h>
      4 #include <stdio.h>
      5 #include <fcntl.h>
      6 #include <stdbool.h>
      7 #include <unistd.h>
      8 #include <string.h>
      9 #include <assert.h>
     10 #include <dirent.h>
     11 #include <time.h>
     12 #include <signal.h>
     13 #include <errno.h>
     14 
     15 #include <sys/stat.h>
     16 #include <sys/socket.h>
     17 #include <sys/types.h>
     18 #include <sys/ptrace.h>
     19 #include <sys/fcntl.h>
     20 #include <netinet/in.h>
     21 #include <arpa/inet.h>
     22 
     23 #include "utils/log.h"
     24 #include "utils/time.h"
     25 #include "utils/vec.h"
     26 #include "utils/sync.h"
     27 #include "utils/html.h"
     28 #include "utils/compress.h"
     29 #include "utils/profile.h"
     30 
     31 #include "http/response.h"
     32 #include "http/request.h"
     33 
     34 
     35 response_t serve_error(enum StatusCode c) {
     36     response_t r = resp_new(c);
     37     resp_add_hdr(&r, "Content-Type", "text/html");
     38 
     39     char errt[256];
     40     sprintf(errt, "Error %d", c);
     41 
     42     html_t html = html_new();
     43     html_body_add(&html.body, html_h1_new(errt));
     44     html_body_add(&html.body, html_p_new("Hey! Don't do that."));
     45     char* msg = html_render(&html);
     46 
     47     resp_add_content(&r, msg, strlen(msg));
     48     return r;
     49 }
     50 
     51 vec_t paths;
     52 hashmap_t path_redirs;
     53 
     54 char* get_file_ext(char* filename) {
     55     char* ext = strtok(filename, ".");
     56     char* next;
     57     while ((next = strtok(NULL, "."))) ext = next;
     58     return ext;
     59 }
     60 
     61 response_t serve_file(request_t* req) {
     62     char path[128+8] = "./public";
     63     strcat(path, req->path);
     64     int fd = open(path, O_RDONLY); // TODO handle
     65     response_t r = resp_new(OK);
     66 
     67     struct stat st;
     68     fstat(fd, &st);
     69     char* fbuf = malloc(st.st_size); // TODO what if file larger than memory?
     70     for (size_t i = 0; read(fd, fbuf+(i*4096), 4096) > 0; i++);
     71 
     72 
     73     char* ext = get_file_ext(req->path);
     74 
     75     resp_set_ctype(&r, ext);
     76 
     77     bool ok = true;
     78     char* mtype = (char*)ext_to_mtype(ext);
     79     char* hdr;
     80     if ((hdr = hashmap_get(&req->headers, "Accept"))) {
     81         ok = false;
     82         vec_t mtypes = hdr_parse_accept(hdr);
     83         for (int j = 0; j < mtypes.vec_sz; j++) {
     84             struct req_mimetype* a_mtype = vec_get(&mtypes, j);
     85             // TODO doesn't handle stuff like image/* (is that even allowed?)
     86             if (strcmp(a_mtype->item, mtype) == 0 || strcmp(a_mtype->item, "*/*") == 0) {
     87                 ok = true;
     88                 break;
     89             }
     90         }
     91     }
     92 
     93     char* buf = fbuf; // buffer to be written
     94     size_t bufsize = st.st_size;
     95 
     96     if (ok && (hdr = hashmap_get(&req->headers, "Accept-Encoding"))) {
     97         ok = false;
     98         vec_t mtypes = hdr_parse_accept(hdr); // abuse of this func
     99         for (int j = 0; j < mtypes.vec_sz; j++) {
    100             struct req_mimetype* a_mtype = vec_get(&mtypes, j);
    101             if (strcmp(a_mtype->item, "gzip") == 0) {
    102                 resp_add_hdr(&r, "Content-Encoding", "gzip");
    103                 buf = gzip_compress(buf, &bufsize);
    104                 ok = true;
    105                 break;
    106             } else if (strcmp(a_mtype->item, "deflate") == 0) {
    107                 resp_add_hdr(&r, "Content-Encoding", "deflate");
    108                 buf = zlib_compress(buf, &bufsize);
    109                 ok = true;
    110                 break;
    111             } else if (strcmp(a_mtype->item, "identity") == 0) {
    112                 ok = true;
    113                 break;
    114             }
    115         }
    116     }
    117     /* if (!ok) return serve_error(NotAcceptable); */
    118 
    119     char datebuf[64];
    120     time_to_str(st.st_mtim.tv_sec, datebuf);
    121     resp_add_hdr(&r, "Last-Modified", datebuf);
    122 
    123     resp_add_content(&r, buf, bufsize);
    124     free(buf);
    125     return r;
    126 }
    127 
    128 // TODO actually respect requests
    129 // TODO list dirs
    130 
    131 response_t make_response (request_t* req, int pfd) {
    132     if (req_parse(req) < 0) {
    133         log_error("Failed to parse incoming request.");
    134         return serve_error(BadRequest);
    135     }
    136 
    137     if (hashmap_get(&req->headers, "Profile") != 0x0) {
    138         int profile = true;
    139         if (ptrace(PTRACE_TRACEME, NULL) < 0) {
    140             log_error("PTRACE_TRACME failed with err %d", errno);
    141         }
    142         if (write(pfd, "profile", 8) != 8) {
    143             log_error("write err");
    144         }
    145         usleep(100);
    146     }
    147 
    148     log_info("Got request %s %s", method_name(req->method), req->path);
    149 
    150     char* mapped_path = hashmap_get(&path_redirs, req->path);
    151     if (mapped_path != NULL) {
    152         strcpy(req->path, mapped_path);
    153     }
    154 
    155     if (vec_check(&paths, req->path, (sv_cmp_t)strcmp) == -1) {
    156         return serve_error(NotFound);
    157     }
    158 
    159     log_debug("method = %d", req->method);
    160     switch (req->method) {
    161     case GET: return serve_file(req);
    162     default: return serve_error(MethodNotAllowed);
    163     }
    164 }
    165 
    166 
    167 double diff_timespec(const struct timespec *time1, const struct timespec *time0) {
    168     return (time1->tv_sec - time0->tv_sec)
    169         + (time1->tv_nsec - time0->tv_nsec) / 1000000000.0;
    170 }
    171 
    172 void* handle_conn(int ns, int pfd) {
    173     char msgbuf[1024] = {0};
    174     while(true) {
    175         struct timespec before, after, tdiff;
    176         if (recv(ns, msgbuf, 1024, 0) == 0) {
    177             log_info("Closing connection.");
    178             return 0x0;
    179         }
    180         clock_gettime(CLOCK_MONOTONIC, &before);
    181         if (msgbuf[0] != 0) {
    182             request_t req = req_new(msgbuf, 1024);
    183 
    184             response_t r = make_response(&req, pfd);
    185             send(ns, r.content, r.sz, 0);
    186             free(r.content);
    187             write(pfd, "stop", 5);
    188             clock_gettime(CLOCK_MONOTONIC, &after);
    189             log_info("Handled request in %f sec", diff_timespec(&after, &before));
    190             req_free(&req);
    191         }
    192     }
    193 }
    194 
    195 int list_files_sv(vec_t* sv, char* base) {
    196     char path[MAX_PATH_LEN] = "/";
    197     struct dirent* dp;
    198     DIR* dir = opendir(base);
    199     if (!dir) return -1;
    200 
    201     while ((dp = readdir(dir)) != NULL) {
    202         if (strcmp(dp->d_name, ".") != 0 && strcmp(dp->d_name, "..") != 0) {
    203             strcpy(path+1, base);
    204             strcat(path, "/");
    205             strcat(path, dp->d_name);
    206             vec_push(sv, strchr(path+1, '/'));
    207             list_files_sv(sv, path+1);
    208         }
    209     }
    210 
    211     closedir(dir);
    212     return 0;
    213 }
    214 
    215 channel_t listen_chan;
    216 
    217 void* listen_for_conns(void* ctxt) {
    218     int s = *(int*)ctxt;
    219     int namelen;
    220     struct sockaddr_in client;
    221     while (true) {
    222         listen(s, 1);
    223         int ns = accept(s, (struct sockaddr*)&client, (socklen_t*)&namelen);
    224         log_info("Handling connection from %s", inet_ntoa(client.sin_addr));
    225         channel_push(&listen_chan, &ns);
    226     }
    227 }
    228 
    229 struct conn_ctxt {
    230     pid_t pid;
    231     int fd;
    232 };
    233 
    234 int main() {
    235     int s = socket(AF_INET, SOCK_STREAM, 0);
    236     if (s == -1) die("socket");
    237 
    238     int portnum = 8080;
    239     struct sockaddr_in name;
    240     name.sin_family = AF_INET;
    241     name.sin_port = htons(portnum);
    242     name.sin_addr.s_addr = htonl(INADDR_ANY);
    243 
    244     while(bind(s, (struct sockaddr*)&name, sizeof(name)) < 0) {
    245         portnum += 1;
    246         name.sin_port = htons(portnum);
    247     }
    248 
    249     paths = vec_new(MAX_PATH_LEN);
    250     path_redirs = hashmap_new(MAX_PATH_LEN, MAX_PATH_LEN);
    251     path_redirs.vark = true;
    252     hashmap_set(&path_redirs, "/", "/index.html");
    253 
    254     list_files_sv(&paths, "public");
    255 
    256     int nlen;
    257     getsockname(s, (struct sockaddr*)&name, (socklen_t*)&nlen);
    258     log_debug("Open on port %d.", htons(name.sin_port));
    259 
    260     listen_chan = channel_new(sizeof(int));
    261 
    262     pthread_t lthread;
    263     pthread_create(&lthread, NULL, listen_for_conns, &s);
    264 
    265     int namelen;
    266     struct sockaddr_in client;
    267 
    268     vec_t conns = vec_new(sizeof(struct conn_ctxt));
    269     while (true) {
    270         int* ptr = channel_try_recv(&listen_chan);
    271         if (ptr != NULL) {
    272             int pfds[2];
    273             pipe(pfds);
    274             int retval = fcntl(pfds[0], F_SETFL, fcntl(pfds[0], F_GETFL) | O_NONBLOCK);
    275             pid_t pid = fork();
    276             if (pid == 0) {
    277                 handle_conn(*ptr, pfds[1]);
    278                 close(pfds[0]);
    279                 close(pfds[1]);
    280                 exit(0);
    281             }
    282             struct conn_ctxt ctxt = {.fd = pfds[0], .pid = pid };
    283             vec_push(&conns, &ctxt);
    284         }
    285         for (int i = 0; i < conns.vec_sz; i++) {
    286             struct conn_ctxt* ctxt = vec_get(&conns, i);
    287             char msg[8] = {0};
    288             if (read(ctxt->fd, &msg, 8) != 8 || strcmp(msg, "profile") != 0) continue;
    289             struct profile_node prof_res = profile_res_new();
    290             while(true) {
    291                 vec_t stack = profile(ctxt->pid);
    292                 profile_proc_stack(&prof_res, &stack);
    293                 usleep(3);
    294                 if (read(ctxt->fd, &msg, 5) == 5 && strcmp(msg, "stop") == 0) break;
    295             }
    296             profile_dump(&prof_res, -1);
    297         }
    298     }
    299 }
    300 
    301 // meta todos:
    302 // - TODO true dependencyless (no zlib, no pthread)
    303 // - TODO HTML parsing maybe but that makes me want to cry
    304 // - TODO nice ways of dynamically *generating* html
    305 // - TODO dynamically profile requests
    306 // - TODO some kinda config file parsing