Simple C Implementation of HTTP Server

README

记录一下自己计算机网络课程设计作品

课题

Unix 下简易 HTTP 服务器的 C 语言实现

要求

使用 Berkeley Socket 实现基本的 TCP 通讯,并在此基础上遵循 HTTP 协议进行通讯

实现 HTTP/1.1 的基本功能,支持GET方法,支持访问静态内容,支持常见的MIME文件类型

代码

web.c

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <sys/socket.h>
#include <sys/stat.h>
#include <sys/mman.h>
#include <sys/wait.h>
#include <netinet/in.h>
#include "mime.h"

#define BUFFERSIZE 1024

/*
* for all the infos about BSD socket: https://en.wikipedia.org/wiki/Berkeley_sockets#socket
*/

/*
* wrapper for perror used for bad syscalls
*/
void error(char *msg)
{
perror(msg);
exit(1);
}

/*
* returns an error message to the client
*/
void error_c(FILE *stream, char *cause, char *status_code,
char *shortmsg, char *longmsg, char *servername)
{
fprintf(stream, "HTTP/1.1 %s %s\n", status_code, shortmsg);
fprintf(stream, "Content-type: text/html\n");
fprintf(stream, "\n");
fprintf(stream, "<html><title>Server Error</title>");
fprintf(stream, "<body bgcolor="
"ffffff"
">\n");
fprintf(stream, "<h1>%s %s</h1>\n", status_code, shortmsg);
fprintf(stream, "<p>%s: %s\n", longmsg, cause);
fprintf(stream, "<hr><em>%s</em>\n", servername);
}

