tinywebd
diff 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 |
line diff
1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/src/tinyweb.c Fri Apr 17 11:45:08 2015 +0300 1.3 @@ -0,0 +1,388 @@ 1.4 +#include <stdio.h> 1.5 +#include <stdlib.h> 1.6 +#include <string.h> 1.7 +#include <ctype.h> 1.8 +#include <errno.h> 1.9 +#include <unistd.h> 1.10 +#include <fcntl.h> 1.11 +#include <sys/stat.h> 1.12 +#include <sys/select.h> 1.13 +#include <sys/mman.h> 1.14 +#include <sys/types.h> 1.15 +#include <sys/socket.h> 1.16 +#include <arpa/inet.h> 1.17 +#include "tinyweb.h" 1.18 +#include "http.h" 1.19 +#include "mime.h" 1.20 +#include "logger.h" 1.21 + 1.22 +/* HTTP version */ 1.23 +#define HTTP_VER_MAJOR 1 1.24 +#define HTTP_VER_MINOR 1 1.25 +#define HTTP_VER_STR "1.1" 1.26 + 1.27 +/* maximum request length: 64mb */ 1.28 +#define MAX_REQ_LENGTH (65536 * 1024) 1.29 + 1.30 +struct client { 1.31 + int s; 1.32 + char *rcvbuf; 1.33 + int bufsz; 1.34 + struct client *next; 1.35 +}; 1.36 + 1.37 +static int accept_conn(int lis); 1.38 +static void close_conn(struct client *c); 1.39 +static int handle_client(struct client *c); 1.40 +static int do_get(struct client *c, const char *uri, int with_body); 1.41 +static void respond_error(struct client *c, int errcode); 1.42 + 1.43 +static int lis = -1; 1.44 +static int maxfd; 1.45 +static int port = 8080; 1.46 +static struct client *clist; 1.47 +static int num_clients; 1.48 + 1.49 +static const char *indexfiles[] = { 1.50 + "index.cgi", 1.51 + "index.html", 1.52 + "index.htm", 1.53 + 0 1.54 +}; 1.55 + 1.56 +void tw_set_port(int p) 1.57 +{ 1.58 + port = p; 1.59 +} 1.60 + 1.61 +int tw_set_root(const char *path) 1.62 +{ 1.63 + return chdir(path); 1.64 +} 1.65 + 1.66 +int tw_set_logfile(const char *fname) 1.67 +{ 1.68 + return set_log_file(fname); 1.69 +} 1.70 + 1.71 +int tw_start(void) 1.72 +{ 1.73 + int s; 1.74 + struct sockaddr_in sa; 1.75 + 1.76 + logmsg("starting server ...\n"); 1.77 + 1.78 + if(lis != -1) { 1.79 + logmsg("can't start tinyweb server: already running!\n"); 1.80 + return -1; 1.81 + } 1.82 + 1.83 + if((s = socket(PF_INET, SOCK_STREAM, 0)) == -1) { 1.84 + logmsg("failed to create listening socket: %s\n", strerror(errno)); 1.85 + return -1; 1.86 + } 1.87 + fcntl(s, F_SETFL, fcntl(s, F_GETFL) | O_NONBLOCK); 1.88 + 1.89 + memset(&sa, 0, sizeof sa); 1.90 + sa.sin_family = AF_INET; 1.91 + sa.sin_addr.s_addr = INADDR_ANY; 1.92 + sa.sin_port = htons(port); 1.93 + 1.94 + if(bind(s, (struct sockaddr*)&sa, sizeof sa) == -1) { 1.95 + logmsg("failed to bind socket to port %d: %s\n", port, strerror(errno)); 1.96 + return -1; 1.97 + } 1.98 + listen(s, 16); 1.99 + 1.100 + lis = s; 1.101 + return s; 1.102 +} 1.103 + 1.104 +int tw_stop(void) 1.105 +{ 1.106 + if(lis == -1) { 1.107 + return -1; 1.108 + } 1.109 + 1.110 + logmsg("stopping server...\n"); 1.111 + 1.112 + close(lis); 1.113 + lis = -1; 1.114 + 1.115 + while(clist) { 1.116 + struct client *c = clist; 1.117 + clist = clist->next; 1.118 + close_conn(c); 1.119 + free(c); 1.120 + } 1.121 + clist = 0; 1.122 + 1.123 + return 0; 1.124 +} 1.125 + 1.126 +int tw_get_sockets(int *socks) 1.127 +{ 1.128 + struct client *c, dummy; 1.129 + 1.130 + if(!socks) { 1.131 + /* just return the count */ 1.132 + return num_clients + 1; /* +1 for the listening socket */ 1.133 + } 1.134 + 1.135 + /* first cleanup the clients marked for removal */ 1.136 + dummy.next = clist; 1.137 + c = &dummy; 1.138 + 1.139 + while(c->next) { 1.140 + struct client *n = c->next; 1.141 + 1.142 + if(n->s == -1) { 1.143 + /* marked for removal */ 1.144 + c->next = n->next; 1.145 + free(n); 1.146 + --num_clients; 1.147 + } else { 1.148 + c = c->next; 1.149 + } 1.150 + } 1.151 + clist = dummy.next; 1.152 + 1.153 + /* go through the client list and populate the array */ 1.154 + maxfd = lis; 1.155 + *socks++ = lis; 1.156 + 1.157 + c = clist; 1.158 + while(c) { 1.159 + *socks++ = c->s; 1.160 + if(c->s > maxfd) { 1.161 + maxfd = c->s; 1.162 + } 1.163 + c = c->next; 1.164 + } 1.165 + return num_clients + 1; /* +1 for the listening socket */ 1.166 +} 1.167 + 1.168 +int tw_get_maxfd(void) 1.169 +{ 1.170 + return maxfd; 1.171 +} 1.172 + 1.173 +int tw_handle_socket(int s) 1.174 +{ 1.175 + struct client *c; 1.176 + 1.177 + if(s == lis) { 1.178 + return accept_conn(s); 1.179 + } 1.180 + 1.181 + /* find which client corresponds to this socket */ 1.182 + c = clist; 1.183 + while(c) { 1.184 + if(c->s == s) { 1.185 + return handle_client(c); 1.186 + } 1.187 + c = c->next; 1.188 + } 1.189 + 1.190 + logmsg("socket %d doesn't correspond to any client\n"); 1.191 + return -1; 1.192 +} 1.193 + 1.194 +static int accept_conn(int lis) 1.195 +{ 1.196 + int s; 1.197 + struct client *c; 1.198 + struct sockaddr_in addr; 1.199 + socklen_t addr_sz = sizeof addr; 1.200 + 1.201 + if((s = accept(lis, (struct sockaddr*)&addr, &addr_sz)) == -1) { 1.202 + logmsg("failed to accept incoming connection: %s\n", strerror(errno)); 1.203 + return -1; 1.204 + } 1.205 + fcntl(s, F_SETFL, fcntl(s, F_GETFL) | O_NONBLOCK); 1.206 + 1.207 + if(!(c = malloc(sizeof *c))) { 1.208 + logmsg("failed to allocate memory while accepting connection: %s\n", strerror(errno)); 1.209 + return -1; 1.210 + } 1.211 + c->s = s; 1.212 + c->rcvbuf = 0; 1.213 + c->bufsz = 0; 1.214 + c->next = clist; 1.215 + clist = c; 1.216 + return 0; 1.217 +} 1.218 + 1.219 +static void close_conn(struct client *c) 1.220 +{ 1.221 + close(c->s); 1.222 + c->s = -1; /* mark it for removal */ 1.223 + free(c->rcvbuf); 1.224 + c->rcvbuf = 0; 1.225 +} 1.226 + 1.227 +static int handle_client(struct client *c) 1.228 +{ 1.229 + struct http_req_header hdr; 1.230 + static char buf[2048]; 1.231 + int rdsz, status; 1.232 + 1.233 + while((rdsz = recv(c->s, buf, sizeof buf, 0)) > 0) { 1.234 + char *newbuf; 1.235 + int newsz = c->bufsz + rdsz; 1.236 + if(newsz > MAX_REQ_LENGTH) { 1.237 + respond_error(c, 413); 1.238 + return -1; 1.239 + } 1.240 + 1.241 + if(!(newbuf = realloc(c->rcvbuf, newsz + 1))) { 1.242 + logmsg("failed to allocate %d byte buffer\n", newsz); 1.243 + respond_error(c, 503); 1.244 + return -1; 1.245 + } 1.246 + 1.247 + memcpy(newbuf + c->bufsz, buf, rdsz); 1.248 + newbuf[newsz] = 0; 1.249 + 1.250 + c->rcvbuf = newbuf; 1.251 + c->bufsz = newsz; 1.252 + } 1.253 + 1.254 + if((status = http_parse_request(&hdr, c->rcvbuf, c->bufsz)) != HTTP_HDR_OK) { 1.255 + http_log_request(&hdr); 1.256 + switch(status) { 1.257 + case HTTP_HDR_INVALID: 1.258 + respond_error(c, 400); 1.259 + return -1; 1.260 + 1.261 + case HTTP_HDR_NOMEM: 1.262 + respond_error(c, 503); 1.263 + return -1; 1.264 + 1.265 + case HTTP_HDR_PARTIAL: 1.266 + return 0; /* partial header, continue reading */ 1.267 + } 1.268 + } 1.269 + http_log_request(&hdr); 1.270 + 1.271 + /* we only support GET and HEAD at this point, so freak out on anything else */ 1.272 + switch(hdr.method) { 1.273 + case HTTP_GET: 1.274 + if(do_get(c, hdr.uri, 1) == -1) { 1.275 + return -1; 1.276 + } 1.277 + break; 1.278 + 1.279 + case HTTP_HEAD: 1.280 + if(do_get(c, hdr.uri, 0) == -1) { 1.281 + return -1; 1.282 + } 1.283 + break; 1.284 + 1.285 + default: 1.286 + respond_error(c, 501); 1.287 + return -1; 1.288 + } 1.289 + 1.290 + close_conn(c); 1.291 + return 0; 1.292 +} 1.293 + 1.294 +static int do_get(struct client *c, const char *uri, int with_body) 1.295 +{ 1.296 + const char *ptr; 1.297 + struct http_resp_header resp; 1.298 + 1.299 + if((ptr = strstr(uri, "://"))) { 1.300 + uri = ptr + 3; 1.301 + } 1.302 + 1.303 + /* skip the host part and the first slash if it exists */ 1.304 + if((ptr = strchr(uri, '/'))) { 1.305 + uri = ptr + 1; 1.306 + } 1.307 + 1.308 + if(*uri) { 1.309 + struct stat st; 1.310 + char *path = 0; 1.311 + char *rsphdr; 1.312 + const char *type; 1.313 + int fd, rspsize; 1.314 + 1.315 + if(stat(uri, &st) == -1) { 1.316 + respond_error(c, 404); 1.317 + return -1; 1.318 + } 1.319 + 1.320 + if(S_ISDIR(st.st_mode)) { 1.321 + int i; 1.322 + path = alloca(strlen(uri) + 64); 1.323 + 1.324 + for(i=0; indexfiles[i]; i++) { 1.325 + sprintf(path, "%s/%s", uri, indexfiles[i]); 1.326 + if(stat(path, &st) == 0 && !S_ISDIR(st.st_mode)) { 1.327 + break; 1.328 + } 1.329 + } 1.330 + 1.331 + if(indexfiles[i] == 0) { 1.332 + respond_error(c, 404); 1.333 + return -1; 1.334 + } 1.335 + } else { 1.336 + path = (char*)uri; 1.337 + } 1.338 + 1.339 + if((fd = open(path, O_RDONLY)) == -1) { 1.340 + respond_error(c, 403); 1.341 + return -1; 1.342 + } 1.343 + 1.344 + /* construct response header */ 1.345 + http_init_resp(&resp); 1.346 + http_add_resp_field(&resp, "Content-Length: %d", st.st_size); 1.347 + if((type = mime_type(path))) { 1.348 + http_add_resp_field(&resp, "Content-Type: %s", type); 1.349 + } 1.350 + 1.351 + rspsize = http_serialize_resp(&resp, 0); 1.352 + rsphdr = alloca(rspsize); 1.353 + http_serialize_resp(&resp, rsphdr); 1.354 + 1.355 + if(with_body) { 1.356 + int cont_left = st.st_size; 1.357 + char *cont = mmap(0, st.st_size, PROT_READ, MAP_PRIVATE, fd, 0); 1.358 + if(cont == (void*)-1) { 1.359 + respond_error(c, 503); 1.360 + close(fd); 1.361 + return -1; 1.362 + } 1.363 + ptr = cont; 1.364 + 1.365 + send(c->s, rsphdr, rspsize, 0); 1.366 + while(cont_left > 0) { 1.367 + int sz = cont_left < 4096 ? cont_left : 4096; 1.368 + send(c->s, ptr, sz, 0); 1.369 + ptr += sz; 1.370 + } 1.371 + 1.372 + munmap(cont, st.st_size); 1.373 + } else { 1.374 + send(c->s, rsphdr, rspsize, 0); 1.375 + } 1.376 + 1.377 + close(fd); 1.378 + } 1.379 + return 0; 1.380 +} 1.381 + 1.382 +static void respond_error(struct client *c, int errcode) 1.383 +{ 1.384 + char buf[512]; 1.385 + 1.386 + sprintf(buf, "HTTP/" HTTP_VER_STR " %d %s\r\n\r\n", errcode, http_strmsg(errcode)); 1.387 + 1.388 + send(c->s, buf, strlen(buf), 0); 1.389 + close_conn(c); 1.390 +} 1.391 +