您的位置:首页 > 理论基础 > 计算机网络

HTTP服务器的简单实现

2016-06-26 17:25 357 查看
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页面,但是闪一下就不见了。)。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: