-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathhttp.c
257 lines (215 loc) · 8.92 KB
/
http.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
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
#include <errno.h>
#include "http.h"
static const char* get_file_type(const char *type);
static void parse_uri(char *uri, int length, char *filename, char *query);
static void do_error(int fd, char *cause, char *err_num, char *short_msg, char *long_msg);
static void serve_static(int fd, char *filename, size_t filesize, han_http_out_t *out);
static char *ROOT = NULL;
mime_type_t haneed_mime[] =
{
{".html", "text/html"},
{".xml", "text/xml"},
{".xhtml", "application/xhtml+xml"},
{".txt", "text/plain"},
{".rtf", "application/rtf"},
{".pdf", "application/pdf"},
{".word", "application/msword"},
{".png", "image/png"},
{".gif", "image/gif"},
{".jpg", "image/jpeg"},
{".jpeg", "image/jpeg"},
{".au", "audio/basic"},
{".mpeg", "video/mpeg"},
{".mpg", "video/mpeg"},
{".avi", "video/x-msvideo"},
{".gz", "application/x-gzip"},
{".tar", "application/x-tar"},
{".css", "text/css"},
{NULL ,"text/plain"}
};
static void parse_uri(char *uri_start, int uri_length, char *filename, char *query){
uri_start[uri_length] = '\0';
// 找到'?'位置界定非参部分
char *delim_pos = strchr(uri_start, '?');
int filename_length = (delim_pos != NULL) ? ((int)(delim_pos - uri_start)) : uri_length;
strcpy(filename, ROOT);
// 将uri中属于'?'之前部分内容追加到filename
strncat(filename, uri_start, filename_length);
// 在请求中找到最后一个'/'位置界定文件位置
char *last_comp = strrchr(filename, '/');
// 在文件名中找到最后一个'.'界定文件类型
char *last_dot = strrchr(last_comp, '.');
// 请求文件时末尾加'/'
if((last_dot == NULL) && (filename[strlen(filename) - 1] != '/')){
strcat(filename, "/");
}
// 默认请求index.html
if(filename[strlen(filename) - 1] == '/'){
strcat(filename, "index.html");
}
return;
}
const char* get_file_type(const char *type){
// 将type和索引表中后缀比较,返回类型用于填充Content-Type字段
for(int i = 0; haneed_mime[i].type != NULL; ++i){
if(strcmp(type, haneed_mime[i].type) == 0)
return haneed_mime[i].value;
}
// 未识别返回"text/plain"
return "text/plain";
}
// 响应错误信息
void do_error(int fd, char *cause, char *err_num, char *short_msg, char *long_msg){
// 响应头缓冲(512字节)和数据缓冲(8192字节)
char header[MAXLINE];
char body[MAXLINE];
// 用log_msg和cause字符串填充错误响应体
sprintf(body, "<html><title>haneed Error<title>");
sprintf(body, "%s<body bgcolor=""ffffff"">\n", body);
sprintf(body, "%s%s : %s\n", body, err_num, short_msg);
sprintf(body, "%s<p>%s : %s\n</p>", body, long_msg, cause);
sprintf(body, "%s<hr><em>haneed web server</em>\n</body></html>", body);
// 返回错误码,组织错误响应头
sprintf(header, "HTTP/1.1 %s %s\r\n", err_num, short_msg);
sprintf(header, "%sServer: haneed\r\n", header);
sprintf(header, "%sContent-type: text/html\r\n", header);
sprintf(header, "%sConnection: close\r\n", header);
sprintf(header, "%sContent-length: %d\r\n\r\n", header, (int)strlen(body));
// Add 404 Page
// 发送错误信息
rio_writen(fd, header, strlen(header));
rio_writen(fd, body, strlen(body));
return;
}
// 处理静态文件请求
void serve_static(int fd, char *filename, size_t filesize, han_http_out_t *out){
// 响应头缓冲(512字节)和数据缓冲(8192字节)
char header[MAXLINE];
char buff[SHORTLINE];
struct tm tm;
// 返回响应报文头,包含HTTP版本号状态码及状态码对应的短描述
sprintf(header, "HTTP/1.1 %d %s\r\n", out->status, get_shortmsg_from_status_code(out->status));
// 返回响应头
// Connection,Keep-Alive,Content-type,Content-length,Last-Modified
if(out->keep_alive){
// 返回默认的持续连接模式及超时时间(默认500ms)
sprintf(header, "%sConnection: keep-alive\r\n", header);
sprintf(header, "%sKeep-Alive: timeout=%d\r\n", header, TIMEOUT_DEFAULT);
}
if(out->modified){
// 得到文件类型并填充Content-type字段
const char* filetype = get_file_type(strrchr(filename, '.'));
sprintf(header, "%sContent-type: %s\r\n", header, filetype);
// 通过Content-length返回文件大小
sprintf(header, "%sContent-length: %zu\r\n", header, filesize);
// 得到最后修改时间并填充Last-Modified字段
localtime_r(&(out->mtime), &tm);
strftime(buff, SHORTLINE, "%a, %d %b %Y %H:%M:%S GMT", &tm);
sprintf(header, "%sLast-Modified: %s\r\n", header, buff);
}
sprintf(header, "%sServer : haneed\r\n", header);
// 空行(must)
sprintf(header, "%s\r\n", header);
// 发送报文头部并校验完整性
size_t send_len = (size_t)rio_writen(fd, header, strlen(header));
if(send_len != strlen(header)){
perror("Send header failed");
return;
}
// 打开并发送文件
int src_fd = open(filename, O_RDONLY, 0);
char *src_addr = mmap(NULL, filesize, PROT_READ, MAP_PRIVATE, src_fd, 0);
close(src_fd);
// 发送文件并校验完整性
send_len = rio_writen(fd, src_addr, filesize);
if(send_len != filesize){
perror("Send file failed");
return;
}
munmap(src_addr, filesize);
}
int error_proess(struct stat* sbufptr, char *filename, int fd){
// 处理文件找不到错误
if(stat(filename, sbufptr) < 0){
do_error(fd, filename, "404", "Not Found", "haneed can't find the file");
return 1;
}
// 处理权限错误
if(!(S_ISREG((*sbufptr).st_mode)) || !(S_IRUSR & (*sbufptr).st_mode)){
do_error(fd, filename, "403", "Forbidden", "haneed can't read the file");
return 1;
}
return 0;
}
void do_request(void* ptr){
han_http_request_t* request = (han_http_request_t*)ptr;
int fd = request->fd;
ROOT = request->root;
char filename[SHORTLINE];
struct stat sbuf;
int rc, n_read;
char* plast = NULL;
size_t remain_size;
han_del_timer(request);
while(1){
// plast指向缓冲区buf当前可写入的第一个字节位置,这里取余是为了实现循环缓冲
plast = &request->buff[request->last % MAX_BUF];
// remain_size表示缓冲区当前剩余可写入字节数
remain_size = MIN(MAX_BUF - (request->last - request->pos) - 1, MAX_BUF - request->last % MAX_BUF);
// 从连接描述符fd读取数据并复制到用户缓冲区plast指向的开始位置
n_read = read(fd, plast, remain_size);
// 已读到文件尾或无可读数据,断开连接
if(n_read == 0)
goto err;
// 非EAGAIN错误,断开连接
if(n_read < 0 && (errno != han_AGAIN))
goto err;
// Non-blocking下errno返回EAGAIN则重置定时器(进入此循环表示连接被激活),重新注册,在不断开TCP连接情况下重新等待下一次用户请求
if((n_read < 0) && (errno == han_AGAIN))
break;
// 更新读到的总字节数
request->last += n_read;
// 解析请求报文行
rc = han_http_parse_request_line(request);
if(rc == han_AGAIN)
continue;
else if(rc != 0)
goto err;
// 解析请求报文体
rc = han_http_parse_request_body(request);
if(rc == han_AGAIN)
continue;
else if(rc != 0)
goto err;
// 分配并初始化返回数据结构
han_http_out_t* out = (han_http_out_t *)malloc(sizeof(han_http_out_t));
han_init_out_t(out, fd);
// 解析URI,获取文件名
parse_uri(request->uri_start, request->uri_end - request->uri_start, filename, NULL);
// 处理相应错误
if(error_proess(&sbuf, filename, fd))
continue;
han_http_handle_header(request, out);
// 获取请求文件类型
out->mtime = sbuf.st_mtime;
// 处理静态文件请求
serve_static(fd, filename, sbuf.st_size, out);
// 释放返回数据结构
free(out);
// 处理HTTP长连接,控制TCP是否断开连接
if (!out->keep_alive)
goto close;
}
// 一次请求响应结束后不直接断开TCP连接,而是重置状态
// 修改已经注册描述符的事件类型
// 重置定时器,每等待下一次请求均生效
han_epoll_mod(request->epoll_fd, request->fd, request, (EPOLLIN | EPOLLET | EPOLLONESHOT));
han_add_timer(request, TIMEOUT_DEFAULT, han_http_close_conn);
// 每完成一次请求数据的响应都会return以移交出worker线程使用权,并在未断开TCP连接情况下通过epoll监听下一次请求
return;
err:
close:
// 发生错误或正常关闭
// 关闭相应连接描述符,释放用户请求数据结构
han_http_close_conn(request);
}