tinywebd

annotate src/main.c @ 5:def49a046566

serialization of responses
author John Tsiombikas <nuclear@member.fsf.org>
date Fri, 17 Apr 2015 01:57:00 +0300
parents 9e054c002489
children 5ec50ca0d071
rev   line source
nuclear@0 1 #include <stdio.h>
nuclear@0 2 #include <stdlib.h>
nuclear@0 3 #include <string.h>
nuclear@3 4 #include <ctype.h>
nuclear@3 5 #include <signal.h>
nuclear@0 6 #include <errno.h>
nuclear@0 7 #include <unistd.h>
nuclear@0 8 #include <fcntl.h>
nuclear@5 9 #include <sys/stat.h>
nuclear@0 10 #include <sys/select.h>
nuclear@5 11 #include <sys/mman.h>
nuclear@0 12 #include <sys/types.h>
nuclear@0 13 #include <sys/socket.h>
nuclear@0 14 #include <arpa/inet.h>
nuclear@1 15 #include "http.h"
nuclear@5 16 #include "mime.h"
nuclear@1 17
nuclear@1 18 /* HTTP version */
nuclear@1 19 #define HTTP_VER_MAJOR 1
nuclear@1 20 #define HTTP_VER_MINOR 1
nuclear@1 21 #define HTTP_VER_STR "1.1"
nuclear@1 22
nuclear@1 23 /* maximum request length: 64mb */
nuclear@1 24 #define MAX_REQ_LENGTH (65536 * 1024)
nuclear@0 25
nuclear@0 26 struct client {
nuclear@0 27 int s;
nuclear@0 28 char *rcvbuf;
nuclear@0 29 int bufsz;
nuclear@0 30 struct client *next;
nuclear@0 31 };
nuclear@0 32
nuclear@0 33 int start_server(void);
nuclear@0 34 int accept_conn(int lis);
nuclear@3 35 void close_conn(struct client *c);
nuclear@0 36 int handle_client(struct client *c);
nuclear@5 37 int do_get(struct client *c, const char *uri, int with_body);
nuclear@1 38 void respond_error(struct client *c, int errcode);
nuclear@3 39 void sighandler(int s);
nuclear@1 40 int parse_args(int argc, char **argv);
nuclear@0 41
nuclear@0 42 static int lis;
nuclear@0 43 static int port = 8080;
nuclear@0 44 static struct client *clist;
nuclear@0 45
nuclear@5 46 static const char *indexfiles[] = {
nuclear@5 47 "index.cgi",
nuclear@5 48 "index.html",
nuclear@5 49 "index.htm",
nuclear@5 50 0
nuclear@5 51 };
nuclear@5 52
nuclear@5 53
nuclear@0 54 int main(int argc, char **argv)
nuclear@0 55 {
nuclear@1 56 if(parse_args(argc, argv) == -1) {
nuclear@1 57 return 1;
nuclear@1 58 }
nuclear@1 59
nuclear@3 60 signal(SIGINT, sighandler);
nuclear@3 61 signal(SIGTERM, sighandler);
nuclear@3 62 signal(SIGQUIT, sighandler);
nuclear@3 63
nuclear@0 64 if((lis = start_server()) == -1) {
nuclear@0 65 return 1;
nuclear@0 66 }
nuclear@0 67
nuclear@0 68 for(;;) {
nuclear@0 69 struct client *c, dummy;
nuclear@0 70 int maxfd = lis;
nuclear@0 71 fd_set rdset;
nuclear@0 72
nuclear@0 73 FD_ZERO(&rdset);
nuclear@0 74 FD_SET(lis, &rdset);
nuclear@0 75
nuclear@0 76 c = clist;
nuclear@0 77 while(c) {
nuclear@0 78 if(c->s > maxfd) {
nuclear@0 79 maxfd = c->s;
nuclear@0 80 }
nuclear@0 81 FD_SET(c->s, &rdset);
nuclear@0 82 c = c->next;
nuclear@0 83 }
nuclear@0 84
nuclear@0 85 while(select(maxfd + 1, &rdset, 0, 0, 0) == -1 && errno == EINTR);
nuclear@0 86
nuclear@0 87 c = clist;
nuclear@0 88 while(c) {
nuclear@0 89 if(FD_ISSET(c->s, &rdset)) {
nuclear@0 90 handle_client(c);
nuclear@0 91 }
nuclear@0 92 c = c->next;
nuclear@0 93 }
nuclear@0 94
nuclear@0 95 if(FD_ISSET(lis, &rdset)) {
nuclear@0 96 accept_conn(lis);
nuclear@0 97 }
nuclear@0 98
nuclear@0 99 dummy.next = clist;
nuclear@0 100 c = &dummy;
nuclear@0 101
nuclear@0 102 while(c->next) {
nuclear@0 103 struct client *n = c->next;
nuclear@0 104
nuclear@0 105 if(n->s == -1) {
nuclear@0 106 /* marked for removal */
nuclear@0 107 c->next = n->next;
nuclear@0 108 free(n);
nuclear@0 109 } else {
nuclear@0 110 c = c->next;
nuclear@0 111 }
nuclear@0 112 }
nuclear@4 113 clist = dummy.next;
nuclear@0 114 }
nuclear@3 115
nuclear@3 116 return 0; /* unreachable */
nuclear@0 117 }
nuclear@0 118
nuclear@0 119 int start_server(void)
nuclear@0 120 {
nuclear@0 121 int s;
nuclear@0 122 struct sockaddr_in sa;
nuclear@0 123
nuclear@0 124 if((s = socket(PF_INET, SOCK_STREAM, 0)) == -1) {
nuclear@0 125 perror("failed to create listening socket");
nuclear@0 126 return -1;
nuclear@0 127 }
nuclear@0 128 fcntl(s, F_SETFL, fcntl(s, F_GETFL) | O_NONBLOCK);
nuclear@0 129
nuclear@0 130 memset(&sa, 0, sizeof sa);
nuclear@0 131 sa.sin_family = AF_INET;
nuclear@0 132 sa.sin_addr.s_addr = INADDR_ANY;
nuclear@0 133 sa.sin_port = htons(port);
nuclear@0 134
nuclear@0 135 if(bind(s, (struct sockaddr*)&sa, sizeof sa) == -1) {
nuclear@0 136 fprintf(stderr, "failed to bind socket to port %d: %s\n", port, strerror(errno));
nuclear@0 137 return -1;
nuclear@0 138 }
nuclear@0 139 listen(s, 16);
nuclear@0 140
nuclear@0 141 return s;
nuclear@0 142 }
nuclear@0 143
nuclear@0 144 int accept_conn(int lis)
nuclear@0 145 {
nuclear@0 146 int s;
nuclear@0 147 struct client *c;
nuclear@0 148 struct sockaddr_in addr;
nuclear@0 149 socklen_t addr_sz = sizeof addr;
nuclear@0 150
nuclear@0 151 if((s = accept(lis, (struct sockaddr*)&addr, &addr_sz)) == -1) {
nuclear@0 152 perror("failed to accept incoming connection");
nuclear@0 153 return -1;
nuclear@0 154 }
nuclear@0 155 fcntl(s, F_SETFL, fcntl(s, F_GETFL) | O_NONBLOCK);
nuclear@0 156
nuclear@0 157 if(!(c = malloc(sizeof *c))) {
nuclear@0 158 perror("failed to allocate memory while accepting connection");
nuclear@0 159 return -1;
nuclear@0 160 }
nuclear@0 161 c->s = s;
nuclear@0 162 c->rcvbuf = 0;
nuclear@0 163 c->bufsz = 0;
nuclear@0 164 c->next = clist;
nuclear@0 165 clist = c;
nuclear@0 166 return 0;
nuclear@0 167 }
nuclear@0 168
nuclear@1 169 void close_conn(struct client *c)
nuclear@1 170 {
nuclear@1 171 close(c->s);
nuclear@1 172 c->s = -1; /* mark it for removal */
nuclear@1 173 free(c->rcvbuf);
nuclear@1 174 }
nuclear@1 175
nuclear@0 176 int handle_client(struct client *c)
nuclear@0 177 {
nuclear@3 178 struct http_req_header hdr;
nuclear@0 179 static char buf[2048];
nuclear@3 180 int rdsz, status;
nuclear@0 181
nuclear@0 182 while((rdsz = recv(c->s, buf, sizeof buf, 0)) > 0) {
nuclear@4 183 char *newbuf;
nuclear@4 184 int newsz = c->bufsz + rdsz;
nuclear@4 185 if(newsz > MAX_REQ_LENGTH) {
nuclear@4 186 respond_error(c, 413);
nuclear@4 187 return -1;
nuclear@4 188 }
nuclear@1 189
nuclear@4 190 if(!(newbuf = realloc(c->rcvbuf, newsz + 1))) {
nuclear@4 191 fprintf(stderr, "failed to allocate %d byte buffer\n", newsz);
nuclear@4 192 respond_error(c, 503);
nuclear@4 193 return -1;
nuclear@4 194 }
nuclear@0 195
nuclear@4 196 memcpy(newbuf + c->bufsz, buf, rdsz);
nuclear@4 197 newbuf[newsz] = 0;
nuclear@0 198
nuclear@4 199 c->rcvbuf = newbuf;
nuclear@4 200 c->bufsz = newsz;
nuclear@0 201 }
nuclear@0 202
nuclear@3 203 if((status = http_parse_header(&hdr, c->rcvbuf, c->bufsz)) != HTTP_HDR_OK) {
nuclear@3 204 http_print_header(&hdr);
nuclear@3 205 switch(status) {
nuclear@3 206 case HTTP_HDR_INVALID:
nuclear@3 207 respond_error(c, 400);
nuclear@3 208 return -1;
nuclear@3 209
nuclear@3 210 case HTTP_HDR_NOMEM:
nuclear@3 211 respond_error(c, 503);
nuclear@3 212 return -1;
nuclear@3 213
nuclear@3 214 case HTTP_HDR_PARTIAL:
nuclear@3 215 return 0; /* partial header, continue reading */
nuclear@3 216 }
nuclear@3 217 }
nuclear@3 218 http_print_header(&hdr);
nuclear@3 219
nuclear@3 220 /* we only support GET and HEAD at this point, so freak out on anything else */
nuclear@3 221 switch(hdr.method) {
nuclear@3 222 case HTTP_GET:
nuclear@5 223 do_get(c, hdr.uri, 1);
nuclear@5 224 break;
nuclear@5 225
nuclear@3 226 case HTTP_HEAD:
nuclear@5 227 do_get(c, hdr.uri, 0);
nuclear@3 228 break;
nuclear@3 229
nuclear@3 230 default:
nuclear@3 231 respond_error(c, 501);
nuclear@3 232 return -1;
nuclear@1 233 }
nuclear@1 234
nuclear@3 235 close_conn(c);
nuclear@3 236 return 0;
nuclear@3 237 }
nuclear@1 238
nuclear@5 239 int do_get(struct client *c, const char *uri, int with_body)
nuclear@3 240 {
nuclear@5 241 const char *ptr;
nuclear@5 242 struct http_resp_header resp;
nuclear@5 243
nuclear@5 244 if((ptr = strstr(uri, "://"))) {
nuclear@5 245 /* skip the host part */
nuclear@5 246 if(!(uri = strchr(ptr + 3, '/'))) {
nuclear@5 247 respond_error(c, 404);
nuclear@5 248 return -1;
nuclear@5 249 }
nuclear@5 250 ++uri;
nuclear@5 251 }
nuclear@5 252
nuclear@5 253 if(*uri) {
nuclear@5 254 struct stat st;
nuclear@5 255 char *path = 0;
nuclear@5 256 char *buf;
nuclear@5 257 const char *type;
nuclear@5 258 int fd, size;
nuclear@5 259
nuclear@5 260 if(stat(uri, &st) == -1) {
nuclear@5 261 respond_error(c, 404);
nuclear@5 262 return -1;
nuclear@5 263 }
nuclear@5 264
nuclear@5 265 if(S_ISDIR(st.st_mode)) {
nuclear@5 266 int i;
nuclear@5 267 path = alloca(strlen(uri) + 64);
nuclear@5 268
nuclear@5 269 for(i=0; indexfiles[i]; i++) {
nuclear@5 270 sprintf(path, "%s/%s", uri, indexfiles[i]);
nuclear@5 271 if(stat(path, &st) == 0 && !S_ISDIR(st.st_mode)) {
nuclear@5 272 break;
nuclear@5 273 }
nuclear@5 274 }
nuclear@5 275
nuclear@5 276 if(indexfiles[i] == 0) {
nuclear@5 277 respond_error(c, 404);
nuclear@5 278 return -1;
nuclear@5 279 }
nuclear@5 280 } else {
nuclear@5 281 path = (char*)uri;
nuclear@5 282 }
nuclear@5 283
nuclear@5 284 if((fd = open(path, O_RDONLY)) == -1) {
nuclear@5 285 respond_error(c, 403);
nuclear@5 286 return -1;
nuclear@5 287 }
nuclear@5 288
nuclear@5 289 /* construct response header */
nuclear@5 290 http_init_resp(&resp);
nuclear@5 291 http_add_resp_field(&resp, "Content-Length: %d", st.st_size);
nuclear@5 292 if((type = mime_type(path))) {
nuclear@5 293 http_add_resp_field(&resp, "Content-Type: %s", type);
nuclear@5 294 }
nuclear@5 295
nuclear@5 296 size = http_serialize_resp(&resp, 0);
nuclear@5 297 buf = alloca(size);
nuclear@5 298 http_serialize_resp(&resp, buf);
nuclear@5 299 send(c->s, buf, size, 0);
nuclear@5 300
nuclear@5 301 if(with_body) {
nuclear@5 302 char *cont = mmap(0, st.st_size, PROT_READ, MAP_PRIVATE, fd, 0);
nuclear@5 303 if(cont == (void*)-1) {
nuclear@5 304 respond_error(c, 503);
nuclear@5 305 close(fd);
nuclear@5 306 return -1;
nuclear@5 307 }
nuclear@5 308 }
nuclear@5 309
nuclear@5 310 close(fd);
nuclear@5 311 }
nuclear@5 312 return 0;
nuclear@1 313 }
nuclear@0 314
nuclear@1 315 void respond_error(struct client *c, int errcode)
nuclear@1 316 {
nuclear@1 317 char buf[512];
nuclear@1 318
nuclear@1 319 sprintf(buf, HTTP_VER_STR " %d %s\r\n\r\n", errcode, http_strmsg(errcode));
nuclear@1 320
nuclear@1 321 send(c->s, buf, strlen(buf), 0);
nuclear@1 322 close_conn(c);
nuclear@1 323 }
nuclear@1 324
nuclear@3 325 void sighandler(int s)
nuclear@3 326 {
nuclear@3 327 if(s == SIGINT || s == SIGTERM || s == SIGQUIT) {
nuclear@3 328 close(lis);
nuclear@3 329 while(clist) {
nuclear@3 330 struct client *c = clist;
nuclear@3 331 clist = clist->next;
nuclear@3 332 close_conn(c);
nuclear@3 333 free(c);
nuclear@3 334 }
nuclear@3 335 clist = 0;
nuclear@3 336
nuclear@3 337 printf("bye!\n");
nuclear@3 338 exit(0);
nuclear@3 339 }
nuclear@3 340 }
nuclear@3 341
nuclear@1 342
nuclear@1 343 static void print_help(const char *argv0)
nuclear@1 344 {
nuclear@1 345 printf("Usage: %s [options]\n", argv0);
nuclear@1 346 printf("Options:\n");
nuclear@1 347 printf(" -p <port> set the TCP/IP port number to use\n");
nuclear@1 348 printf(" -h print usage help and exit\n");
nuclear@1 349 }
nuclear@1 350
nuclear@1 351 int parse_args(int argc, char **argv)
nuclear@1 352 {
nuclear@1 353 int i;
nuclear@1 354
nuclear@1 355 for(i=1; i<argc; i++) {
nuclear@1 356 if(argv[i][0] == '-' && argv[i][2] == 0) {
nuclear@1 357 switch(argv[i][1]) {
nuclear@1 358 case 'p':
nuclear@1 359 if((port = atoi(argv[++i])) == 0) {
nuclear@1 360 fprintf(stderr, "-p must be followed by a valid port number\n");
nuclear@1 361 return -1;
nuclear@1 362 }
nuclear@1 363 break;
nuclear@1 364
nuclear@1 365 case 'h':
nuclear@1 366 print_help(argv[0]);
nuclear@1 367 exit(0);
nuclear@1 368
nuclear@1 369 default:
nuclear@1 370 fprintf(stderr, "unrecognized option: %s\n", argv[i]);
nuclear@1 371 return -1;
nuclear@1 372 }
nuclear@1 373 } else {
nuclear@1 374 fprintf(stderr, "unexpected argument: %s\n", argv[i]);
nuclear@1 375 return -1;
nuclear@1 376 }
nuclear@0 377 }
nuclear@0 378 return 0;
nuclear@0 379 }