diff --git a/concurrency-webserver/src/Makefile b/concurrency-webserver/src/Makefile new file mode 100644 index 0000000..05444a9 --- /dev/null +++ b/concurrency-webserver/src/Makefile @@ -0,0 +1,26 @@ +# An admittedly primitive Makefile +# To compile, type "make" or make "all" +# To remove files, type "make clean" + +CC = gcc +CFLAGS = -Wall +OBJS = wserver.o wclient.o request.o io_helper.o + +.SUFFIXES: .c .o + +all: wserver wclient spin.cgi + +wserver: wserver.o request.o io_helper.o + $(CC) $(CFLAGS) -o wserver wserver.o request.o io_helper.o + +wclient: wclient.o io_helper.o + $(CC) $(CFLAGS) -o wclient wclient.o io_helper.o + +spin.cgi: spin.c + $(CC) $(CFLAGS) -o spin.cgi spin.c + +.c.o: + $(CC) $(CFLAGS) -o $@ -c $< + +clean: + -rm -f $(OBJS) wserver wclient spin.cgi diff --git a/concurrency-webserver/src/io_helper.c b/concurrency-webserver/src/io_helper.c new file mode 100644 index 0000000..623caee --- /dev/null +++ b/concurrency-webserver/src/io_helper.c @@ -0,0 +1,83 @@ +#include "io_helper.h" + +ssize_t readline(int fd, void *buf, size_t maxlen) { + char c; + char *bufp = buf; + int n; + for (n = 0; n < maxlen - 1; n++) { // leave room at end for '\0' + int rc; + if ((rc = read_or_die(fd, &c, 1)) == 1) { + *bufp++ = c; + if (c == '\n') + break; + } else if (rc == 0) { + if (n == 1) + return 0; /* EOF, no data read */ + else + break; /* EOF, some data was read */ + } else + return -1; /* error */ + } + *bufp = '\0'; + return n; +} + + +int open_client_fd(char *hostname, int port) { + int client_fd; + struct hostent *hp; + struct sockaddr_in server_addr; + + if ((client_fd = socket(AF_INET, SOCK_STREAM, 0)) < 0) + return -1; + + // Fill in the server's IP address and port + if ((hp = gethostbyname(hostname)) == NULL) + return -2; // check h_errno for cause of error + bzero((char *) &server_addr, sizeof(server_addr)); + server_addr.sin_family = AF_INET; + bcopy((char *) hp->h_addr, + (char *) &server_addr.sin_addr.s_addr, hp->h_length); + server_addr.sin_port = htons(port); + + // Establish a connection with the server + if (connect(client_fd, (sockaddr_t *) &server_addr, sizeof(server_addr)) < 0) + return -1; + return client_fd; +} + +int open_listen_fd(int port) { + // Create a socket descriptor + int listen_fd; + if ((listen_fd = socket(AF_INET, SOCK_STREAM, 0)) < 0) { + fprintf(stderr, "socket() failed\n"); + return -1; + } + + // Eliminates "Address already in use" error from bind + int optval = 1; + if (setsockopt(listen_fd, SOL_SOCKET, SO_REUSEADDR, (const void *) &optval, sizeof(int)) < 0) { + fprintf(stderr, "setsockopt() failed\n"); + return -1; + } + + // Listen_fd will be an endpoint for all requests to port on any IP address for this host + struct sockaddr_in server_addr; + bzero((char *) &server_addr, sizeof(server_addr)); + server_addr.sin_family = AF_INET; + server_addr.sin_addr.s_addr = htonl(INADDR_ANY); + server_addr.sin_port = htons((unsigned short) port); + if (bind(listen_fd, (sockaddr_t *) &server_addr, sizeof(server_addr)) < 0) { + fprintf(stderr, "bind() failed\n"); + return -1; + } + + // Make it a listening socket ready to accept connection requests + if (listen(listen_fd, 1024) < 0) { + fprintf(stderr, "listen() failed\n"); + return -1; + } + return listen_fd; +} + + diff --git a/concurrency-webserver/src/io_helper.h b/concurrency-webserver/src/io_helper.h new file mode 100644 index 0000000..59275ca --- /dev/null +++ b/concurrency-webserver/src/io_helper.h @@ -0,0 +1,95 @@ +#ifndef __IO_HELPER__ +#define __IO_HELPER__ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +typedef struct sockaddr sockaddr_t; + +// useful here: gcc statement expressions +// http://gcc.gnu.org/onlinedocs/gcc/Statement-Exprs.html +// macro ({ ...; x; }) returns value 'x' for caller +// e.g., macro 'fork_or_die()' below returns 'pid' value +#define fork_or_die() \ + ({ pid_t pid = fork(); assert(pid >= 0); pid; }) +#define execve_or_die(filename, argv, envp) \ + assert(execve(filename, argv, envp) == 0); +#define wait_or_die(status) \ + ({ pid_t pid = wait(status); assert(pid >= 0); pid; }) +#define gethostname_or_die(name, len) \ + ({ int rc = gethostname(name, len); assert(rc == 0); rc; }) +#define setenv_or_die(name, value, overwrite) \ + ({ int rc = setenv(name, value, overwrite); assert(rc == 0); rc; }) +#define chdir_or_die(path) \ + assert(chdir(path) == 0); +#define open_or_die(pathname, flags, mode) \ + ({ int rc = open(pathname, flags, mode); assert(rc >= 0); rc; }) +#define read_or_die(fd, buf, count) \ + ({ ssize_t rc = read(fd, buf, count); assert(rc >= 0); rc; }) +#define write_or_die(fd, buf, count) \ + ({ ssize_t rc = write(fd, buf, count); assert(rc >= 0); rc; }) +#define lseek_or_die(fd, offset, whence) \ + ({ off_t rc = lseek(fd, offset, whence); assert(rc >= 0); rc; }) +#define close_or_die(fd) \ + assert(close(fd) == 0); +#define select_or_die(n, readfds, writefds, exceptfds, timeout) \ + ({ int rc = select(n, readfds, writefds, exceptfds, timeout); assert(rc >= 0); rc; }) +#define dup2_or_die(fd1, fd2) \ + ({ int rc = dup2(fd1, fd2); assert(rc >= 0); rc; }) +#define stat_or_die(filename, buf) \ + assert(stat(filename, buf) >= 0); +#define fstat_or_die(fd, buf) \ + { assert(fstat(fd, buf) >= 0); } +#define mmap_or_die(addr, len, prot, flags, fd, offset) \ + ({ void *ptr = mmap(addr, len, prot, flags, fd, offset); assert(ptr != (void *) -1); ptr; }) +#define munmap_or_die(start, length) \ + assert(munmap(start, length) >= 0); +#define socket_or_die(domain, type, protocol) \ + ({ int rc = socket(domain, type, protocol); assert(rc >= 0); rc; }) +#define setsockopt_or_die(s, level, optname, optval, optlen) \ + { assert(setsockopt(s, level, optname, optval, optlen) >= 0); } +#define bind_or_die(sockfd, my_addr, addrlen) \ + { assert(bind(sockfd, my_addr, addrlen) >= 0); } +#define listen_or_die(s, backlog) \ + { assert(listen(s, backlog) >= 0); } +#define accept_or_die(s, addr, addrlen) \ + ({ int rc = accept(s, addr, addrlen); assert(rc >= 0); rc; }) +#define connect_or_die(sockfd, serv_addr, addrlen) \ + { assert(connect(sockfd, serv_addr, addrlen) >= 0); } +#define gethostbyname_or_die(name) \ + ({ struct hostent *p = gethostbyname(name); assert(p != NULL); p; }) +#define gethostbyaddr_or_die(addr, len, type) \ + ({ struct hostent *p = gethostbyaddr(addr, len, type); assert(p != NULL); p; }) + +// client/server helper functions +ssize_t readline(int fd, void *buf, size_t maxlen); +int open_client_fd(char *hostname, int portno); +int open_listen_fd(int portno); + +// wrappers for above +#define readline_or_die(fd, buf, maxlen) \ + ({ ssize_t rc = readline(fd, buf, maxlen); assert(rc >= 0); rc; }) +#define open_client_fd_or_die(hostname, port) \ + ({ int rc = open_client_fd(hostname, port); assert(rc >= 0); rc; }) +#define open_listen_fd_or_die(port) \ + ({ int rc = open_listen_fd(port); assert(rc >= 0); rc; }) + +#endif // __IO_HELPER__ diff --git a/concurrency-webserver/src/request.c b/concurrency-webserver/src/request.c new file mode 100644 index 0000000..b5b9ce4 --- /dev/null +++ b/concurrency-webserver/src/request.c @@ -0,0 +1,180 @@ +#include "io_helper.h" +#include "request.h" + +// +// Some of this code stolen from Bryant/O'Halloran +// Hopefully this is not a problem ... :) +// + +#define MAXBUF (8192) + +void request_error(int fd, char *cause, char *errnum, char *shortmsg, char *longmsg) { + char buf[MAXBUF], body[MAXBUF]; + + // Create the body of error message first (have to know its length for header) + sprintf(body, "" + "\r\n" + "\r\n" + " OSTEP WebServer Error\r\n" + "\r\n" + "\r\n" + "

