tinywebd

annotate src/tinyweb.c @ 7:5ec50ca0d071

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