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 +