tiny-web-server.c 7.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253
  1. /*
  2. * tiny.c - a minimal HTTP server that serves static and
  3. * dynamic content with the GET method. Neither
  4. * robust, secure, nor modular. Use for instructional
  5. * purposes only.
  6. * Dave O'Hallaron, Carnegie Mellon
  7. *
  8. * usage: tiny <port>
  9. */
  10. #include <stdio.h>
  11. #include <unistd.h>
  12. #include <stdlib.h>
  13. #include <string.h>
  14. #include <netdb.h>
  15. #include <fcntl.h>
  16. #include <sys/types.h>
  17. #include <sys/socket.h>
  18. #include <sys/stat.h>
  19. #include <sys/mman.h>
  20. #include <sys/wait.h>
  21. #include <netinet/in.h>
  22. #include <arpa/inet.h>
  23. #define BUFSIZE 1024
  24. #define MAXERRS 16
  25. extern char **environ; /* the environment */
  26. /*
  27. * error - wrapper for perror used for bad syscalls
  28. */
  29. void error(char *msg) {
  30. perror(msg);
  31. exit(1);
  32. }
  33. /*
  34. * cerror - returns an error message to the client
  35. */
  36. void cerror(FILE *stream, char *cause, char *errno,
  37. char *shortmsg, char *longmsg) {
  38. fprintf(stream, "HTTP/1.1 %s %s\n", errno, shortmsg);
  39. fprintf(stream, "Content-type: text/html\n");
  40. fprintf(stream, "\n");
  41. fprintf(stream, "<html><title>Tiny Error</title>");
  42. fprintf(stream, "<body bgcolor=""ffffff"">\n");
  43. fprintf(stream, "%s: %s\n", errno, shortmsg);
  44. fprintf(stream, "<p>%s: %s\n", longmsg, cause);
  45. fprintf(stream, "<hr><em>The Tiny Web server</em>\n");
  46. }
  47. int main(int argc, char **argv) {
  48. /* variables for connection management */
  49. int parentfd; /* parent socket */
  50. int childfd; /* child socket */
  51. int portno; /* port to listen on */
  52. int clientlen; /* byte size of client's address */
  53. int optval; /* flag value for setsockopt */
  54. struct sockaddr_in serveraddr; /* server's addr */
  55. struct sockaddr_in clientaddr; /* client addr */
  56. /* variables for connection I/O */
  57. FILE *stream; /* stream version of childfd */
  58. char buf[BUFSIZE]; /* message buffer */
  59. char method[BUFSIZE]; /* request method */
  60. char uri[BUFSIZE]; /* request uri */
  61. char version[BUFSIZE]; /* request method */
  62. char filename[BUFSIZE];/* path derived from uri */
  63. char filetype[BUFSIZE];/* path derived from uri */
  64. char cgiargs[BUFSIZE]; /* cgi argument list */
  65. char *p; /* temporary pointer */
  66. int is_static; /* static request? */
  67. struct stat sbuf; /* file status */
  68. int fd; /* static content filedes */
  69. int pid; /* process id from fork */
  70. int wait_status; /* status from wait */
  71. /* check command line args */
  72. if (argc != 2) {
  73. fprintf(stderr, "usage: %s <port>\n", argv[0]);
  74. exit(1);
  75. }
  76. portno = atoi(argv[1]);
  77. /* open socket descriptor */
  78. parentfd = socket(AF_INET, SOCK_STREAM, 0);
  79. if (parentfd < 0)
  80. error("ERROR opening socket");
  81. /* allows us to restart server immediately */
  82. optval = 1;
  83. setsockopt(parentfd, SOL_SOCKET, SO_REUSEADDR,
  84. (const void *)&optval , sizeof(int));
  85. /* bind port to socket */
  86. bzero((char *) &serveraddr, sizeof(serveraddr));
  87. serveraddr.sin_family = AF_INET;
  88. serveraddr.sin_addr.s_addr = htonl(INADDR_ANY);
  89. serveraddr.sin_port = htons((unsigned short)portno);
  90. if (bind(parentfd, (struct sockaddr *) &serveraddr,
  91. sizeof(serveraddr)) < 0)
  92. error("ERROR on binding");
  93. /* get us ready to accept connection requests */
  94. if (listen(parentfd, 5) < 0) /* allow 5 requests to queue up */
  95. error("ERROR on listen");
  96. /*
  97. * main loop: wait for a connection request, parse HTTP,
  98. * serve requested content, close connection.
  99. */
  100. clientlen = sizeof(clientaddr);
  101. while (1) {
  102. /* wait for a connection request */
  103. childfd = accept(parentfd, (struct sockaddr *) &clientaddr, &clientlen);
  104. if (childfd < 0)
  105. error("ERROR on accept");
  106. /* open the child socket descriptor as a stream */
  107. if ((stream = fdopen(childfd, "r+")) == NULL)
  108. error("ERROR on fdopen");
  109. /* get the HTTP request line */
  110. fgets(buf, BUFSIZE, stream);
  111. printf("%s", buf);
  112. sscanf(buf, "%s %s %s\n", method, uri, version);
  113. /* tiny only supports the GET method */
  114. if (strcasecmp(method, "GET")) {
  115. cerror(stream, method, "501", "Not Implemented",
  116. "Tiny does not implement this method");
  117. fclose(stream);
  118. close(childfd);
  119. continue;
  120. }
  121. /* read (and ignore) the HTTP headers */
  122. fgets(buf, BUFSIZE, stream);
  123. printf("%s", buf);
  124. while(strcmp(buf, "\r\n")) {
  125. fgets(buf, BUFSIZE, stream);
  126. printf("%s", buf);
  127. }
  128. /* parse the uri [crufty] */
  129. if (!strstr(uri, "cgi-bin")) { /* static content */
  130. is_static = 1;
  131. strcpy(cgiargs, "");
  132. strcpy(filename, ".");
  133. strcat(filename, uri);
  134. if (uri[strlen(uri)-1] == '/')
  135. strcat(filename, "index.html");
  136. }
  137. else { /* dynamic content */
  138. is_static = 0;
  139. p = index(uri, '?');
  140. if (p) {
  141. strcpy(cgiargs, p+1);
  142. *p = '\0';
  143. }
  144. else {
  145. strcpy(cgiargs, "");
  146. }
  147. strcpy(filename, ".");
  148. strcat(filename, uri);
  149. }
  150. /* make sure the file exists */
  151. if (stat(filename, &sbuf) < 0) {
  152. cerror(stream, filename, "404", "Not found",
  153. "Tiny couldn't find this file");
  154. fclose(stream);
  155. close(childfd);
  156. continue;
  157. }
  158. /* serve static content */
  159. if (is_static) {
  160. if (strstr(filename, ".html"))
  161. strcpy(filetype, "text/html");
  162. else if (strstr(filename, ".gif"))
  163. strcpy(filetype, "image/gif");
  164. else if (strstr(filename, ".jpg"))
  165. strcpy(filetype, "image/jpg");
  166. else
  167. strcpy(filetype, "text/plain");
  168. /* print response header */
  169. fprintf(stream, "HTTP/1.1 200 OK\n");
  170. fprintf(stream, "Server: Tiny Web Server\n");
  171. fprintf(stream, "Content-length: %d\n", (int)sbuf.st_size);
  172. fprintf(stream, "Content-type: %s\n", filetype);
  173. fprintf(stream, "\r\n");
  174. fflush(stream);
  175. /* Use mmap to return arbitrary-sized response body */
  176. fd = open(filename, O_RDONLY);
  177. p = mmap(0, sbuf.st_size, PROT_READ, MAP_PRIVATE, fd, 0);
  178. fwrite(p, 1, sbuf.st_size, stream);
  179. munmap(p, sbuf.st_size);
  180. }
  181. /* serve dynamic content */
  182. else {
  183. /* make sure file is a regular executable file */
  184. if (!(S_IFREG & sbuf.st_mode) || !(S_IXUSR & sbuf.st_mode)) {
  185. cerror(stream, filename, "403", "Forbidden",
  186. "You are not allow to access this item");
  187. fclose(stream);
  188. close(childfd);
  189. continue;
  190. }
  191. /* a real server would set other CGI environ vars as well*/
  192. setenv("QUERY_STRING", cgiargs, 1);
  193. /* print first part of response header */
  194. sprintf(buf, "HTTP/1.1 200 OK\n");
  195. write(childfd, buf, strlen(buf));
  196. sprintf(buf, "Server: Tiny Web Server\n");
  197. write(childfd, buf, strlen(buf));
  198. /* create and run the child CGI process so that all child
  199. output to stdout and stderr goes back to the client via the
  200. childfd socket descriptor */
  201. pid = fork();
  202. if (pid < 0) {
  203. perror("ERROR in fork");
  204. exit(1);
  205. }
  206. else if (pid > 0) { /* parent process */
  207. wait(&wait_status);
  208. }
  209. else { /* child process*/
  210. close(0); /* close stdin */
  211. dup2(childfd, 1); /* map socket to stdout */
  212. dup2(childfd, 2); /* map socket to stderr */
  213. if (execve(filename, NULL, environ) < 0) {
  214. perror("ERROR in execve");
  215. }
  216. }
  217. }
  218. /* clean up */
  219. fclose(stream);
  220. close(childfd);
  221. }
  222. }