Mercurial > urweb
comparison src/c/driver.c @ 853:19fdeef40ada
Factor out common request functionality, in preparation for supporting different protocols
author | Adam Chlipala <adamc@hcoop.net> |
---|---|
date | Tue, 23 Jun 2009 14:05:12 -0400 |
parents | 5f49a6b759cb |
children | 158d980889ac |
comparison
equal
deleted
inserted
replaced
852:4d4c62d95b9c | 853:19fdeef40ada |
---|---|
12 #include <pthread.h> | 12 #include <pthread.h> |
13 | 13 |
14 #include <mhash.h> | 14 #include <mhash.h> |
15 | 15 |
16 #include "urweb.h" | 16 #include "urweb.h" |
17 #include "request.h" | |
17 | 18 |
18 int uw_backlog = 10; | 19 int uw_backlog = 10; |
19 int uw_bufsize = 1024; | |
20 | 20 |
21 typedef struct node { | 21 typedef struct node { |
22 int fd; | 22 int fd; |
23 struct node *next; | 23 struct node *next; |
24 } *node; | 24 } *node; |
52 } | 52 } |
53 | 53 |
54 static pthread_mutex_t queue_mutex = PTHREAD_MUTEX_INITIALIZER; | 54 static pthread_mutex_t queue_mutex = PTHREAD_MUTEX_INITIALIZER; |
55 static pthread_cond_t queue_cond = PTHREAD_COND_INITIALIZER; | 55 static pthread_cond_t queue_cond = PTHREAD_COND_INITIALIZER; |
56 | 56 |
57 #define MAX_RETRIES 5 | |
58 | |
59 static int try_rollback(uw_context ctx) { | |
60 int r = uw_rollback(ctx); | |
61 | |
62 if (r) { | |
63 printf("Error running SQL ROLLBACK\n"); | |
64 uw_reset(ctx); | |
65 uw_write(ctx, "HTTP/1.1 500 Internal Server Error\n\r"); | |
66 uw_write(ctx, "Content-type: text/plain\r\n\r\n"); | |
67 uw_write(ctx, "Error running SQL ROLLBACK\n"); | |
68 } | |
69 | |
70 return r; | |
71 } | |
72 | |
73 static uw_context new_context() { | |
74 uw_context ctx = uw_init(); | |
75 int retries_left = MAX_RETRIES; | |
76 | |
77 while (1) { | |
78 failure_kind fk = uw_begin_init(ctx); | |
79 | |
80 if (fk == SUCCESS) { | |
81 printf("Database connection initialized.\n"); | |
82 break; | |
83 } else if (fk == BOUNDED_RETRY) { | |
84 if (retries_left) { | |
85 printf("Initialization error triggers bounded retry: %s\n", uw_error_message(ctx)); | |
86 --retries_left; | |
87 } else { | |
88 printf("Fatal initialization error (out of retries): %s\n", uw_error_message(ctx)); | |
89 uw_free(ctx); | |
90 return NULL; | |
91 } | |
92 } else if (fk == UNLIMITED_RETRY) | |
93 printf("Initialization error triggers unlimited retry: %s\n", uw_error_message(ctx)); | |
94 else if (fk == FATAL) { | |
95 printf("Fatal initialization error: %s\n", uw_error_message(ctx)); | |
96 uw_free(ctx); | |
97 return NULL; | |
98 } else { | |
99 printf("Unknown uw_begin_init return code!\n"); | |
100 uw_free(ctx); | |
101 return NULL; | |
102 } | |
103 } | |
104 | |
105 return ctx; | |
106 } | |
107 | |
108 #define KEYSIZE 16 | |
109 #define PASSSIZE 4 | |
110 | |
111 #define HASH_ALGORITHM MHASH_SHA256 | |
112 #define HASH_BLOCKSIZE 32 | |
113 #define KEYGEN_ALGORITHM KEYGEN_MCRYPT | |
114 | |
115 int uw_hash_blocksize = HASH_BLOCKSIZE; | |
116 | |
117 static int password[PASSSIZE]; | |
118 static unsigned char private_key[KEYSIZE]; | |
119 | |
120 static void init_crypto() { | |
121 KEYGEN kg = {{HASH_ALGORITHM, HASH_ALGORITHM}}; | |
122 int i; | |
123 | |
124 assert(mhash_get_block_size(HASH_ALGORITHM) == HASH_BLOCKSIZE); | |
125 | |
126 for (i = 0; i < PASSSIZE; ++i) | |
127 password[i] = rand(); | |
128 | |
129 if (mhash_keygen_ext(KEYGEN_ALGORITHM, kg, | |
130 private_key, sizeof(private_key), | |
131 (unsigned char*)password, sizeof(password)) < 0) { | |
132 printf("Key generation failed\n"); | |
133 exit(1); | |
134 } | |
135 } | |
136 | |
137 void uw_sign(const char *in, char *out) { | |
138 MHASH td; | |
139 | |
140 td = mhash_hmac_init(HASH_ALGORITHM, private_key, sizeof(private_key), | |
141 mhash_get_hash_pblock(HASH_ALGORITHM)); | |
142 | |
143 mhash(td, in, strlen(in)); | |
144 if (mhash_hmac_deinit(td, out) < 0) | |
145 printf("Signing failed"); | |
146 } | |
147 | |
148 static void *worker(void *data) { | 57 static void *worker(void *data) { |
149 int me = *(int *)data, retries_left = MAX_RETRIES; | 58 int me = *(int *)data; |
150 uw_context ctx = new_context(); | 59 uw_context ctx = uw_request_new_context(); |
151 size_t buf_size = 2; | 60 size_t buf_size = 2; |
152 char *buf = malloc(buf_size); | 61 char *buf = malloc(buf_size); |
153 size_t path_copy_size = 0; | 62 uw_request_context rc = uw_new_request_context(); |
154 char *path_copy = malloc(path_copy_size); | |
155 | 63 |
156 while (1) { | 64 while (1) { |
157 char *back = buf, *s, *post; | 65 char *back = buf; |
158 int sock, dont_close = 0; | 66 int sock; |
159 | 67 |
160 pthread_mutex_lock(&queue_mutex); | 68 pthread_mutex_lock(&queue_mutex); |
161 while (empty()) | 69 while (empty()) |
162 pthread_cond_wait(&queue_cond, &queue_mutex); | 70 pthread_cond_wait(&queue_cond, &queue_mutex); |
163 sock = dequeue(); | 71 sock = dequeue(); |
164 pthread_mutex_unlock(&queue_mutex); | 72 pthread_mutex_unlock(&queue_mutex); |
165 | 73 |
166 printf("Handling connection with thread #%d.\n", me); | 74 printf("Handling connection with thread #%d.\n", me); |
167 | 75 |
168 while (1) { | 76 while (1) { |
169 unsigned retries_left = MAX_RETRIES; | |
170 int r; | 77 int r; |
78 char *s1, *s2; | |
171 | 79 |
172 if (back - buf == buf_size - 1) { | 80 if (back - buf == buf_size - 1) { |
173 char *new_buf; | 81 char *new_buf; |
174 buf_size *= 2; | 82 buf_size *= 2; |
175 new_buf = realloc(buf, buf_size); | 83 new_buf = realloc(buf, buf_size); |
187 if (r == 0) { | 95 if (r == 0) { |
188 printf("Connection closed.\n"); | 96 printf("Connection closed.\n"); |
189 break; | 97 break; |
190 } | 98 } |
191 | 99 |
192 //printf("Received %d bytes.\n", r); | |
193 | |
194 back += r; | 100 back += r; |
195 *back = 0; | 101 *back = 0; |
196 | 102 |
197 if (s = strstr(buf, "\r\n\r\n")) { | 103 if ((s1 = strstr(buf, "\r\n\r\n"))) { |
198 failure_kind fk; | 104 request_result rr; |
199 int is_post = 0, do_normal_send = 1; | 105 |
200 char *boundary = NULL; | 106 if ((s2 = strcasestr(buf, "\r\nContent-Length: ")) && s2 < s1) { |
201 size_t boundary_len; | 107 int clen; |
202 char *cmd, *path, *headers, *inputs, *after_headers; | 108 |
203 | 109 if (sscanf(s2 + 18, "%d\r\n", &clen) != 1) { |
204 //printf("All: %s\n", buf); | 110 fprintf(stderr, "Malformed Content-Length header\n"); |
205 | |
206 s[2] = 0; | |
207 after_headers = s + 4; | |
208 | |
209 if (!(s = strstr(buf, "\r\n"))) { | |
210 fprintf(stderr, "No newline in buf\n"); | |
211 break; | |
212 } | |
213 | |
214 *s = 0; | |
215 headers = s + 2; | |
216 cmd = s = buf; | |
217 | |
218 //printf("Read: %s\n", buf); | |
219 | |
220 if (!strsep(&s, " ")) { | |
221 fprintf(stderr, "No first space in HTTP command\n"); | |
222 break; | |
223 } | |
224 | |
225 uw_set_headers(ctx, headers); | |
226 | |
227 if (!strcmp(cmd, "POST")) { | |
228 char *clen_s = uw_Basis_requestHeader(ctx, "Content-length"); | |
229 if (!clen_s) { | |
230 fprintf(stderr, "No Content-length with POST\n"); | |
231 goto done; | |
232 } | |
233 int clen = atoi(clen_s); | |
234 if (clen < 0) { | |
235 fprintf(stderr, "Negative Content-length with POST\n"); | |
236 goto done; | |
237 } | |
238 | |
239 while (back - after_headers < clen) { | |
240 if (back - buf == buf_size - 1) { | |
241 char *new_buf; | |
242 buf_size *= 2; | |
243 new_buf = realloc(buf, buf_size); | |
244 | |
245 back = new_buf + (back - buf); | |
246 headers = new_buf + (headers - buf); | |
247 uw_headers_moved(ctx, headers); | |
248 after_headers = new_buf + (after_headers - buf); | |
249 s = new_buf + (s - buf); | |
250 | |
251 buf = new_buf; | |
252 } | |
253 | |
254 r = recv(sock, back, buf_size - 1 - (back - buf), 0); | |
255 | |
256 if (r < 0) { | |
257 fprintf(stderr, "Recv failed\n"); | |
258 goto done; | |
259 } | |
260 | |
261 if (r == 0) { | |
262 printf("Connection closed.\n"); | |
263 goto done; | |
264 } | |
265 | |
266 back += r; | |
267 *back = 0; | |
268 } | |
269 | |
270 is_post = 1; | |
271 | |
272 clen_s = uw_Basis_requestHeader(ctx, "Content-type"); | |
273 if (clen_s && !strncasecmp(clen_s, "multipart/form-data", 19)) { | |
274 if (strncasecmp(clen_s + 19, "; boundary=", 11)) { | |
275 fprintf(stderr, "Bad multipart boundary spec"); | |
276 break; | |
277 } | |
278 | |
279 boundary = clen_s + 28; | |
280 boundary[0] = '-'; | |
281 boundary[1] = '-'; | |
282 boundary_len = strlen(boundary); | |
283 } | |
284 } else if (strcmp(cmd, "GET")) { | |
285 fprintf(stderr, "Not ready for non-GET/POST command: %s\n", cmd); | |
286 break; | |
287 } | |
288 | |
289 path = s; | |
290 if (!strsep(&s, " ")) { | |
291 fprintf(stderr, "No second space in HTTP command\n"); | |
292 break; | |
293 } | |
294 | |
295 if (!strcmp(path, "/.msgs")) { | |
296 char *id = uw_Basis_requestHeader(ctx, "UrWeb-Client"); | |
297 char *pass = uw_Basis_requestHeader(ctx, "UrWeb-Pass"); | |
298 | |
299 if (id && pass) { | |
300 unsigned idn = atoi(id); | |
301 uw_client_connect(idn, atoi(pass), sock); | |
302 dont_close = 1; | |
303 fprintf(stderr, "Processed request for messages by client %u\n\n", idn); | |
304 } | |
305 else { | |
306 fprintf(stderr, "Missing fields in .msgs request: %s, %s\n\n", id, pass); | |
307 } | |
308 break; | |
309 } | |
310 | |
311 if (boundary) { | |
312 char *part = after_headers, *after_sub_headers, *header, *after_header; | |
313 size_t part_len; | |
314 | |
315 part = strstr(part, boundary); | |
316 if (!part) { | |
317 fprintf(stderr, "Missing first multipart boundary\n"); | |
318 break; | 111 break; |
319 } | 112 } |
320 part += boundary_len; | 113 |
321 | 114 if (s1 + 4 + clen > back) |
322 while (1) { | 115 continue; |
323 char *name = NULL, *filename = NULL, *type = NULL; | |
324 | |
325 if (part[0] == '-' && part[1] == '-') | |
326 break; | |
327 | |
328 if (*part != '\r') { | |
329 fprintf(stderr, "No \\r after multipart boundary\n"); | |
330 goto done; | |
331 } | |
332 ++part; | |
333 if (*part != '\n') { | |
334 fprintf(stderr, "No \\n after multipart boundary\n"); | |
335 goto done; | |
336 } | |
337 ++part; | |
338 | |
339 if (!(after_sub_headers = strstr(part, "\r\n\r\n"))) { | |
340 fprintf(stderr, "Missing end of headers after multipart boundary\n"); | |
341 goto done; | |
342 } | |
343 after_sub_headers[2] = 0; | |
344 after_sub_headers += 4; | |
345 | |
346 for (header = part; after_header = strstr(header, "\r\n"); header = after_header + 2) { | |
347 char *colon, *after_colon; | |
348 | |
349 *after_header = 0; | |
350 if (!(colon = strchr(header, ':'))) { | |
351 fprintf(stderr, "Missing colon in multipart sub-header\n"); | |
352 goto done; | |
353 } | |
354 *colon++ = 0; | |
355 if (*colon++ != ' ') { | |
356 fprintf(stderr, "No space after colon in multipart sub-header\n"); | |
357 goto done; | |
358 } | |
359 | |
360 if (!strcasecmp(header, "Content-Disposition")) { | |
361 if (strncmp(colon, "form-data; ", 11)) { | |
362 fprintf(stderr, "Multipart data is not \"form-data\"\n"); | |
363 goto done; | |
364 } | |
365 | |
366 for (colon += 11; after_colon = strchr(colon, '='); colon = after_colon) { | |
367 char *data; | |
368 after_colon[0] = 0; | |
369 if (after_colon[1] != '"') { | |
370 fprintf(stderr, "Disposition setting is missing initial quote\n"); | |
371 goto done; | |
372 } | |
373 data = after_colon+2; | |
374 if (!(after_colon = strchr(data, '"'))) { | |
375 fprintf(stderr, "Disposition setting is missing final quote\n"); | |
376 goto done; | |
377 } | |
378 after_colon[0] = 0; | |
379 ++after_colon; | |
380 if (after_colon[0] == ';' && after_colon[1] == ' ') | |
381 after_colon += 2; | |
382 | |
383 if (!strcasecmp(colon, "name")) | |
384 name = data; | |
385 else if (!strcasecmp(colon, "filename")) | |
386 filename = data; | |
387 } | |
388 } else if (!strcasecmp(header, "Content-Type")) { | |
389 type = colon; | |
390 } | |
391 } | |
392 | |
393 part = memmem(after_sub_headers, back - after_sub_headers, boundary, boundary_len); | |
394 if (!part) { | |
395 fprintf(stderr, "Missing boundary after multipart payload\n"); | |
396 goto done; | |
397 } | |
398 part[-2] = 0; | |
399 part_len = part - after_sub_headers - 2; | |
400 part[0] = 0; | |
401 part += boundary_len; | |
402 | |
403 if (filename) { | |
404 uw_Basis_file f = {filename, type, {part_len, after_sub_headers}}; | |
405 | |
406 if (uw_set_file_input(ctx, name, f)) { | |
407 puts(uw_error_message(ctx)); | |
408 goto done; | |
409 } | |
410 } else if (uw_set_input(ctx, name, after_sub_headers)) { | |
411 puts(uw_error_message(ctx)); | |
412 goto done; | |
413 } | |
414 } | |
415 } | 116 } |
416 else { | 117 |
417 if (is_post) | 118 rr = uw_request(rc, ctx, buf, back - buf, sock); |
418 inputs = after_headers; | |
419 else if (inputs = strchr(path, '?')) | |
420 *inputs++ = 0; | |
421 | |
422 if (inputs) { | |
423 char *name, *value; | |
424 | |
425 while (*inputs) { | |
426 name = inputs; | |
427 if (inputs = strchr(inputs, '&')) | |
428 *inputs++ = 0; | |
429 else | |
430 inputs = strchr(name, 0); | |
431 | |
432 if (value = strchr(name, '=')) { | |
433 *value++ = 0; | |
434 if (uw_set_input(ctx, name, value)) { | |
435 puts(uw_error_message(ctx)); | |
436 goto done; | |
437 } | |
438 } | |
439 else if (uw_set_input(ctx, name, "")) { | |
440 puts(uw_error_message(ctx)); | |
441 goto done; | |
442 } | |
443 } | |
444 } | |
445 } | |
446 | |
447 printf("Serving URI %s....\n", path); | |
448 | |
449 while (1) { | |
450 size_t path_len = strlen(path); | |
451 | |
452 uw_write_header(ctx, "HTTP/1.1 200 OK\r\n"); | |
453 | |
454 if (path_len + 1 > path_copy_size) { | |
455 path_copy_size = path_len + 1; | |
456 path_copy = realloc(path_copy, path_copy_size); | |
457 } | |
458 strcpy(path_copy, path); | |
459 fk = uw_begin(ctx, path_copy); | |
460 if (fk == SUCCESS || fk == RETURN_BLOB) { | |
461 uw_commit(ctx); | |
462 break; | |
463 } else if (fk == BOUNDED_RETRY) { | |
464 if (retries_left) { | |
465 printf("Error triggers bounded retry: %s\n", uw_error_message(ctx)); | |
466 --retries_left; | |
467 } | |
468 else { | |
469 printf("Fatal error (out of retries): %s\n", uw_error_message(ctx)); | |
470 | |
471 try_rollback(ctx); | |
472 | |
473 uw_reset_keep_error_message(ctx); | |
474 uw_write_header(ctx, "HTTP/1.1 500 Internal Server Error\n\r"); | |
475 uw_write_header(ctx, "Content-type: text/plain\r\n"); | |
476 uw_write(ctx, "Fatal error (out of retries): "); | |
477 uw_write(ctx, uw_error_message(ctx)); | |
478 uw_write(ctx, "\n"); | |
479 | |
480 break; | |
481 } | |
482 } else if (fk == UNLIMITED_RETRY) | |
483 printf("Error triggers unlimited retry: %s\n", uw_error_message(ctx)); | |
484 else if (fk == FATAL) { | |
485 printf("Fatal error: %s\n", uw_error_message(ctx)); | |
486 | |
487 try_rollback(ctx); | |
488 | |
489 uw_reset_keep_error_message(ctx); | |
490 uw_write_header(ctx, "HTTP/1.1 500 Internal Server Error\r\n"); | |
491 uw_write_header(ctx, "Content-type: text/html\r\n"); | |
492 uw_write(ctx, "<html><head><title>Fatal Error</title></head><body>"); | |
493 uw_write(ctx, "Fatal error: "); | |
494 uw_write(ctx, uw_error_message(ctx)); | |
495 uw_write(ctx, "\n</body></html>"); | |
496 | |
497 break; | |
498 } else { | |
499 printf("Unknown uw_handle return code!\n"); | |
500 | |
501 try_rollback(ctx); | |
502 | |
503 uw_reset_keep_request(ctx); | |
504 uw_write_header(ctx, "HTTP/1.1 500 Internal Server Error\n\r"); | |
505 uw_write_header(ctx, "Content-type: text/plain\r\n"); | |
506 uw_write(ctx, "Unknown uw_handle return code!\n"); | |
507 | |
508 break; | |
509 } | |
510 | |
511 if (try_rollback(ctx)) | |
512 break; | |
513 | |
514 uw_reset_keep_request(ctx); | |
515 } | |
516 | |
517 uw_send(ctx, sock); | 119 uw_send(ctx, sock); |
518 | 120 |
519 printf("Done with client.\n\n"); | 121 if (rr == SERVED || rr == FAILED) |
520 uw_memstats(ctx); | 122 close(sock); |
123 else if (rr != KEEP_OPEN) | |
124 fprintf(stderr, "Illegal uw_request return code: %d\n", rr); | |
125 | |
521 break; | 126 break; |
522 } | 127 } |
523 } | 128 } |
524 | 129 |
525 done: | |
526 if (!dont_close) | |
527 close(sock); | |
528 uw_reset(ctx); | 130 uw_reset(ctx); |
529 } | |
530 } | |
531 | |
532 static void *client_pruner(void *data) { | |
533 uw_context ctx = new_context(); | |
534 | |
535 if (!ctx) | |
536 exit(1); | |
537 | |
538 while (1) { | |
539 uw_prune_clients(ctx); | |
540 sleep(5); | |
541 } | 131 } |
542 } | 132 } |
543 | 133 |
544 static void help(char *cmd) { | 134 static void help(char *cmd) { |
545 printf("Usage: %s [-p <port>] [-t <thread-count>]\n", cmd); | 135 printf("Usage: %s [-p <port>] [-t <thread-count>]\n", cmd); |
546 } | 136 } |
547 | 137 |
548 static void sigint(int signum) { | 138 static void sigint(int signum) { |
549 printf("Exiting....\n"); | 139 printf("Exiting....\n"); |
550 exit(0); | 140 exit(0); |
551 } | |
552 | |
553 static void initialize() { | |
554 uw_context ctx; | |
555 failure_kind fk; | |
556 | |
557 init_crypto(); | |
558 | |
559 ctx = new_context(); | |
560 | |
561 if (!ctx) | |
562 exit(1); | |
563 | |
564 for (fk = uw_initialize(ctx); fk == UNLIMITED_RETRY; fk = uw_initialize(ctx)) { | |
565 printf("Unlimited retry during init: %s\n", uw_error_message(ctx)); | |
566 uw_db_rollback(ctx); | |
567 uw_reset(ctx); | |
568 } | |
569 | |
570 if (fk != SUCCESS) { | |
571 printf("Failed to initialize database! %s\n", uw_error_message(ctx)); | |
572 uw_db_rollback(ctx); | |
573 exit(1); | |
574 } | |
575 | |
576 uw_free(ctx); | |
577 } | 141 } |
578 | 142 |
579 int main(int argc, char *argv[]) { | 143 int main(int argc, char *argv[]) { |
580 // The skeleton for this function comes from Beej's sockets tutorial. | 144 // The skeleton for this function comes from Beej's sockets tutorial. |
581 int sockfd; // listen on sock_fd | 145 int sockfd; // listen on sock_fd |
620 fprintf(stderr, "Unexpected getopt() behavior\n"); | 184 fprintf(stderr, "Unexpected getopt() behavior\n"); |
621 return 1; | 185 return 1; |
622 } | 186 } |
623 } | 187 } |
624 | 188 |
625 uw_global_init(); | 189 uw_request_init(); |
626 initialize(); | |
627 | 190 |
628 names = calloc(nthreads, sizeof(int)); | 191 names = calloc(nthreads, sizeof(int)); |
629 | 192 |
630 sockfd = socket(PF_INET, SOCK_STREAM, 0); // do some error checking! | 193 sockfd = socket(PF_INET, SOCK_STREAM, 0); // do some error checking! |
631 | 194 |