HTTP服务器的简单实现

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页面,但是闪一下就不见了。)。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值