int main(int argc, char **argv)
{

// variables for connection management
int parentfd; // parent socket
int childfd; // child socket
int port; // port to listen on
int optval; // flag value for setsockopt
struct sockaddr_in serveraddr; // server's addr
struct sockaddr_in clientaddr; // client addr
int clientlen = sizeof(clientaddr); // byte size of client's address

// variables for connection I/O
FILE *stream; // stream of childfd
char buf[BUFFERSIZE]; // message buffer
char method[BUFFERSIZE]; // request method
char uri[BUFFERSIZE]; // request uri
char version[BUFFERSIZE]; // request http version
char filename[BUFFERSIZE]; // path derived from uri
char filetype[BUFFERSIZE]; // path derived from uri
char *p; // temporary pointer
int is_static; // flag value for static request
struct stat fileinfo; // file status
int fd; // static content file descriptor

char *servername = "BakaFT's C-Written Server";

// commandline arg check
if (argc != 2)
{
fprintf(stderr, "usage: %s <port>\n", argv[0]);
exit(1);
}
port = atoi(argv[1]);

// open socket descriptor
parentfd = socket(AF_INET, SOCK_STREAM, 0);
//The function returns -1 if an error occurred. Otherwise, it returns an integer representing the newly assigned descriptor.
if (parentfd < 0)
error("ERROR opening socket");

/*
SO_REUSEADDR allows your server to FORCIBLY bind to an address which is in a TIME_WAIT state after closed.

Reference:
https://docs.microsoft.com/en-us/windows/win32/winsock/using-so-reuseaddr-and-so-exclusiveaddruse
https://stackoverflow.com/questions/3229860/what-is-the-meaning-of-so-reuseaddr-setsockopt-option-linux

*/
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; // IPv4 protocol
serveraddr.sin_addr.s_addr = htonl(INADDR_ANY); // listen on all available interfaces.
serveraddr.sin_port = htons((unsigned short)port);
if (bind(parentfd, (struct sockaddr *)&serveraddr,
sizeof(serveraddr)) < 0)
error("ERROR on binding");

// get ready to accept connection requests
if (listen(parentfd, 10) < 0) // allow up to 10 requests to queue up
error("ERROR on listen");

/*
* main loop: wait for a connection request, parse HTTP,
* serve requested content, close connection.
*/

while (1)
{

// wait for a connection request
childfd = accept(parentfd, (struct sockaddr *)&clientaddr, &clientlen);
/*
accept() returns the new socket descriptor for the accepted connection, or the value -1 if an error occurs.
Why cast sockaddr_in* to sockaddr* here?
https://stackoverflow.com/questions/21099041/why-do-we-cast-sockaddr-in-to-sockaddr-when-calling-bind
*/

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, BUFFERSIZE, stream);
printf("HTTP Request body:\n%s", buf);
sscanf(buf, "%s %s %s\n", method, uri, version);

/* tiny only supports the GET method */
if (strcasecmp(method, "GET"))
/*
strcasecmp() return an integer greater than, equal
to, or less than 0, according as s1 is lexicographically greater than,
equal to, or less than s2
*/
{
error_c(stream, method, "501", "Not Implemented",
"Server does not support this method", servername);
fclose(stream);
close(childfd);
continue;
}

/* read and ignore the HTTP headers */
fgets(buf, BUFFERSIZE, stream);
printf("%s", buf);
while (strcmp(buf, "\r\n"))
{
fgets(buf, BUFFERSIZE, stream);
printf("%s", buf);
}

// parse the uri
if (!strstr(uri, "?"))
{ //static content
is_static = 1;
strcpy(filename, ".");
strcat(filename, uri);
if (strlen(uri) == 1) // namely "/"
{
strcat(filename, "index.html"); // if uri is "/" then add "index.html" by default
}
else
{
//do nothing
}
}
else
{ /* dynamic content
not supported */
error_c(stream, uri, "501", "Not Implemented",
"Server does not support dynamic content", servername);
fclose(stream);
close(childfd);
continue;
}

/* make sure the file exists */
/* stat()
Upon successful completion a value of 0 is returned. Otherwise, a value
of -1 is returned and errno is set to indicate the error.
*/
if (stat(filename, &fileinfo) < 0) // and here we store file info to fileinfo
{
error_c(stream, filename, "404", "Not found",
"Server couldn't find this file", servername);
fclose(stream);
close(childfd);
continue;
}

// serve static content
if (is_static)
{
// Here we assume that only 1 extension in filename
// which means there will not be filename like “tom.html.jpg"
strcpy(filetype,"text/plain");
for (int i = 0; i < ARRAYSIZE ; i++)
{
if (strstr(filename, mimelist[i].key))
{
strcpy(filetype,mimelist[i].value);
break;
}
}

// print response header
fprintf(stream, "HTTP/1.1 200 OK\n");
fprintf(stream, "Allow: GET\n");
fprintf(stream, "Server: %s\n",servername);
fprintf(stream, "Content-length: %d\n", (int)fileinfo.st_size); // file size in bytes
fprintf(stream, "Content-type: %s;charset=UTF-8\n", filetype);
fprintf(stream, "\r\n");
fflush(stream);

/* Use mmap to return arbitrary-sized response body
open file and map the contents to memory, then copy it from memory to stream and send out thourgh socket.
use munmap() to free the mapped memory at last.
*/
fd = open(filename, O_RDONLY);
p = mmap(0, fileinfo.st_size, PROT_READ, MAP_PRIVATE, fd, 0);
fwrite(p, 1, fileinfo.st_size, stream);
munmap(p, fileinfo.st_size); // undo mmap
}

// close this socket and wait for the next accept()
fclose(stream);
close(childfd);
}
}

mime.h

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#define ARRAYSIZE 10 
typedef struct Map
{
char *key;
char *value;
} Mapnode;
Mapnode mimelist[ARRAYSIZE] = {
{"mp3", "audio/mpeg"},
{"gif", "image/gif"},
{"jpg", "image/jpeg"},
{"png", "image/png"},
{"svg", "image/svg+xml"},
{"css", "text/css"},
{"html", "text/html"},
{"txt", "text/plain"},
{"xml", "text/xml"},
{"ico","image/x-icon"}
};
Author

BakaFT

Posted on

2021-05-28

Updated on

2023-12-28

Licensed under

Your browser is out-of-date!

Update your browser to view this website correctly.&npsb;Update my browser now

×