/* Minimal webserver.c by Pieter Suurmond, februari 4, 2003. No copyrights. Updated november 14, 2003 ( remark). Compile with a UNIX C compiler with POSIX threads, for example with GNU GCC: gcc -Wall -fomit-frame-pointer -ffast-math -O2 -D_REENTRANT \ mtserver.c -o mtserver -lpthread Startup './mtserver' and watch 'http://localhost:1234/BLOOP/' in a web- browser. One can shutdown with something like 'http://localhost:1234/Q'. */ #include #include #include #include #include #include #include #include #include #include /* For sigprocmask(). */ #include /* POSIX threads. */ #include /* For directory-reading and other stuff but also for */ /* bind and accept (thanks to Sam). */ #include /* File dir.h for 4.3BSD, dirent.h for System V versions. */ #include /* For S_ISDIR(st.st_mode) */ /*---------------------------------------------------------------------------------*/ typedef struct /* Data common to all threads. */ { char* software; /* Pointer to string with name and version. */ long in_size; /* Number of unsigned chars for input buffer. */ long out_size; /* Number of unsigned chars for output buffer. */ unsigned char up; /* Shared memery for quitting (make it 0). */ } GLOBAL_DATA; /*---------------------------------------------------------------------------------*/ typedef struct /* Such a structure is passed during spawning. */ { GLOBAL_DATA* global_data; unsigned char* in_buff; /* Allocated and released by parent. */ unsigned char* out_buff; /* Allocated and released by parent. */ int socket; /* Opened by parent, closed by child. */ unsigned char active; /* Not active if 0, otherwise active. */ } MT_SERVICE; /*--------------------------------------------------------------------------*/ int selFunc_dotfilter(struct direct* de) /* Skip names that begin with dot. */ { if (*de->d_name != '.') return(1); return(0); } /*---------------------------------------------------------------------*/ int genIndex(char* path, MT_SERVICE* s) { int dlen, nn, totlen, n, num, result = 0; struct direct** handle = NULL; struct stat statBuf; char *longPath, *what; num = scandir(path, &handle, selFunc_dotfilter, alphasort); if ((num < 0) || (!handle)) /* Test both to be sure. */ return -1; n = sprintf(s->out_buff, "Index of %s:\n\n", path); if (n != send(s->socket, s->out_buff, n, 0)) { result = -2; goto done; } dlen = 1 + strlen(path); /* 1 extra for separator '/' in pathnames. */ totlen = dlen + 128; /* Guess (and check!) 128 chars for dir/file-name. */ longPath = malloc(1 + totlen); /* 1 extra for terminating '\0'. */ if (longPath) { for (n=0; nd_name)) > totlen) { result = -3; goto done; } sprintf(longPath, "%s/%s", path, handle[n]->d_name); if (stat(longPath, &statBuf)) { result = -4; goto done; } if (S_ISDIR(statBuf.st_mode)) what = "DIR "; else what = "FILE"; nn = sprintf(s->out_buff, " %s %s\n", what, handle[n]->d_name); if (nn != send(s->socket, s->out_buff, nn, 0)) { result = -5; goto done; } } free(longPath); } else result = -6; done: for (n=0; nREQUEST_METHOD = in; /* Returns < 0 in case of failure, */ in = strchr(in, ' '); /* 0 for request, +1 for index. */ if (!in) return -1; *in++ = (char)0; out->SCRIPT_NAME = in; in = strchr(in, ' '); if (!in) return -2; *in++ = (char)0; if ('/' != out->SCRIPT_NAME[0]) /* SCRIPT_NAME must start with '/' */ return -3; /* (so at least one char there). */ if (!out->SCRIPT_NAME[1]) /* Request ""--> +1 */ return 1; if (strchr(".,/\\", out->SCRIPT_NAME[1])) /* Name may not start with dot, slash... */ return -4; /* True if SCRIPT_NAME[1] == '\0'. */ out->SERVER_PROTOCOL = in; while (*in) { if ((*in == 10) || (*in == 13)) { *in++ = (char)0; out->remains = in; if (strcasecmp("HTTP/1.0", out->SERVER_PROTOCOL) && strcasecmp("HTTP/1.1", out->SERVER_PROTOCOL)) return -5; return 0; } in++; } return -6; } /*---------------------------------------------------------------------------------*/ void* childServer(void* service) { FILE* diag = stdout; MT_SERVICE* s = (MT_SERVICE*)service; RQ request; int n, num; char *msg = "?", *bye = ""; num = recv(s->socket, s->in_buff, (s->global_data->in_size)-1, 0); if (num < 0) /* One less for termination. */ { msg = "Recv error!"; goto childFinish; } #if 0 if (num >= s->global_data->in_size) /* Should never occur. */ { msg = "Weird recv error!"; goto childFinish; } #endif s->in_buff[num] = (char)0; /* Make it a C-string. */ num = parseInput(s->in_buff, &request); if (num < 0) { fprintf(diag, "Bad request: parseInput() = %d.\n", num); n = sprintf(s->out_buff, "\ HTTP/1.0 400 Bad request\nServer: %s\nContent-type: text/plain\n\n\ parseInput() = %d\n", s->global_data->software, num); if (n != send(s->socket, s->out_buff, n, 0)) { msg = "Send error!"; goto childFinish; } } else if (strcasecmp(request.REQUEST_METHOD, "GET")) /* Block anything else. */ { fprintf(diag, "Request method not allowed: %s.\n", request.REQUEST_METHOD); n = sprintf(s->out_buff, "\ %s 401 Unauthorized\nServer: %s\nContent-type: text/plain\n\n\ Not allowed REQUEST_METHOD: %s\n", request.SERVER_PROTOCOL, s->global_data->software, request.REQUEST_METHOD); if (n != send(s->socket, s->out_buff, n, 0)) { msg = "Send error!"; goto childFinish; } } else if (num/*==1*/) { n = sprintf(s->out_buff, "%s 200 OK\nServer: %s\nContent-type: text/plain\n\n", request.SERVER_PROTOCOL, s->global_data->software); if (n != send(s->socket, s->out_buff, n, 0)) { msg = "Send error!"; goto childFinish; } if (genIndex("./", s)) { msg = "GenIndex error!"; goto childFinish; } } else /* SCRIPT_NAME[0] == '/' */ { if ((request.SCRIPT_NAME[1] == 'q') || (request.SCRIPT_NAME[1] == 'Q')) bye = ", YOU ARE KILLING ME (the next request I'll die)"; /* Output complete header: */ n = sprintf(s->out_buff, "\ %s 200 OK\nServer: %s\nContent-type: text/plain\n\n\ Hi%s!\nI am %s.\n\ You sent me this:\n\n\ REQUEST_METHOD = %s\n\ SCRIPT_NAME = %s\n\ SERVER_PROTOCOL = %s\n\ remains = \n%s\n", request.SERVER_PROTOCOL, s->global_data->software, bye, s->global_data->software, request.REQUEST_METHOD, request.SCRIPT_NAME, request.SERVER_PROTOCOL, request.remains); if (n != send(s->socket, s->out_buff, n, 0)) { msg = "Send error!"; goto childFinish; } } fprintf(diag, "Served ok.\n"); childFinish: close(s->socket); /* Close socket created by accept() in parent. */ if (*bye) s->global_data->up = 0; /* Signal to parent to quit. */ s->active = 0; /* signal to parent we're (almost?) dead. */ return (void*)NULL; } /*---------------------------------------------------------------------------------*/ int main(void) { /* Configuration: */ char* software = "mtserver 0.20"; int port = 1234; /* port number for server. */ FILE* diag = stdout; /* diag may not be NULL. */ long in_size = 4096; long out_size = 16384; short parallel = 8; /* Max simultaneous conns. */ short queue = 16; /* Max queued connections. */ GLOBAL_DATA global_data; MT_SERVICE *services, *s; pthread_attr_t detach_attr; pthread_t forget_thread; sigset_t blockSet; /* To block broken-pipe. */ struct sockaddr_in addr, remote_addr; int sockfd, fd_size, one = 1 /*, zero = 0*/; time_t t; char *tt, *msg = "?"; short i, activeCount; unsigned char failed; /*------------------------------------------------------------- Time and date: */ time(&t); tt = ctime(&t); fprintf(diag, "%s started on %s", software, tt); /*------------------------------------------------------------- Init: */ global_data.software = software; global_data.in_size = in_size; global_data.out_size = out_size; global_data.up = 1; /*------------------------------------------------------------- Memory alloc: */ services = (MT_SERVICE*)malloc(parallel * sizeof(MT_SERVICE)); if (!services) { msg = "Not enough memory!"; goto finish; } s = services; /* Initialise array. */ failed = 0; for (i = 0; i < parallel; i++) { /* s->socket needs no initialisation. */ s->global_data = &global_data; s->in_buff = (unsigned char*)malloc(in_size * sizeof(unsigned char)); s->out_buff = (unsigned char*)malloc(out_size * sizeof(unsigned char)); s->active = (unsigned char)0; if ((!s->in_buff) || (!s->out_buff)) failed = 1; /* But go on initialising all members. */ s++; } if (failed) { msg = "Not enough memory for buffers!"; goto finish; } /*------------------------------------------------------------ Create socket: */ sockfd = socket(PF_INET, SOCK_STREAM, 0); /* Or is 'AF_INET' better? */ if (sockfd == -1) { msg = "Socket error!"; goto finish; } /* Alternative for 'perror("socket()");'. */ addr.sin_family = PF_INET; addr.sin_port = htons(port); memset(&addr.sin_zero, 0, 8); addr.sin_addr.s_addr = htonl(INADDR_ANY); if (setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, (void*)&one, sizeof(int)) < 0) { msg = "Setsockopt error!"; goto finish; } /* if (setsockopt(sockfd, SOL_SOCKET, SO_KEEPALIVE, (void*)&zero, sizeof(int)) < 0) { msg = "Setsockopt error2!"; goto finish; } */ if (bind(sockfd, &addr, sizeof(struct sockaddr)) < 0) { msg = "Bind error!"; goto finish; } if (listen(sockfd, queue) < 0) { msg = "Listen error!"; goto finish; } /*-------------------------------- Prevent broken pipe signals from exitting program: */ if (sigemptyset(&blockSet)) /* Clear all signals in specified set. */ { msg = "Sigemptyset error!"; goto finish; } /* Adds sig to the specified set. */ if (sigaddset(&blockSet, SIGPIPE)) { msg = "Sigaddset error!"; goto finish; } /* Manipulate set of signals which are blocked. */ if (pthread_sigmask(SIG_BLOCK, &blockSet, NULL)) /* All children inherit this! */ { msg = "Sigprocmask error!"; goto finish; } /*-------------------------------------- Set thread options: */ if (pthread_attr_init(&detach_attr)) /* Prepare thread attribute for automatic de- */ { /* tachment, so we don't need pthread_join(). */ msg = "Pthread_attr_init error!"; goto finish; } if (pthread_attr_setdetachstate(&detach_attr, PTHREAD_CREATE_DETACHED)) { msg = "Pthread_attr_setdetachstate error!"; global_data.up = 0; /* Don't jump over pthread_attr_destroy(). */ } /*------------------------------------------------ Service incoming requests: */ fprintf(diag, "Listening on port %d\n\n", port); activeCount = 0; while (global_data.up) { s = services; i = 0; while (s->active && (i < parallel)) { s++; i++; if (activeCount++ == 1023) { fprintf(diag, "Server busy!\n"); sleep(2); /* Give the machine some rest. */ } } if (i < parallel) /* Thus (!s->active). */ { activeCount = 0; acceptAgain: fd_size = sizeof(struct sockaddr_in); s->socket = accept(sockfd, &remote_addr, &fd_size); /* Wait for a request. */ if (s->socket < 0) { fprintf(diag, "Accept error!\n"); goto acceptAgain; } /* Spawned child closes own socket. */ if (pthread_create(&forget_thread, &detach_attr, childServer, (void*)s)) { close(s->socket); fprintf(diag, "Pthread_create error!\n"); goto acceptAgain; } else s->active = 1; /* Only when we really succeeded, reset by child. */ } } /* I don't see no point in 'destroying'... might it even be dangerous? I did not alloc it, it is static object. */ if (pthread_attr_destroy(&detach_attr)) msg = "pthread_attr_destroy() failed!"; else msg = "Graceful shutdown."; finish: fprintf(diag, "%s\r\n", msg); if (services) /* Cleanup array. */ { s = services; for (i = 0; i < parallel; i++) { if (s->active) { fprintf(diag, "Waiting for thread %d...\n", i); activeCount = 0; while (s->active) { sleep(1); /* Give children i.e. clients 1 minute to finish. */ if (activeCount++ > 60) { fprintf(diag, "KILLING!\n"); exit(1); /* PANIC. */ } } } if (s->in_buff) free(s->in_buff); if (s->out_buff) free(s->out_buff); s++; } free(services); } return 0; }