Skip to content

Commit 324ced7

Browse files
committedFeb 28, 2019
initial web server skeleton
1 parent 18ea4eb commit 324ced7

File tree

8 files changed

+580
-0
lines changed

8 files changed

+580
-0
lines changed
 

‎concurrency-webserver/src/Makefile

+26
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
# An admittedly primitive Makefile
2+
# To compile, type "make" or make "all"
3+
# To remove files, type "make clean"
4+
5+
CC = gcc
6+
CFLAGS = -Wall
7+
OBJS = wserver.o wclient.o request.o io_helper.o
8+
9+
.SUFFIXES: .c .o
10+
11+
all: wserver wclient spin.cgi
12+
13+
wserver: wserver.o request.o io_helper.o
14+
$(CC) $(CFLAGS) -o wserver wserver.o request.o io_helper.o
15+
16+
wclient: wclient.o io_helper.o
17+
$(CC) $(CFLAGS) -o wclient wclient.o io_helper.o
18+
19+
spin.cgi: spin.c
20+
$(CC) $(CFLAGS) -o spin.cgi spin.c
21+
22+
.c.o:
23+
$(CC) $(CFLAGS) -o $@ -c $<
24+
25+
clean:
26+
-rm -f $(OBJS) wserver wclient spin.cgi

‎concurrency-webserver/src/io_helper.c

