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