我是靠谱客的博主 自由向日葵,最近开发中收集的这篇文章主要介绍有限状态机实现解析HTTP报文,觉得挺不错的,现在分享给大家,希望可以做个参考。

概述

该代码为《LINUX高性能服务器编程》上的代码

为实现功能,设计了主状态机和从状态机:主状态机在内部调用从状态机。

从状态机的状态转换图如下:

在这里插入图片描述

代码如下

#include <sys/socket.h>
#include <sys/types.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <errno.h>
#include <netinet/in.h>
#include <sys/socket.h>
#include <stdio.h>
#include <assert.h>
#include <stdlib.h>
#include <string.h>

#define BUFFER_SIZE 4096
// 主状态机状态(功能的选择):正在分析请求行, 正在分析头部字段
enum CHECK_STATE
{
    CHECK_STATE_REQUESTLINE = 0,
    CHECK_STATE_HEADER
};
// 从状态机状态:读取到一个完整的行,行出错,行数据尚不完整
enum LINE_STATUS
{
    LINE_OK = 0,
    LINE_BAD,
    LINE_OPEN
};

// 服务器处理HTTP请求的结果:
// NO_REQUEST 表示请求不完整,需要继续读取客户数据
// GET_REQUEST 表示获得了一个完整的客户请求
// BAD_REQUEST 表示客户请求有语法错误
// FORBIDDEN_REQUEST 表示客户对资源没有足够的访问权限
// INTERNAL_ERROR 表示服务器内部错误
// CLOSED_CONNECTION 表示客户端已经关闭连接
enum HTTP_CODE
{
    NO_REQUEST,
    GET_REQUEST,
    BAD_REQUEST,
    FORBIDDEN_REQUEST,
    INTERNAL_ERROR,
    CLOSED_CONNECTION
};

// 根据服务器处理结果发送如下成功或失败的消息
static const char *szret[] = {"I got a correct resultn", "Something wrong"};

// 从状态机,用于解析出一行内容
LINE_STATUS parse_line(char *buffer, int &checked_index, int &read_index)
{
    char temp;
    // checked_index指向buffer(应用程序的读缓冲区)中当前正在分析的字节
    // read_index指向buffer中客户数据的尾部的下一字节
    // buffer 中第0~checked_index字节都已分析完毕,第checked_index ~ (read_index - 1)字节有下面的循环进行分析
    for (; checked_index < read_index; ++checked_index)
    {
        // 获得当前要分析的字节
        temp = buffer[checked_index];
        // 若当前字节是 回车符,说明可能读到一个完整的行
        if (temp == 'r')
        {
            // 若 "r"碰巧是目前buffer中最后一个已经被读入的客户数据,则这次分析没读到一个完整的行,返回LINE_OPEN表示还要继续读取客户数据才能进行下一步分析
            if ((checked_index + 1) == read_index)
            {
                return LINE_OPEN;
            }
            else if (buffer[checked_index + 1] == 'n')
            {
                // 若下一个字符是换行符,则表明成功读取到一个完整的行
                buffer[checked_index++] = '';
                buffer[checked_index++] = '';
                return LINE_OK;
            }
            return LINE_BAD;
        }
        else if (temp == 'n')
        { // 可能读到完整的行
            if ((checked_index > 1) && buffer[checked_index - 1] == 'r')
            {
                buffer[checked_index - 1] = '';
                buffer[checked_index++] = '';
            }
        }

        return LINE_BAD;
    }
}

// 分析请求行
HTTP_CODE parse_requestline(char *temp, CHECK_STATE &checkstate)
{
    // 检索第一个匹配' ' 或 't'的位置
    char *url = strpbrk(temp, " t"); 
    printf("url1 = %sn", url);
    if(!url) {
        return BAD_REQUEST;
    }
    *url++ = ''; // temp此时输出为GET

    char *method = temp; // 请求方法(如get post)
    printf("method:%sn", method);
    printf("url2 = %sn", url);
    // 
    if(strcasecmp(method, "GET") == 0)
    {
        printf("The request method is GETn");
    }
    else 
    {
        return BAD_REQUEST;
    }

    url += strspn(url, " t"); // 查找第一个不为 ' ' 且 't'的位置  
    printf("url3 = %sn", url);
    char *version = strpbrk(url, " t");
    printf("version = %sn", version);
    if(!version) 
    {
        return BAD_REQUEST;
    }
    *version++ = '';
    version += strspn(version, " t");
    printf("version2 = %sn", version);
    
    if(strcasecmp(version,  "HTTP/1.1") != 0) {
        return BAD_REQUEST;
    }

    // 检查URL是否合法
    if(strncasecmp(url, "http://", 7) == 0) {
        url += 7;
        url = strchr(url, '/');
    }

    if(!url || url[0]  !='/') {
        return BAD_REQUEST;
    }

    printf("The request URL is: %sn", url);

    // HTTP请求行处理完毕,状态转移到头部字段的分析
    checkstate = CHECK_STATE_HEADER;
    return NO_REQUEST;
}