%s: %s

\r\n" + "

%s: %s

\r\n" + "\r\n" + "\r\n", errnum, shortmsg, longmsg, cause); + + // Write out the header information for this response + sprintf(buf, "HTTP/1.0 %s %s\r\n", errnum, shortmsg); + write_or_die(fd, buf, strlen(buf)); + + sprintf(buf, "Content-Type: text/html\r\n"); + write_or_die(fd, buf, strlen(buf)); + + sprintf(buf, "Content-Length: %lu\r\n\r\n", strlen(body)); + write_or_die(fd, buf, strlen(buf)); + + // Write out the body last + write_or_die(fd, body, strlen(body)); +} + +// +// Reads and discards everything up to an empty text line +// +void request_read_headers(int fd) { + char buf[MAXBUF]; + + readline_or_die(fd, buf, MAXBUF); + while (strcmp(buf, "\r\n")) { + readline_or_die(fd, buf, MAXBUF); + } + return; +} + +// +// Return 1 if static, 0 if dynamic content +// Calculates filename (and cgiargs, for dynamic) from uri +// +int request_parse_uri(char *uri, char *filename, char *cgiargs) { + char *ptr; + + if (!strstr(uri, "cgi")) { + // static + strcpy(cgiargs, ""); + sprintf(filename, ".%s", uri); + if (uri[strlen(uri)-1] == '/') { + strcat(filename, "index.html"); + } + return 1; + } else { + // dynamic + ptr = index(uri, '?'); + if (ptr) { + strcpy(cgiargs, ptr+1); + *ptr = '\0'; + } else { + strcpy(cgiargs, ""); + } + sprintf(filename, ".%s", uri); + return 0; + } +} + +// +// Fills in the filetype given the filename +// +void request_get_filetype(char *filename, char *filetype) { + 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/jpeg"); + else + strcpy(filetype, "text/plain"); +} + +void request_serve_dynamic(int fd, char *filename, char *cgiargs) { + char buf[MAXBUF], *argv[] = { NULL }; + + // The server does only a little bit of the header. + // The CGI script has to finish writing out the header. + sprintf(buf, "" + "HTTP/1.0 200 OK\r\n" + "Server: OSTEP WebServer\r\n"); + + write_or_die(fd, buf, strlen(buf)); + + if (fork_or_die() == 0) { // child + setenv_or_die("QUERY_STRING", cgiargs, 1); // args to cgi go here + dup2_or_die(fd, STDOUT_FILENO); // make cgi writes go to socket (not screen) + extern char **environ; // defined by libc + execve_or_die(filename, argv, environ); + } else { + wait_or_die(NULL); + } +} + +void request_serve_static(int fd, char *filename, int filesize) { + int srcfd; + char *srcp, filetype[MAXBUF], buf[MAXBUF]; + + request_get_filetype(filename, filetype); + srcfd = open_or_die(filename, O_RDONLY, 0); + + // Rather than call read() to read the file into memory, + // which would require that we allocate a buffer, we memory-map the file + srcp = mmap_or_die(0, filesize, PROT_READ, MAP_PRIVATE, srcfd, 0); + close_or_die(srcfd); + + // put together response + sprintf(buf, "" + "HTTP/1.0 200 OK\r\n" + "Server: OSTEP WebServer\r\n" + "Content-Length: %d\r\n" + "Content-Type: %s\r\n\r\n", + filesize, filetype); + + write_or_die(fd, buf, strlen(buf)); + + // Writes out to the client socket the memory-mapped file + write_or_die(fd, srcp, filesize); + munmap_or_die(srcp, filesize); +} + +// handle a request +void request_handle(int fd) { + int is_static; + struct stat sbuf; + char buf[MAXBUF], method[MAXBUF], uri[MAXBUF], version[MAXBUF]; + char filename[MAXBUF], cgiargs[MAXBUF]; + + readline_or_die(fd, buf, MAXBUF); + sscanf(buf, "%s %s %s", method, uri, version); + printf("method:%s uri:%s version:%s\n", method, uri, version); + + if (strcasecmp(method, "GET")) { + request_error(fd, method, "501", "Not Implemented", "server does not implement this method"); + return; + } + request_read_headers(fd); + + is_static = request_parse_uri(uri, filename, cgiargs); + if (stat(filename, &sbuf) < 0) { + request_error(fd, filename, "404", "Not found", "server could not find this file"); + return; + } + + if (is_static) { + if (!(S_ISREG(sbuf.st_mode)) || !(S_IRUSR & sbuf.st_mode)) { + request_error(fd, filename, "403", "Forbidden", "server could not read this file"); + return; + } + request_serve_static(fd, filename, sbuf.st_size); + } else { + if (!(S_ISREG(sbuf.st_mode)) || !(S_IXUSR & sbuf.st_mode)) { + request_error(fd, filename, "403", "Forbidden", "server could not run this CGI program"); + return; + } + request_serve_dynamic(fd, filename, cgiargs); + } +} diff --git a/concurrency-webserver/src/request.h b/concurrency-webserver/src/request.h new file mode 100644 index 0000000..65d0422 --- /dev/null +++ b/concurrency-webserver/src/request.h @@ -0,0 +1,5 @@ +#ifndef __REQUEST_H__ + +void request_handle(int fd); + +#endif // __REQUEST_H__ diff --git a/concurrency-webserver/src/spin.c b/concurrency-webserver/src/spin.c new file mode 100644 index 0000000..7f04d6b --- /dev/null +++ b/concurrency-webserver/src/spin.c @@ -0,0 +1,52 @@ +#include +#include +#include +#include +#include +#include + +#define MAXBUF (8192) + +// +// This program is intended to help you test your web server. +// You can use it to test that you are correctly having multiple threads +// handling http requests. +// + +double get_seconds() { + struct timeval t; + int rc = gettimeofday(&t, NULL); + assert(rc == 0); + return (double) ((double)t.tv_sec + (double)t.tv_usec / 1e6); +} + + +int main(int argc, char *argv[]) { + // Extract arguments + double spin_for = 0.0; + char *buf; + if ((buf = getenv("QUERY_STRING")) != NULL) { + // just expecting a single number + spin_for = (double) atoi(buf); + } + + double t1 = get_seconds(); + while ((get_seconds() - t1) < spin_for) + sleep(1); + double t2 = get_seconds(); + + /* Make the response body */ + char content[MAXBUF]; + sprintf(content, "

Welcome to the CGI program (%s)

\r\n", buf); + sprintf(content, "%s

My only purpose is to waste time on the server!

\r\n", content); + sprintf(content, "%s

I spun for %.2f seconds

\r\n", content, t2 - t1); + + /* Generate the HTTP response */ + printf("Content-length: %lu\r\n", strlen(content)); + printf("Content-type: text/html\r\n\r\n"); + printf("%s", content); + fflush(stdout); + + exit(0); +} + diff --git a/concurrency-webserver/src/wclient.c b/concurrency-webserver/src/wclient.c new file mode 100644 index 0000000..ee39777 --- /dev/null +++ b/concurrency-webserver/src/wclient.c @@ -0,0 +1,92 @@ +// +// client.c: A very, very primitive HTTP client. +// +// To run, try: +// client hostname portnumber filename +// +// Sends one HTTP request to the specified HTTP server. +// Prints out the HTTP response. +// +// For testing your server, you will want to modify this client. +// For example: +// You may want to make this multi-threaded so that you can +// send many requests simultaneously to the server. +// +// You may also want to be able to request different URIs; +// you may want to get more URIs from the command line +// or read the list from a file. +// +// When we test your server, we will be using modifications to this client. +// + +#include "io_helper.h" + +#define MAXBUF (8192) + +// +// Send an HTTP request for the specified file +// +void client_send(int fd, char *filename) { + char buf[MAXBUF]; + char hostname[MAXBUF]; + + gethostname_or_die(hostname, MAXBUF); + + /* Form and send the HTTP request */ + sprintf(buf, "GET %s HTTP/1.1\n", filename); + sprintf(buf, "%shost: %s\n\r\n", buf, hostname); + write_or_die(fd, buf, strlen(buf)); +} + +// +// Read the HTTP response and print it out +// +void client_print(int fd) { + char buf[MAXBUF]; + int n; + + // Read and display the HTTP Header + n = readline_or_die(fd, buf, MAXBUF); + while (strcmp(buf, "\r\n") && (n > 0)) { + printf("Header: %s", buf); + n = readline_or_die(fd, buf, MAXBUF); + + // If you want to look for certain HTTP tags... + // int length = 0; + //if (sscanf(buf, "Content-Length: %d ", &length) == 1) { + // printf("Length = %d\n", length); + //} + } + + // Read and display the HTTP Body + n = readline_or_die(fd, buf, MAXBUF); + while (n > 0) { + printf("%s", buf); + n = readline_or_die(fd, buf, MAXBUF); + } +} + +int main(int argc, char *argv[]) { + char *host, *filename; + int port; + int clientfd; + + if (argc != 4) { + fprintf(stderr, "Usage: %s \n", argv[0]); + exit(1); + } + + host = argv[1]; + port = atoi(argv[2]); + filename = argv[3]; + + /* Open a single connection to the specified host and port */ + clientfd = open_client_fd_or_die(host, port); + + client_send(clientfd, filename); + client_print(clientfd); + + close_or_die(clientfd); + + exit(0); +} diff --git a/concurrency-webserver/src/wserver.c b/concurrency-webserver/src/wserver.c new file mode 100644 index 0000000..188abad --- /dev/null +++ b/concurrency-webserver/src/wserver.c @@ -0,0 +1,47 @@ +#include +#include "request.h" +#include "io_helper.h" + +char default_root[] = "."; + +// +// ./wserver [-d ] [-p ] +// +int main(int argc, char *argv[]) { + int c; + char *root_dir = default_root; + int port = 10000; + + while ((c = getopt(argc, argv, "d:p:")) != -1) + switch (c) { + case 'd': + root_dir = optarg; + break; + case 'p': + port = atoi(optarg); + break; + default: + fprintf(stderr, "usage: wserver [-d basedir] [-p port]\n"); + exit(1); + } + + // run out of this directory + chdir_or_die(root_dir); + + // now, get to work + int listen_fd = open_listen_fd_or_die(port); + while (1) { + struct sockaddr_in client_addr; + int client_len = sizeof(client_addr); + int conn_fd = accept_or_die(listen_fd, (sockaddr_t *) &client_addr, (socklen_t *) &client_len); + request_handle(conn_fd); + close_or_die(conn_fd); + } + return 0; +} + + + + + +