initial web server skeleton

This commit is contained in:
Remzi Arpaci-Dusseau
2019-02-28 15:02:13 -06:00
parent 18ea4eba7f
commit 324ced7cd6
8 changed files with 580 additions and 0 deletions

View File

@@ -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

View File

@@ -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;
}

View File

@@ -0,0 +1,95 @@
#ifndef __IO_HELPER__
#define __IO_HELPER__
#include <arpa/inet.h>
#include <assert.h>
#include <ctype.h>
#include <errno.h>
#include <fcntl.h>
#include <netdb.h>
#include <netinet/in.h>
#include <setjmp.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <strings.h>
#include <sys/mman.h>
#include <sys/time.h>
#include <sys/socket.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
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__

View File

@@ -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, ""
"<!doctype html>\r\n"
"<head>\r\n"
" <title>OSTEP WebServer Error</title>\r\n"
"</head>\r\n"
"<body>\r\n"
" <h2>%s: %s</h2>\r\n"
" <p>%s: %s</p>\r\n"
"</body>\r\n"
"</html>\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);
}
}

View File

@@ -0,0 +1,5 @@
#ifndef __REQUEST_H__
void request_handle(int fd);
#endif // __REQUEST_H__

View File

@@ -0,0 +1,52 @@
#include <assert.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/time.h>
#include <unistd.h>
#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, "<p>Welcome to the CGI program (%s)</p>\r\n", buf);
sprintf(content, "%s<p>My only purpose is to waste time on the server!</p>\r\n", content);
sprintf(content, "%s<p>I spun for %.2f seconds</p>\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);
}

View File

@@ -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 <host> <port> <filename>\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);
}

View File

@@ -0,0 +1,47 @@
#include <stdio.h>
#include "request.h"
#include "io_helper.h"
char default_root[] = ".";
//
// ./wserver [-d <basedir>] [-p <portnum>]
//
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;
}