+83
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
#include "io_helper.h"
2+
3+
ssize_t readline(int fd, void *buf, size_t maxlen) {
4+
char c;
5+
char *bufp = buf;
6+
int n;
7+
for (n = 0; n < maxlen - 1; n++) { // leave room at end for '\0'
8+
int rc;
9+
if ((rc = read_or_die(fd, &c, 1)) == 1) {
10+
*bufp++ = c;
11+
if (c == '\n')
12+
break;
13+
} else if (rc == 0) {
14+
if (n == 1)
15+
return 0; /* EOF, no data read */
16+
else
17+
break; /* EOF, some data was read */
18+
} else
19+
return -1; /* error */
20+
}
21+
*bufp = '\0';
22+
return n;
23+
}
24+
25+
26+
int open_client_fd(char *hostname, int port) {
27+
int client_fd;
28+
struct hostent *hp;
29+
struct sockaddr_in server_addr;
30+
31+
if ((client_fd = socket(AF_INET, SOCK_STREAM, 0)) < 0)
32+
return -1;
33+
34+
// Fill in the server's IP address and port
35+
if ((hp = gethostbyname(hostname)) == NULL)
36+
return -2; // check h_errno for cause of error
37+
bzero((char *) &server_addr, sizeof(server_addr));
38+
server_addr.sin_family = AF_INET;
39+
bcopy((char *) hp->h_addr,
40+
(char *) &server_addr.sin_addr.s_addr, hp->h_length);
41+
server_addr.sin_port = htons(port);
42+
43+
// Establish a connection with the server
44+
if (connect(client_fd, (sockaddr_t *) &server_addr, sizeof(server_addr)) < 0)
45+
return -1;
46+
return client_fd;
47+
}
48+
49+
int open_listen_fd(int port) {
50+
// Create a socket descriptor
51+
int listen_fd;
52+
if ((listen_fd = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
53+
fprintf(stderr, "socket() failed\n");
54+
return -1;
55+
}
56+
57+
// Eliminates "Address already in use" error from bind
58+
int optval = 1;
59+
if (setsockopt(listen_fd, SOL_SOCKET, SO_REUSEADDR, (const void *) &optval, sizeof(int)) < 0) {
60+
fprintf(stderr, "setsockopt() failed\n");
61+
return -1;
62+
}
63+
64+
// Listen_fd will be an endpoint for all requests to port on any IP address for this host
65+
struct sockaddr_in server_addr;
66+
bzero((char *) &server_addr, sizeof(server_addr));
67+
server_addr.sin_family = AF_INET;
68+
server_addr.sin_addr.s_addr = htonl(INADDR_ANY);
69+
server_addr.sin_port = htons((unsigned short) port);
70+
if (bind(listen_fd, (sockaddr_t *) &server_addr, sizeof(server_addr)) < 0) {
71+
fprintf(stderr, "bind() failed\n");
72+
return -1;
73+
}
74+
75+
// Make it a listening socket ready to accept connection requests
76+
if (listen(listen_fd, 1024) < 0) {
77+
fprintf(stderr, "listen() failed\n");
78+
return -1;
79+
}
80+
return listen_fd;
81+
}
82+
83+

‎concurrency-webserver/src/io_helper.h

+95
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
#ifndef __IO_HELPER__
2+
#define __IO_HELPER__
3+
4+
#include <arpa/inet.h>
5+
#include <assert.h>
6+
#include <ctype.h>
7+
#include <errno.h>
8+
#include <fcntl.h>
9+
#include <netdb.h>
10+
#include <netinet/in.h>
11+
#include <setjmp.h>
12+
#include <signal.h>
13+
#include <stdio.h>
14+
#include <stdlib.h>
15+
#include <string.h>
16+
#include <strings.h>
17+
#include <sys/mman.h>
18+
#include <sys/time.h>
19+
#include <sys/socket.h>
20+
#include <sys/stat.h>
21+
#include <sys/types.h>
22+
#include <sys/wait.h>
23+
#include <unistd.h>
24+
25+
typedef struct sockaddr sockaddr_t;
26+
27+
// useful here: gcc statement expressions
28+
// http://gcc.gnu.org/onlinedocs/gcc/Statement-Exprs.html
29+
// macro ({ ...; x; }) returns value 'x' for caller
30+
// e.g., macro 'fork_or_die()' below returns 'pid' value
31+
#define fork_or_die() \
32+
({ pid_t pid = fork(); assert(pid >= 0); pid; })
33+
#define execve_or_die(filename, argv, envp) \
34+
assert(execve(filename, argv, envp) == 0);
35+
#define wait_or_die(status) \
36+
({ pid_t pid = wait(status); assert(pid >= 0); pid; })
37+
#define gethostname_or_die(name, len) \
38+
({ int rc = gethostname(name, len); assert(rc == 0); rc; })
39+
#define setenv_or_die(name, value, overwrite) \
40+
({ int rc = setenv(name, value, overwrite); assert(rc == 0); rc; })
41+
#define chdir_or_die(path) \
42+
assert(chdir(path) == 0);
43+
#define open_or_die(pathname, flags, mode) \
44+
({ int rc = open(pathname, flags, mode); assert(rc >= 0); rc; })
45+
#define read_or_die(fd, buf, count) \
46+
({ ssize_t rc = read(fd, buf, count); assert(rc >= 0); rc; })
47+
#define write_or_die(fd, buf, count) \
48+
({ ssize_t rc = write(fd, buf, count); assert(rc >= 0); rc; })
49+
#define lseek_or_die(fd, offset, whence) \
50+
({ off_t rc = lseek(fd, offset, whence); assert(rc >= 0); rc; })
51+
#define close_or_die(fd) \
52+
assert(close(fd) == 0);
53+
#define select_or_die(n, readfds, writefds, exceptfds, timeout) \
54+
({ int rc = select(n, readfds, writefds, exceptfds, timeout); assert(rc >= 0); rc; })
55+
#define dup2_or_die(fd1, fd2) \
56+
({ int rc = dup2(fd1, fd2); assert(rc >= 0); rc; })
57+
#define stat_or_die(filename, buf) \
58+
assert(stat(filename, buf) >= 0);
59+
#define fstat_or_die(fd, buf) \
60+
{ assert(fstat(fd, buf) >= 0); }
61+
#define mmap_or_die(addr, len, prot, flags, fd, offset) \
62+
({ void *ptr = mmap(addr, len, prot, flags, fd, offset); assert(ptr != (void *) -1); ptr; })
63+
#define munmap_or_die(start, length) \
64+
assert(munmap(start, length) >= 0);
65+
#define socket_or_die(domain, type, protocol) \
66+
({ int rc = socket(domain, type, protocol); assert(rc >= 0); rc; })
67+
#define setsockopt_or_die(s, level, optname, optval, optlen) \
68+
{ assert(setsockopt(s, level, optname, optval, optlen) >= 0); }
69+
#define bind_or_die(sockfd, my_addr, addrlen) \
70+
{ assert(bind(sockfd, my_addr, addrlen) >= 0); }
71+
#define listen_or_die(s, backlog) \
72+
{ assert(listen(s, backlog) >= 0); }
73+
#define accept_or_die(s, addr, addrlen) \
74+
({ int rc = accept(s, addr, addrlen); assert(rc >= 0); rc; })
75+
#define connect_or_die(sockfd, serv_addr, addrlen) \
76+
{ assert(connect(sockfd, serv_addr, addrlen) >= 0); }
77+
#define gethostbyname_or_die(name) \
78+
({ struct hostent *p = gethostbyname(name); assert(p != NULL); p; })
79+
#define gethostbyaddr_or_die(addr, len, type) \
80+
({ struct hostent *p = gethostbyaddr(addr, len, type); assert(p != NULL); p; })
81+
82+
// client/server helper functions
83+
ssize_t readline(int fd, void *buf, size_t maxlen);
84+
int open_client_fd(char *hostname, int portno);
85+
int open_listen_fd(int portno);
86+
87+
// wrappers for above
88+
#define readline_or_die(fd, buf, maxlen) \
89+
({ ssize_t rc = readline(fd, buf, maxlen); assert(rc >= 0); rc; })
90+
#define open_client_fd_or_die(hostname, port) \
91+
({ int rc = open_client_fd(hostname, port); assert(rc >= 0); rc; })
92+
#define open_listen_fd_or_die(port) \
93+
({ int rc = open_listen_fd(port); assert(rc >= 0); rc; })
94+
95+
#endif // __IO_HELPER__

‎concurrency-webserver/src/request.c

+180
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,180 @@
1+
#include "io_helper.h"
2+
#include "request.h"
3+
4+
//
5+
// Some of this code stolen from Bryant/O'Halloran
6+
// Hopefully this is not a problem ... :)
7+
//
8+
9+
#define MAXBUF (8192)
10+
11+
void request_error(int fd, char *cause, char *errnum, char *shortmsg, char *longmsg) {
12+
char buf[MAXBUF], body[MAXBUF];
13+
14+
// Create the body of error message first (have to know its length for header)
15+
sprintf(body, ""
16+
"<!doctype html>\r\n"
17+
"<head>\r\n"
18+
" <title>OSTEP WebServer Error</title>\r\n"
19+
"</head>\r\n"
20+
"<body>\r\n"
21+
" <h2>%s: %s</h2>\r\n"
22+
" <p>%s: %s</p>\r\n"
23+
"</body>\r\n"
24+
"</html>\r\n", errnum, shortmsg, longmsg, cause);
25+
26+
// Write out the header information for this response
27+
sprintf(buf, "HTTP/1.0 %s %s\r\n", errnum, shortmsg);
28+
write_or_die(fd, buf, strlen(buf));
29+
30+
sprintf(buf, "Content-Type: text/html\r\n");
31+
write_or_die(fd, buf, strlen(buf));
32+
33+
sprintf(buf, "Content-Length: %lu\r\n\r\n", strlen(body));
34+
write_or_die(fd, buf, strlen(buf));
35+
36+
// Write out the body last
37+
write_or_die(fd, body, strlen(body));
38+
}
39+
40+
//
41+
// Reads and discards everything up to an empty text line
42+
//
43+
void request_read_headers(int fd) {
44+
char buf[MAXBUF];
45+
46+
readline_or_die(fd, buf, MAXBUF);
47+
while (strcmp(buf, "\r\n")) {
48+
readline_or_die(fd, buf, MAXBUF);
49+
}
50+
return;
51+
}
52+
53+
//
54+
// Return 1 if static, 0 if dynamic content
55+
// Calculates filename (and cgiargs, for dynamic) from uri
56+
//
57+
int request_parse_uri(char *uri, char *filename, char *cgiargs) {
58+
char *ptr;
59+
60+
if (!strstr(uri, "cgi")) {
61+
// static
62+
strcpy(cgiargs, "");
63+
sprintf(filename, ".%s", uri);
64+
if (uri[strlen(uri)-1] == '/') {
65+
strcat(filename, "index.html");
66+
}
67+
return 1;
68+
} else {
69+
// dynamic
70+
ptr = index(uri, '?');
71+
if (ptr) {
72+
strcpy(cgiargs, ptr+1);
73+
*ptr = '\0';
74+
} else {
75+
strcpy(cgiargs, "");
76+
}
77+
sprintf(filename, ".%s", uri);
78+
return 0;
79+
}
80+
}
81+
82+
//
83+
// Fills in the filetype given the filename
84+
//
85+
void request_get_filetype(char *filename, char *filetype) {
86+
if (strstr(filename, ".html"))
87+
strcpy(filetype, "text/html");
88+
else if (strstr(filename, ".gif"))
89+
strcpy(filetype, "image/gif");
90+
else if (strstr(filename, ".jpg"))
91+
strcpy(filetype, "image/jpeg");
92+
else
93+
strcpy(filetype, "text/plain");
94+
}
95+
96+
void request_serve_dynamic(int fd, char *filename, char *cgiargs) {
97+
char buf[MAXBUF], *argv[] = { NULL };
98+
99+
// The server does only a little bit of the header.
100+
// The CGI script has to finish writing out the header.
101+
sprintf(buf, ""
102+
"HTTP/1.0 200 OK\r\n"
103+
"Server: OSTEP WebServer\r\n");
104+
105+
write_or_die(fd, buf, strlen(buf));
106+
107+
if (fork_or_die() == 0) { // child
108+
setenv_or_die("QUERY_STRING", cgiargs, 1); // args to cgi go here
109+
dup2_or_die(fd, STDOUT_FILENO); // make cgi writes go to socket (not screen)
110+
extern char **environ; // defined by libc
111+
execve_or_die(filename, argv, environ);
112+
} else {
113+
wait_or_die(NULL);
114+
}
115+
}
116+
117+
void request_serve_static(int fd, char *filename, int filesize) {
118+
int srcfd;
119+
char *srcp, filetype[MAXBUF], buf[MAXBUF];
120+
121+
request_get_filetype(filename, filetype);
122+
srcfd = open_or_die(filename, O_RDONLY, 0);
123+
124+
// Rather than call read() to read the file into memory,
125+
// which would require that we allocate a buffer, we memory-map the file
126+
srcp = mmap_or_die(0, filesize, PROT_READ, MAP_PRIVATE, srcfd, 0);
127+
close_or_die(srcfd);
128+
129+
// put together response
130+
sprintf(buf, ""
131+
"HTTP/1.0 200 OK\r\n"
132+
"Server: OSTEP WebServer\r\n"
133+
"Content-Length: %d\r\n"
134+
"Content-Type: %s\r\n\r\n",
135+
filesize, filetype);
136+
137+
write_or_die(fd, buf, strlen(buf));
138+
139+
// Writes out to the client socket the memory-mapped file
140+
write_or_die(fd, srcp, filesize);
141+
munmap_or_die(srcp, filesize);
142+
}
143+
144+
// handle a request
145+
void request_handle(int fd) {
146+
int is_static;
147+
struct stat sbuf;
148+
char buf[MAXBUF], method[MAXBUF], uri[MAXBUF], version[MAXBUF];
149+
char filename[MAXBUF], cgiargs[MAXBUF];
150+
151+
readline_or_die(fd, buf, MAXBUF);
152+
sscanf(buf, "%s %s %s", method, uri, version);
153+
printf("method:%s uri:%s version:%s\n", method, uri, version);
154+
155+
if (strcasecmp(method, "GET")) {
156+
request_error(fd, method, "501", "Not Implemented", "server does not implement this method");
157+
return;
158+
}
159+
request_read_headers(fd);
160+
161+
is_static = request_parse_uri(uri, filename, cgiargs);
162+
if (stat(filename, &sbuf) < 0) {
163+
request_error(fd, filename, "404", "Not found", "server could not find this file");
164+
return;
165+
}
166+
167+
if (is_static) {
168+
if (!(S_ISREG(sbuf.st_mode)) || !(S_IRUSR & sbuf.st_mode)) {
169+
request_error(fd, filename, "403", "Forbidden", "server could not read this file");
170+
return;
171+
}
172+
request_serve_static(fd, filename, sbuf.st_size);
173+
} else {
174+
if (!(S_ISREG(sbuf.st_mode)) || !(S_IXUSR & sbuf.st_mode)) {
175+
request_error(fd, filename, "403", "Forbidden", "server could not run this CGI program");
176+
return;
177+
}
178+
request_serve_dynamic(fd, filename, cgiargs);
179+
}
180+
}

‎concurrency-webserver/src/request.h

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
#ifndef __REQUEST_H__
2+
3+
void request_handle(int fd);
4+
5+
#endif // __REQUEST_H__

‎concurrency-webserver/src/spin.c

+52
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
#include <assert.h>
2+
#include <stdio.h>
3+
#include <stdlib.h>
4+
#include <string.h>
5+
#include <sys/time.h>
6+
#include <unistd.h>
7+
8+
#define MAXBUF (8192)
9+
10+
//
11+
// This program is intended to help you test your web server.
12+
// You can use it to test that you are correctly having multiple threads
13+
// handling http requests.
14+
//
15+
16+
double get_seconds() {
17+
struct timeval t;
18+
int rc = gettimeofday(&t, NULL);
19+
assert(rc == 0);
20+
return (double) ((double)t.tv_sec + (double)t.tv_usec / 1e6);
21+
}
22+
23+
24+
int main(int argc, char *argv[]) {
25+
// Extract arguments
26+
double spin_for = 0.0;
27+
char *buf;
28+
if ((buf = getenv("QUERY_STRING")) != NULL) {
29+
// just expecting a single number
30+
spin_for = (double) atoi(buf);
31+
}
32+
33+
double t1 = get_seconds();
34+
while ((get_seconds() - t1) < spin_for)
35+
sleep(1);
36+
double t2 = get_seconds();
37+
38+
/* Make the response body */
39+
char content[MAXBUF];
40+
sprintf(content, "<p>Welcome to the CGI program (%s)</p>\r\n", buf);
41+
sprintf(content, "%s<p>My only purpose is to waste time on the server!</p>\r\n", content);
42+
sprintf(content, "%s<p>I spun for %.2f seconds</p>\r\n", content, t2 - t1);
43+
44+
/* Generate the HTTP response */
45+
printf("Content-length: %lu\r\n", strlen(content));
46+
printf("Content-type: text/html\r\n\r\n");
47+
printf("%s", content);
48+
fflush(stdout);
49+
50+
exit(0);
51+
}
52+

‎concurrency-webserver/src/wclient.c

+92
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
//
2+
// client.c: A very, very primitive HTTP client.
3+
//
4+
// To run, try:
5+
// client hostname portnumber filename
6+
//
7+
// Sends one HTTP request to the specified HTTP server.
8+
// Prints out the HTTP response.
9+
//
10+
// For testing your server, you will want to modify this client.
11+
// For example:
12+
// You may want to make this multi-threaded so that you can
13+
// send many requests simultaneously to the server.
14+
//
15+
// You may also want to be able to request different URIs;
16+
// you may want to get more URIs from the command line
17+
// or read the list from a file.
18+
//
19+
// When we test your server, we will be using modifications to this client.
20+
//
21+
22+
#include "io_helper.h"
23+
24+
#define MAXBUF (8192)
25+
26+
//
27+
// Send an HTTP request for the specified file
28+
//
29+
void client_send(int fd, char *filename) {
30+
char buf[MAXBUF];
31+
char hostname[MAXBUF];
32+
33+
gethostname_or_die(hostname, MAXBUF);
34+
35+
/* Form and send the HTTP request */
36+
sprintf(buf, "GET %s HTTP/1.1\n", filename);
37+
sprintf(buf, "%shost: %s\n\r\n", buf, hostname);
38+
write_or_die(fd, buf, strlen(buf));
39+
}
40+
41+
//
42+
// Read the HTTP response and print it out
43+
//
44+
void client_print(int fd) {
45+
char buf[MAXBUF];
46+
int n;
47+
48+
// Read and display the HTTP Header
49+
n = readline_or_die(fd, buf, MAXBUF);
50+
while (strcmp(buf, "\r\n") && (n > 0)) {
51+
printf("Header: %s", buf);
52+
n = readline_or_die(fd, buf, MAXBUF);
53+
54+
// If you want to look for certain HTTP tags...
55+
// int length = 0;
56+
//if (sscanf(buf, "Content-Length: %d ", &length) == 1) {
57+
// printf("Length = %d\n", length);
58+
//}
59+
}
60+
61+
// Read and display the HTTP Body
62+
n = readline_or_die(fd, buf, MAXBUF);
63+
while (n > 0) {
64+
printf("%s", buf);
65+
n = readline_or_die(fd, buf, MAXBUF);
66+
}
67+
}
68+
69+
int main(int argc, char *argv[]) {
70+
char *host, *filename;
71+
int port;
72+
int clientfd;
73+
74+
if (argc != 4) {
75+
fprintf(stderr, "Usage: %s <host> <port> <filename>\n", argv[0]);
76+
exit(1);
77+
}
78+
79+
host = argv[1];
80+
port = atoi(argv[2]);
81+
filename = argv[3];
82+
83+
/* Open a single connection to the specified host and port */
84+
clientfd = open_client_fd_or_die(host, port);
85+
86+
client_send(clientfd, filename);
87+
client_print(clientfd);
88+
89+
close_or_die(clientfd);
90+
91+
exit(0);
92+
}

‎concurrency-webserver/src/wserver.c

+47
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
#include <stdio.h>
2+
#include "request.h"
3+
#include "io_helper.h"
4+
5+
char default_root[] = ".";
6+
7+
//
8+
// ./wserver [-d <basedir>] [-p <portnum>]
9+
//
10+
int main(int argc, char *argv[]) {
11+
int c;
12+
char *root_dir = default_root;
13+
int port = 10000;
14+
15+
while ((c = getopt(argc, argv, "d:p:")) != -1)
16+
switch (c) {
17+
case 'd':
18+
root_dir = optarg;
19+
break;
20+
case 'p':
21+
port = atoi(optarg);
22+
break;
23+
default:
24+
fprintf(stderr, "usage: wserver [-d basedir] [-p port]\n");
25+
exit(1);
26+
}
27+
28+
// run out of this directory
29+
chdir_or_die(root_dir);
30+
31+
// now, get to work
32+
int listen_fd = open_listen_fd_or_die(port);
33+
while (1) {
34+
struct sockaddr_in client_addr;
35+
int client_len = sizeof(client_addr);
36+
int conn_fd = accept_or_die(listen_fd, (sockaddr_t *) &client_addr, (socklen_t *) &client_len);
37+
request_handle(conn_fd);
38+
close_or_die(conn_fd);
39+
}
40+
return 0;
41+
}
42+
43+
44+
45+
46+
47+

0 commit comments

Comments
 (0)
Please sign in to comment.