// 分析头部字段
HTTP_CODE parse_headers(char *temp)
{
    // 遇到一个空行,说明得到了一个正确的HTTP请求
    if(temp[0] == '') 
    {
        return GET_REQUEST;
    }
    else if(strncasecmp(temp, "Host:", 5) == 0) //
    {
        temp += 5;
        // 跳过空格和制表符
        temp += strspn(temp, " t");
        printf("the reques host is:%sn", temp);
    }
    else
    {
        printf("I can not handle this headern");
    }
    return NO_REQUEST; // 请求不完整
}

// 分析http请求的入口函数
HTTP_CODE parse_content(char *buffer, int &checked_index, CHECK_STATE &checkstate,
                        int &read_index, int &start_line)
{
    LINE_STATUS linestatus = LINE_OK; // 记录当前行的【读取】状态
    HTTP_CODE retcode = NO_REQUEST; // 记录当前HTTP请求的处理结果
    // 主状态机,用于从buffer中取出所有完整的行 ???
    while((linestatus = parse_line(buffer, checked_index, read_index)) == LINE_OK) 
    {
        char *temp = buffer + start_line; // start_line是行在buffer中的起始位置
        start_line = checked_index; // 记录下一行的 起始位置
        switch (checkstate)
        {
            case CHECK_STATE_REQUESTLINE: // 分析请求行
            {
                retcode = parse_requestline(temp, checkstate);
                if(retcode == BAD_REQUEST) 
                {
                    return BAD_REQUEST;
                }
                break;
            }
            case CHECK_STATE_HEADER:    // 分析头部字段
            {
                retcode = parse_headers(temp);
                if(retcode == BAD_REQUEST) 
                {
                    return BAD_REQUEST;
                }
                else if(retcode == GET_REQUEST) 
                {
                    return GET_REQUEST;
                }
                break;
            }
            default:
            {
                return INTERNAL_ERROR;
            }
        }
    
    }

    // 若没有读取到一个完整的行,则表示还需要继续读取客户数据才能进一部分析
    if(linestatus == LINE_OPEN) 
    {
        return NO_REQUEST; // main收到这个返回会继续循环recv客户数据
    }
    else
    {
        return BAD_REQUEST;
    }
}

int main(int argc, char *argv[])
{
    if (argc <= 2)
    {
        printf("usage: %s ip_address port_numn", basename(argv[0]));
        return 1;
    }
    const char *ip = argv[1];
    int port = atoi(argv[2]);

    struct sockaddr_in addr;
    bzero(&addr, sizeof(addr));
    addr.sin_family = AF_INET;
    inet_pton(AF_INET, ip, &addr.sin_addr);
    addr.sin_port = htons(port);

    int listenfd = socket(PF_INET, SOCK_STREAM, 0);
    assert(listenfd >= 0);

    int ret = bind(listenfd, (struct sockaddr *)&addr, sizeof(addr));
    assert(ret != -1);

    ret = listen(listenfd, 5);
    assert(ret != -1);

    struct sockaddr_in client;
    socklen_t client_addrlength = sizeof(client);
    int connfd = accept(listenfd, (struct sockaddr *)&client, &client_addrlength);
    if (connfd < 0)
    {
        printf("errno is: %sn", errno);
    }
    else
    {
        char buffer[BUFFER_SIZE];
        memset(buffer, '', BUFFER_SIZE);
        int data_read = 0;     // 记录每次recv读出的数据字节数
        int read_index = 0;    // 当前已经读取了多少字节的客户数据据
        int checked_index = 0; // 当前已分析完了多少字节的客户数据
        int start_line = 0;    // 行在buffer中的起始位置
        // 设置主状态机的初始状态,分析请求行
        CHECK_STATE checkstate = CHECK_STATE_REQUESTLINE;
        while (1)
        {
            data_read = recv(connfd, buffer + read_index, BUFFER_SIZE - read_index, 0);
            if (data_read == -1)
            {
                printf("reading failedn");
                break;
            }
            else if (data_read == 0)
            {
                printf("remote client has closed the connectionn");
                break;
            }
            read_index += data_read;
            // 分析目前已获得的所有客户数据
            HTTP_CODE result = parse_content(buffer, checked_index, checkstate, read_index, start_line);

            if (result == NO_REQUEST) // 请求不完整
            {
                continue;
            }
            else if (result == GET_REQUEST) // 请求正确且完整
            {
                send(connfd, szret[0], strlen(szret[0]), 0);
                break;
            }
            else 
            {
                send(connfd, szret[1], strlen(szret[1]), 0);
                break;
            }
            close(connfd);
        }
    }
    close(listenfd);
    return 0;
}

最后

以上就是自由向日葵为你收集整理的有限状态机实现解析HTTP报文的全部内容,希望文章能够帮你解决有限状态机实现解析HTTP报文所遇到的程序开发问题。

如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。

本图文内容来源于网友提供,作为学习参考使用,或来自网络收集整理,版权属于原作者所有。
点赞(36)

评论列表共有 0 条评论

立即
投稿
返回
顶部