HTTP服务器的简单实现
2016-06-26 17:25
357 查看
在 HTTP学习笔记——报文格式
一文中,我们已经可以了解到HTTP的报文格式,有了报文格式,我们就可以实现简单的HTTP服务器了。
一个简单的HTTP服务器会包含一下几个部分:
+ 接收HTTP请求
+ 解析HTTP请求
+ 构造HTTP响应
+ 发送HTTP响应。
我们看一下如何用C语言实现简单的HTTP服务器。
在下面代码中会用到几个自定义的重要的结构体,这里先列出来:
我们先看一下main函数
首先通过
目前我们只支持GET方法,其他方法进入
当HTTP报文请求的方法是GET时,我们进入
该函数有两个调用,
解析了URL之后,我们就需要想客户端做出响应了。对于
HTTP响应报文我们主要分为两部分: 非实体部分(包括起始行和首部)、实体部分(资源的内容)。
对于一个简单的
非实体部分(起始行和首部)的构造与发送在函数
这里要提一下
一文中,我们已经可以了解到HTTP的报文格式,有了报文格式,我们就可以实现简单的HTTP服务器了。
一个简单的HTTP服务器会包含一下几个部分:
+ 接收HTTP请求
+ 解析HTTP请求
+ 构造HTTP响应
+ 发送HTTP响应。
我们看一下如何用C语言实现简单的HTTP服务器。
在下面代码中会用到几个自定义的重要的结构体,这里先列出来:
typedef struct request_startup{ char method[64]; char req_url[1024]; char version[64]; } RequestS; typedef struct URL{ char file_path[64]; char query_string[512]; } URL;
我们先看一下main函数
int main(int argc, char** argv) { int sockfd, clientfd; short port; struct sockaddr_in serv_addr, client_addr; socklen_t serv_len, client_len; port = htons(PORT); sockfd = Socket(AF_INET, SOCK_STREAM, 0); memset(&serv_addr, 0, sizeof(serv_addr)); serv_addr.sin_family = AF_INET; serv_addr.sin_port = port; serv_addr.sin_addr.s_addr = htonl(INADDR_ANY); // inet_pton(AF_INET, SERV_ADDR, &serv_addr.sin_addr); Bind(sockfd, (struct sockaddr*)&serv_addr, sizeof (serv_addr)); Listen(sockfd, 6); while (1) { client_len = sizeof (client_addr); clientfd = Accept(sockfd, (struct sockaddr*)&client_addr, &client_len); handle_request(clientfd); close(clientfd); } close(sockfd); return 0; }
接收请求
首先我们要单间一个socket套接字的服务端程序,然后阻塞在Accept等待HTTP客户端的请求。当接收到客户端的连接时,我们定义了函数
handle_request(clientfd);来处理客户端的请求。在
handle_request()函数中,我们根据HTTP请求报文的格式,解析HTTP报文
解析HTTP报文:
int handle_request(int clientfd) { char buf[1024]; char method[32]; char req_url[1024]; char version[32]; int i, j; int numchars; printf("Handle_request\n"); numchars = get_line(clientfd, buf, sizeof(buf)); // Get start line printf("get line: %s\n", buf); i = 0; j = 0; /* get method from start_line */ while ( !isspace(buf[j]) && i < (sizeof (method) - 1)) { method[i] = buf[j]; ++i; ++j; } method[i] = '\0'; /* Get Request URL from start_line */ i = 0; while( isspace(buf[j]) ) ++j; while ( !isspace(buf[j]) && i < (sizeof (req_url) -1 )) { req_url[i] = buf[j]; ++i; ++j; } req_url[i] = '\0'; printf("Get URL:%s\n", req_url); /* Get Version from start_line */ i = 0; while( isspace(buf[j]) ) ++j; while ( buf[j] != '\0' && i < (sizeof (version) -1 )) { version[i] = buf[j]; ++i; ++j; } version[i] = '\0'; if(!strcasecmp(method, "GET")) { printf("%s\n", req_url); response_GET(clientfd, req_url); printf("GET finished%s\n", req_url); } else { NotSupportMethod(); } return 0; }
首先通过
get_line()读取HTTP报文的起始行,然后解析出HTTP请求报文的方法、请求的URL。
目前我们只支持GET方法,其他方法进入
NotSupportMethod()处理。
当HTTP报文请求的方法是GET时,我们进入
response_GET()处理。
void response_GET(int clientfd, char *url_str) { printf("Response GET...\n"); URL url; prase_url(url_str, &url); serve_file(clientfd, url.file_path); }
该函数有两个调用,
prase_url()用来解析客户端请求的url, 然后将他转换为服务器上响应资源的地址。 比如客户端的输入的网址为
http://127.0.0.1:8080/index.html,则报文中的URL为
/index.html该报文请求的是服务器上相对路径为
./index.html的资源。
解析了URL之后,我们就需要想客户端做出响应了。对于
GET请求而言,就是把报文请求的资源返回给客户端,这一部分通过
serve_file实现。
构造并发送HTTP响应
void serve_file(int clientfd, const char* file_path) { printf("Serve file...\n"); FILE *resource; char buf[1024]; char filename[1024]; int numchars = 1; int file_len = 0; filename[0] = '.'; filename[1] = '\0'; strcat(filename, file_path); printf("respond file: %s\n", filename); resource = fopen(filename, "r"); fseek(resource,0,2); //指针:移动到文件尾部 file_len = ftell(resource); //返回指针偏离文件头的位置(即文件中字符个数) fseek(resource,0,0); //指针:移动到文件尾部 printf("====file_len:%d=======\n", file_len); if (resource == NULL) { printf("File not found!\n"); //not_found(client); } else { response_header(clientfd, filename); fgets(buf, sizeof(buf), resource); while (!feof(resource)) { send(clientfd, buf, strlen(buf), 0); fgets(buf, sizeof(buf), resource); } } fclose(resource); }
HTTP响应报文我们主要分为两部分: 非实体部分(包括起始行和首部)、实体部分(资源的内容)。
对于一个简单的
GET请求,其实体部分就是资源
./index.html的内容。 实体部分(资源的内容)的构造与发送其实就是读取资源内容,然后发送给客户端:
fgets(buf, sizeof(buf), resource); while (!feof(resource)) { send(clientfd, buf, strlen(buf), 0); fgets(buf, sizeof(buf), resource); }
非实体部分(起始行和首部)的构造与发送在函数
response_header(clientfd, filename);中实现。
void response_header(int client, const char* filename) { char buf[1024]; (void)filename; /* could use filename to determine file type */ ssize_t n; strcpy(buf, "HTTP/1.0 200 OK\r\nContent-Type: text/html\r\nContent-Length: 125\r\n\r\n"); send(client, buf, strlen(buf), 0); }
这里要提一下
Content-Length: 125这一个首部,这里我写了固定值125(其实它是资源内容的大小,这里为了简单说明,在代码中写了固定值)。
Content-Length这个首部可以说是必须的,它能帮助客户端识别HTTP响应报文的结束。正常情况下,我们必须给这个首部赋上准确的值,否则一些客户端可能无法正确的识别HTTP报文,发生连接错误、浏览器不进行缓存。(如会发现浏览器已经请求到了index.html页面,但是闪一下就不见了。)。
相关文章推荐
- Select模型及tcp select模型
- HTTPS的七个误解
- The superclass "javax.servlet.http.HttpServlet" was not found on the Java Build Path的解决办法
- 【DAY18】Socket编程,ROSE建模与TCP/IP的学习笔记
- 网络爬虫(3)--Beautiful页面解析
- 封装OKHttp
- 利用Nginx搭建http和rtmp协议的流媒体服务器
- python2.0_s12_day10_Twsited异步网络框架
- TCP包防粘连的读取方法
- 亦大自学51cto--网络工程师成长之路前沿
- TCP的代码
- win10怎么映射网络驱动器,怎么连接共享文件夹
- 网络协议系列之:TCP连接的建立与释放
- Android 网络工具类HttpManaer
- 久久发营销软件站
- 使用URLConnction发送HTTP/HTTPS请求
- 【网络高可用】华为交换机/路由器链路聚合
- 网络协议系列之二:HTTP(2)
- NodeJs——(8)http.ServerRequest的过程
- 网络协议系列之一:HTTP(1)