2 changed files with 254 additions and 1 deletions
@ -0,0 +1,253 @@
|
||||
/*
|
||||
* tiny.c - a minimal HTTP server that serves static and |
||||
* dynamic content with the GET method. Neither
|
||||
* robust, secure, nor modular. Use for instructional |
||||
* purposes only. |
||||
* Dave O'Hallaron, Carnegie Mellon |
||||
* |
||||
* usage: tiny <port> |
||||
*/ |
||||
|
||||
#include <stdio.h> |
||||
#include <unistd.h> |
||||
#include <stdlib.h> |
||||
#include <string.h> |
||||
#include <netdb.h> |
||||
#include <fcntl.h> |
||||
#include <sys/types.h> |
||||
#include <sys/socket.h> |
||||
#include <sys/stat.h> |
||||
#include <sys/mman.h> |
||||
#include <sys/wait.h> |
||||
#include <netinet/in.h> |
||||
#include <arpa/inet.h> |
||||
|
||||
#define BUFSIZE 1024 |
||||
#define MAXERRS 16 |
||||
|
||||
extern char **environ; /* the environment */ |
||||
|
||||
/*
|
||||
* error - wrapper for perror used for bad syscalls |
||||
*/ |
||||
void error(char *msg) { |
||||
perror(msg); |
||||
exit(1); |
||||
} |
||||
|
||||
/*
|
||||
* cerror - returns an error message to the client |
||||
*/ |
||||
void cerror(FILE *stream, char *cause, char *errno,
|
||||
char *shortmsg, char *longmsg) { |
||||
fprintf(stream, "HTTP/1.1 %s %s\n", errno, shortmsg); |
||||
fprintf(stream, "Content-type: text/html\n"); |
||||
fprintf(stream, "\n"); |
||||
fprintf(stream, "<html><title>Tiny Error</title>"); |
||||
fprintf(stream, "<body bgcolor=""ffffff"">\n"); |
||||
fprintf(stream, "%s: %s\n", errno, shortmsg); |
||||
fprintf(stream, "<p>%s: %s\n", longmsg, cause); |
||||
fprintf(stream, "<hr><em>The Tiny Web server</em>\n"); |
||||
} |
||||
|
||||
int main(int argc, char **argv) { |
||||
|
||||
/* variables for connection management */ |
||||
int parentfd; /* parent socket */ |
||||
int childfd; /* child socket */ |
||||
int portno; /* port to listen on */ |
||||
int clientlen; /* byte size of client's address */ |
||||
int optval; /* flag value for setsockopt */ |
||||
struct sockaddr_in serveraddr; /* server's addr */ |
||||
struct sockaddr_in clientaddr; /* client addr */ |
||||
|
||||
/* variables for connection I/O */ |
||||
FILE *stream; /* stream version of childfd */ |
||||
char buf[BUFSIZE]; /* message buffer */ |
||||
char method[BUFSIZE]; /* request method */ |
||||
char uri[BUFSIZE]; /* request uri */ |
||||
char version[BUFSIZE]; /* request method */ |
||||
char filename[BUFSIZE];/* path derived from uri */ |
||||
char filetype[BUFSIZE];/* path derived from uri */ |
||||
char cgiargs[BUFSIZE]; /* cgi argument list */ |
||||
char *p; /* temporary pointer */ |
||||
int is_static; /* static request? */ |
||||
struct stat sbuf; /* file status */ |
||||
int fd; /* static content filedes */ |
||||
int pid; /* process id from fork */ |
||||
int wait_status; /* status from wait */ |
||||
|
||||
/* check command line args */ |
||||
if (argc != 2) { |
||||
fprintf(stderr, "usage: %s <port>\n", argv[0]); |
||||
exit(1); |
||||
} |
||||
portno = atoi(argv[1]); |
||||
|
||||
/* open socket descriptor */ |
||||
parentfd = socket(AF_INET, SOCK_STREAM, 0); |
||||
if (parentfd < 0)
|
||||
error("ERROR opening socket"); |
||||
|
||||
/* allows us to restart server immediately */ |
||||
optval = 1; |
||||
setsockopt(parentfd, SOL_SOCKET, SO_REUSEADDR,
|
||||
(const void *)&optval , sizeof(int)); |
||||
|
||||
/* bind port to socket */ |
||||
bzero((char *) &serveraddr, sizeof(serveraddr)); |
||||
serveraddr.sin_family = AF_INET; |
||||
serveraddr.sin_addr.s_addr = htonl(INADDR_ANY); |
||||
serveraddr.sin_port = htons((unsigned short)portno); |
||||
if (bind(parentfd, (struct sockaddr *) &serveraddr,
|
||||
sizeof(serveraddr)) < 0)
|
||||
error("ERROR on binding"); |
||||
|
||||
/* get us ready to accept connection requests */ |
||||
if (listen(parentfd, 5) < 0) /* allow 5 requests to queue up */
|
||||
error("ERROR on listen"); |
||||
|
||||
/*
|
||||
* main loop: wait for a connection request, parse HTTP, |
||||
* serve requested content, close connection. |
||||
*/ |
||||
clientlen = sizeof(clientaddr); |
||||
while (1) { |
||||
|
||||
/* wait for a connection request */ |
||||
childfd = accept(parentfd, (struct sockaddr *) &clientaddr, &clientlen); |
||||
if (childfd < 0)
|
||||
error("ERROR on accept"); |
||||
|
||||
/* open the child socket descriptor as a stream */ |
||||
if ((stream = fdopen(childfd, "r+")) == NULL) |
||||
error("ERROR on fdopen"); |
||||
|
||||
/* get the HTTP request line */ |
||||
fgets(buf, BUFSIZE, stream); |
||||
printf("%s", buf); |
||||
sscanf(buf, "%s %s %s\n", method, uri, version); |
||||
|
||||
/* tiny only supports the GET method */ |
||||
if (strcasecmp(method, "GET")) { |
||||
cerror(stream, method, "501", "Not Implemented",
|
||||
"Tiny does not implement this method"); |
||||
fclose(stream); |
||||
close(childfd); |
||||
continue; |
||||
} |
||||
|
||||
/* read (and ignore) the HTTP headers */ |
||||
fgets(buf, BUFSIZE, stream); |
||||
printf("%s", buf); |
||||
while(strcmp(buf, "\r\n")) { |
||||
fgets(buf, BUFSIZE, stream); |
||||
printf("%s", buf); |
||||
} |
||||
|
||||
/* parse the uri [crufty] */ |
||||
if (!strstr(uri, "cgi-bin")) { /* static content */ |
||||
is_static = 1; |
||||
strcpy(cgiargs, ""); |
||||
strcpy(filename, "."); |
||||
strcat(filename, uri); |
||||
if (uri[strlen(uri)-1] == '/')
|
||||
strcat(filename, "index.html"); |
||||
} |
||||
else { /* dynamic content */ |
||||
is_static = 0; |
||||
p = index(uri, '?'); |
||||
if (p) { |
||||
strcpy(cgiargs, p+1); |
||||
*p = '\0'; |
||||
} |
||||
else { |
||||
strcpy(cgiargs, ""); |
||||
} |
||||
strcpy(filename, "."); |
||||
strcat(filename, uri); |
||||
} |
||||
|
||||
/* make sure the file exists */ |
||||
if (stat(filename, &sbuf) < 0) { |
||||
cerror(stream, filename, "404", "Not found",
|
||||
"Tiny couldn't find this file"); |
||||
fclose(stream); |
||||
close(childfd); |
||||
continue; |
||||
} |
||||
|
||||
/* serve static content */ |
||||
if (is_static) { |
||||
if (strstr(filename, ".html")) |
||||
strcpy(filetype, "text/html"); |
||||
else if (strstr(filename, ".gif")) |
||||
strcpy(filetype, "image/gif"); |
||||
else if (strstr(filename, ".jpg")) |
||||
strcpy(filetype, "image/jpg"); |
||||
else
|
||||
strcpy(filetype, "text/plain"); |
||||
|
||||
/* print response header */ |
||||
fprintf(stream, "HTTP/1.1 200 OK\n"); |
||||
fprintf(stream, "Server: Tiny Web Server\n"); |
||||
fprintf(stream, "Content-length: %d\n", (int)sbuf.st_size); |
||||
fprintf(stream, "Content-type: %s\n", filetype); |
||||
fprintf(stream, "\r\n");
|
||||
fflush(stream); |
||||
|
||||
/* Use mmap to return arbitrary-sized response body */ |
||||
fd = open(filename, O_RDONLY); |
||||
p = mmap(0, sbuf.st_size, PROT_READ, MAP_PRIVATE, fd, 0); |
||||
fwrite(p, 1, sbuf.st_size, stream); |
||||
munmap(p, sbuf.st_size); |
||||
} |
||||
|
||||
/* serve dynamic content */ |
||||
else { |
||||
/* make sure file is a regular executable file */ |
||||
if (!(S_IFREG & sbuf.st_mode) || !(S_IXUSR & sbuf.st_mode)) { |
||||
cerror(stream, filename, "403", "Forbidden",
|
||||
"You are not allow to access this item"); |
||||
fclose(stream); |
||||
close(childfd); |
||||
continue; |
||||
} |
||||
|
||||
/* a real server would set other CGI environ vars as well*/ |
||||
setenv("QUERY_STRING", cgiargs, 1);
|
||||
|
||||
/* print first part of response header */ |
||||
sprintf(buf, "HTTP/1.1 200 OK\n"); |
||||
write(childfd, buf, strlen(buf)); |
||||
sprintf(buf, "Server: Tiny Web Server\n"); |
||||
write(childfd, buf, strlen(buf)); |
||||
|
||||
/* create and run the child CGI process so that all child
|
||||
output to stdout and stderr goes back to the client via the |
||||
childfd socket descriptor */ |
||||
pid = fork(); |
||||
if (pid < 0) { |
||||
perror("ERROR in fork"); |
||||
exit(1); |
||||
} |
||||
else if (pid > 0) { /* parent process */ |
||||
wait(&wait_status); |
||||
} |
||||
else { /* child process*/ |
||||
close(0); /* close stdin */ |
||||
dup2(childfd, 1); /* map socket to stdout */ |
||||
dup2(childfd, 2); /* map socket to stderr */ |
||||
if (execve(filename, NULL, environ) < 0) { |
||||
perror("ERROR in execve"); |
||||
} |
||||
} |
||||
} |
||||
|
||||
/* clean up */ |
||||
fclose(stream); |
||||
close(childfd); |
||||
|
||||
} |
||||
} |
||||
|
||||
Loading…
Reference in new issue