TinyWebServer1--HTTP代码部分

HTTP--HyperText Transfer Protocol,超文本传输协议,一种用于分布式、协作式和超媒体信息系统的应用层协议,用于传输HTML。

HTML--HyperText Mark-up Language,超文本传输语言

http协议

请求分类

请求方法    意义
OPTIONS    请求一些选项信息,允许客户端查看服务器的性能
GET    请求指定的页面信息,并返回实体主体
HEAD    类似于 get 请求,只不过返回的响应中没有具体的内容,用于获取报头
POST    向指定资源提交数据进行处理请求(例如提交表单或者上传文件)。数据被包含在请求体中。POST请求可能会导致新的资源的建立和/或已有资源的修改
PUT    从客户端向服务器传送的数据取代指定的文档的内容
DELETE    请求服务器删除指定的页面
TRACE    回显服务器收到的请求,主要用于测试或诊断

请求消息

 HTTP请求的完全过程_艾伦lee的博客-CSDN博客

浏览器-->服务器,主要包含:

        请求行:说明请求类型,要访问的资源,以及使用的http版本

        请求头:说明服务器要使用的附加信息

        空行:必须

        请求数据:主体,GET请求无,POST有。是浏览器携带给服务器的数据。

GET请求

向服务器请求数据

请求协议: --- 浏览器组织,发送

GET /hello.c Http1.1\r\n
2. Host: localhost:2222\r\n
3. User-Agent: Mozilla/5.0 (X11; Ubuntu; Linux i686; rv:24.0) Gecko/201001    01 Firefox/24.0\r\n
4. Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8\r\n
5. Accept-Language: zh-cn,zh;q=0.8,en-us;q=0.5,en;q=0.3\r\n
6. Accept-Encoding: gzip, deflate\r\n
7. Connection: keep-alive\r\n
8. If-Modified-Since: Fri, 18 Jul 2014 08:36:36 GMT\r\n
【空行】\r\n
#其中GET /hello.c Http1.1\r\n和最后一行的【空行】\r\n最重要

应答协议:

Http1.1 200 OK
2. Server: xhttpd
Content-Type:text/plain; charset=iso-8859-1 
3. Date: Fri, 18 Jul 2014 14:34:26 GMT
5. Content-Length: 32  ( 要么不写 或者 传-1, 要写务必精确 ! )
6. Content-Language: zh-CN
7. Last-Modified: Fri, 18 Jul 2014 08:36:36 GMT
8. Connection: close 
\r\n
[数据起始。。。。。
。。。。
。。。数据终止]
#其中Http1.1 200 OK和Content-Type:text/plain; charset=iso-8859-1 必须要有
#\r\n必须要有,表示协议头结束,后面就是内容

POST请求 

携带数据给服务器

POST / color1.cgi HTTP / 1.1
Host: 192.168.0.23 : 47310
Connection : keep - alive
Content - Length : 10
Cache - Control : max - age = 0
Origin : http ://192.168.0.23:40786
Upgrade - Insecure - Requests : 1
User - Agent : Mozilla / 5.0 (Windows NT 6.1; WOW64) AppleWebKit / 537.36 (KHTML, like Gecko) Chrome / 55.0.2883.87
Safari / 537.36
Content - Type : application / x - www - form - urlencoded
Accept : text / html, application / xhtml + xml, application / xml; q = 0.9, image / webp, */*;q=0.8
Referer: http://192.168.0.23:47310/
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.8
Cookie: __guid=179317988.1576506943281708800.1510107225903.8862; monitor_count=281
Form Data
color=gray
第一部分:请求头行,包含请求类型、URI、HTTP协议版本;
请求信息类型通常有:get、post、put等等;  
第二部分:即紧跟第一行之后的,请求头部,包含服务器所使用的说明信息;
接下来解释一下这些说明信息的意思:
1、host:请求web服务器的域名地址
2、Connection: 表示是否持久连接;即keep-alive表示持久连接;
3、Cache-Control:指定请求和响应的缓存机制;no-cache(不能缓存)、no-store(在请求消息中发送将使得请求和响应消息都不使用缓存)、  max-age(客户机可以接收生存期不大于指定时间(以秒为单位)的响应)、max-stale(客户机可以接收超出超时期间的响应消息)、min-fresh(客户机可以接收响应时间小于当前时间加上指定时间的响应)、  only-if-cached等等
4、User-Agent: HTTP协议运行的浏览器类型的详细信息;比如:谷歌/67.0.3396.995、Accept: 指浏览器可以接收的内容类型;
6、Accept-Encoding: 客户端浏览器可以支持的web服务器返回内容压缩编码类型;
7、Accept-Language:浏览器支持的语言类型,
8、Cookie: 某些网站为了辨别用户身份、进行 session 跟踪而储存在用户本地终端上的数据(通常经过加密);例如当我们上网时,某些网站能准确的推送我们想要的信息。  
第三部分:"\r\n" --> 分割header和body部分的分界线

功能分析

主要就是利用C++进行HTTP报文的字符串解析。

http_conn.h代码实现

class http_conn
{
public:
    
    static const int FILENAME_LEN = 200;
    //设置读缓冲区m_read_buf大小
    static const int READ_BUFFER_SIZE = 2048;
    //设置写缓冲区m_write_buf大小
    static const int WRITE_BUFFER_SIZE = 1024;
    //报文的请求方法,只用到GET和POST
    enum METHOD
    {
        GET = 0,
        POST,
        HEAD,
        PUT,
        DELETE,
        TRACE,
        OPTIONS,
        CONNECT,
        PATH
    };
    //主状态机的状态
    enum CHECK_STATE
    {
        CHECK_STATE_REQUESTLINE = 0,
        CHECK_STATE_HEADER,
        CHECK_STATE_CONTENT
    };
    //报文解析的结果,表示HTTP请求的处理结果,一共初始化了八种情形
    enum HTTP_CODE
    {
        NO_REQUEST,//请求不完整,需要继续读取请求报文数据
        GET_REQUEST,//获得了完整的HTTP请求
        BAD_REQUEST,//HTTP请求报文有语法错误或请求资源为目录
        NO_RESOURCE,//请求资源不存在
        FORBIDDEN_REQUEST,//请求资源禁止访问,没有读取权限
        FILE_REQUEST,//请求资源可以正常访问
        INTERNAL_ERROR,//服务器内部错误
        CLOSED_CONNECTION
    };
    从状态机的状态
    enum LINE_STATUS
    {
        LINE_OK = 0,
        LINE_BAD,
        LINE_OPEN
    };

public:
    //构造和析构
    http_conn() {}
    ~http_conn() {}

public:
    void init(int sockfd, const sockaddr_in &addr, char *, int, int, string user, string passwd, string sqlname);
    void close_conn(bool real_close = true);

    //报文解析函数,返回的是解析出来的情况,根据不同的值完成不同的操作
    void process();
    
    bool read_once();
    bool write();
    sockaddr_in *get_address()
    {
        return &m_address;
    }
    void initmysql_result(connection_pool *connPool);
    int timer_flag;
    int improv;

    //我们需要在对象内部定义出:读缓冲区,写缓冲区

private:
    void init();
    //处理http请求协议以及http应答协议,使用process_read和process_write
    HTTP_CODE process_read();
    
    bool process_write(HTTP_CODE ret);
    HTTP_CODE parse_request_line(char *text);//读请求行
    HTTP_CODE parse_headers(char *text);//读请求头
    HTTP_CODE parse_content(char *text);//读请求数据
    HTTP_CODE do_request();
    char *get_line() { return m_read_buf + m_start_line; };//读一行
    //从状态机,返回值为行的读取状态,有LINE_OK,LINE_BAD,LINE_OPEN
    LINE_STATUS parse_line();
    void unmap();
    bool add_response(const char *format, ...);
    bool add_content(const char *content);
    bool add_status_line(int status, const char *title);
    bool add_headers(int content_length);
    bool add_content_type();
    bool add_content_length(int content_length);
    bool add_linger();
    bool add_blank_line();

public:
    static int m_epollfd;
    static int m_user_count;
    MYSQL *mysql;
    int m_state;  //读为0, 写为1

private:
    int m_sockfd;
    sockaddr_in m_address;
    //存储读取的请求报文数据
    char m_read_buf[READ_BUFFER_SIZE];
    //缓冲区中m_read_buf中数据的最后一个字节的下一个位置
    int m_read_idx;
    //缓冲区中m_read_buf读取的位置m_checked_idx
    int m_checked_idx;
    //m_read_buf中已经解析的字符个数
    int m_start_line;
    //存储发出的响应报文数据
    char m_write_buf[WRITE_BUFFER_SIZE];
    //指示buffer中的长度
    int m_write_idx;
    //主状态机的状态
    CHECK_STATE m_check_state;
    //请求方法
    METHOD m_method;

    //以下为解析请求报文中对应的6个变量
    //存储读取文件的名称
    char m_real_file[FILENAME_LEN];
    char *m_url;
    char *m_version;
    char *m_host;
    int m_content_length;
    bool m_linger;

    char *m_file_address;
    struct stat m_file_stat;
    struct iovec m_iv[2];
    int m_iv_count;
    int cgi;        //是否启用的POST
    char *m_string; //存储请求头数据
    int bytes_to_send;
    int bytes_have_send;
    char *doc_root;

    map<string, string> m_users;
    int m_TRIGMode;
    int m_close_log;

    char sql_user[100];
    char sql_passwd[100];
    char sql_name[100];
};

#endif

 http_conn.cpp

http_conn.h的具体实现

viod process()

        处理http请求协议:process_read函数

        处理http应答协议:process_write函数

 Linux网络编程:状态机_linux 状态机_才文嘉的博客-CSDN博客

        处理http请求协议使用主从状态机,即process_read,从状态机使用parse_line。主状态机由从状态机驱动。

process_read:解析http请求报文是请求行、请求头还是请求数据;

parse_line:负责读取http报文的一行,解析这一行是否结束;

void http_conn::process()
{
    //读取浏览器发出的http请求
    HTTP_CODE read_ret = process_read();
    if (read_ret == NO_REQUEST)
    {
        //NO_REQUEST,表示请求不完整,需要继续接收请求数据
        modfd(m_epollfd, m_sockfd, EPOLLIN, m_TRIGMode);
        return;
    }
    //写应答协议,往浏览器传输
    bool write_ret = process_write(read_ret);
    if (!write_ret)//读完,关闭cfd
    {
        close_conn();
    }
    modfd(m_epollfd, m_sockfd, EPOLLOUT, m_TRIGMode);
}

LINE_STATUS parse_line()

从状态机函数

从状态机负责读取http报文的一行,解析这一行是否结束,用以支持主状态机判断是何种报文。

从状态机关心当前一行读完整了(LINE_OK)、不完整(LINE_OPEN)、格式有误(LINE_BAD)。一个主状态机状态包含多个从状态机状态

具体如下:

因为在HTTP报文中可以通过查找\r\n将报文拆解成单独的行进行解析,从状态机负责读取buffer中的数据,将每行数据末尾的\r\n置为\0\0,并更新从状态机在buffer中读取的位置m_checked_idx,以此来驱动主状态机解析。

流程:

1、从状态机从m_read_buf中逐字节读取,判断当前字节是否为\r

        接下来的字符是\n,将\r\n修改成\0\0,将m_checked_idx指向下一行的开头,则返回LINE_OK

        接下来达到buffer末尾,表示buffer还需要继续接收,返回LINE_OPEN

        否则,表示语法错误,返回LINE_BAD

2、当前字节不是\r,判断是否是\n(一般是上次读取到\r就到了buffer末尾,没有接收完整,再次接收时会出现这种情况

        如果前一个字符是\r,则将\r\n修改为\0\0,将m_checked_idx指向下一行的开头,则返回LINE_OK

3、当前字节既不是\r也不是\n

        表示接收不完整,需要继续接收,返回LINE_OPEN

 代码

//返回值为行的读取状态,有LINE_OK,LINE_BAD,LINE_OPEN
http_conn::LINE_STATUS http_conn::parse_line()
{
    char temp;
    //m_read_idx指向缓冲区m_read_buf的数据末尾的下一个字节
    //m_checked_idx指向从状态机当前正在分析的字节
    for (; m_checked_idx < m_read_idx; ++m_checked_idx)
    {
        //temp为将要分析的字节
        temp = m_read_buf[m_checked_idx];
        //如果当前是\r字符,则有可能会读取到完整行
        if (temp == '\r')
        {
            //下一个字符达到了buffer结尾,则接收不完整,需要继续接收
            if ((m_checked_idx + 1) == m_read_idx)
                return LINE_OPEN;
            //下一个字符是\n,将\r\n改为\0\0
            else if (m_read_buf[m_checked_idx + 1] == '\n')
            {
                m_read_buf[m_checked_idx++] = '\0';
                m_read_buf[m_checked_idx++] = '\0';
                return LINE_OK;
            }
            //如果都不符合,则返回语法错误
            return LINE_BAD;
        }
        //如果当前字符是\n,也有可能读取到完整行
        //一般是上次读取到\r就到buffer末尾了,没有接收完整,再次接收时会出现这种情况
        else if (temp == '\n')
        {
            //前一个字符是\r,则接收完整
            if (m_checked_idx > 1 && m_read_buf[m_checked_idx - 1] == '\r')
            {
                m_read_buf[m_checked_idx - 1] = '\0';
                m_read_buf[m_checked_idx++] = '\0';
                return LINE_OK;
            }
            return LINE_BAD;
        }
    }
    //并没有找到\r\n,需要继续接收
    return LINE_OPEN;
}

HTTP_CODE process_read()

主状态机函数,负责对该行数据解析

通过调用从状态机来驱动主状态机,在主状态机进行解析前,从状态机已经将每一行的末尾\r\n符号改为\0\0,以便于主状态机直接取出对应字符串进行处理。

主状态机三种状态:

        CHECK_STATE_REQUESTLINE,解析请求行,主状态机的初始状态,调用parse_request_line函数从m_read_buf中解析HTTP请求行,获得请求方法、目标URL及HTTP版本号

        CHECK_STATE_HEADER,解析请求头,调用parse_headers函数解析请求头部信息

        CHECK_STATE_CONTENT,解析请求数据,仅用于解析POST请求,调用parse_content函数解析,为后面的登录和注册做准备

初始化状态是CHECK_STATE_REQUESTLINE,在使用parse_request_line解析完后进行状态转移CHECK_STATE_HEADER

状态为CHECK_STATE_HEADER时,在使用parse_headers解析完会进行状态转移CHECK_STATE_CONTENT

CHECK_STATE_CONTENT为最终状态

//通过while循环 封装主状态机 对每一行进行循环处理
//此时 从状态机已经修改完毕 主状态机可以取出完整的行进行解析
http_conn::HTTP_CODE http_conn::process_read()
{
    //初始化从状态机的状态
    LINE_STATUS line_status = LINE_OK;
    HTTP_CODE ret = NO_REQUEST;
    char *text = 0;

    //判断条件,这里就是从状态机驱动主状态机
    while ((m_check_state == CHECK_STATE_CONTENT && line_status == LINE_OK) || ((line_status = parse_line()) == LINE_OK))
    {
        text = get_line();

        //m_start_line 是每一个数据行在m_read_buf中的起始位置
        //m_checked_idx 表示从状态机在m_read_buf中的读取位置
        m_start_line = m_checked_idx;
        LOG_INFO("%s", text);
        //三种状态转换逻辑
        switch (m_check_state)
        {
        case CHECK_STATE_REQUESTLINE:
        {
            //解析请求行
            ret = parse_request_line(text);
            if (ret == BAD_REQUEST)
                return BAD_REQUEST;
            break;
        }
        case CHECK_STATE_HEADER:
        {
            //解析请求头
            ret = parse_headers(text);
            if (ret == BAD_REQUEST)
                return BAD_REQUEST;
            //作为get请求 则需要跳转到报文响应函数
            else if (ret == GET_REQUEST)
            {
                return do_request();
            }
            break;
        }
        case CHECK_STATE_CONTENT:
        {
            //解析消息体
            ret = parse_content(text);

            //对于post请求 跳转到报文响应函数
            if (ret == GET_REQUEST)
                return do_request();
            //更新 跳出循环 代表解析完了消息体
            line_status = LINE_OPEN;
            break;
        }
        //默认状态
        default:
            return INTERNAL_ERROR;
        }
    }
    return NO_REQUEST;
}

HTTP_CODE parse_request_line(char *text)

process_read函数中当状态是CHECK_STATE_REQUESTLINE调用parse_request_line,用以解析http请求行,获得请求方法,目标url及http版本号

char *strpbrk(const char *str1, const char *str2) 依次检验字符串 str1 中的字符,当被检验字符在字符串 str2 中也包含时,则停止检验,并返回该字符位置。

返回值:str1 中第一个匹配字符串 str2 中字符的字符数,如果未找到字符则返回 NULL。

 int strcasecmp (const char *s1, const char *s2)

用来比较参数s1和s2字符串前n个字符,比较时会自动忽略大小写的差异。

若参数s1和s2字符串相等返回0,s1大于s2则返回大于0的值,s1小于s2则返回小于0的值

size_t strspn(const char *str1, const char *str2) 检索字符串 str1 中第一个不在字符串 str2 中出现的字符下标

返回值:str1 中第一个不在字符串 str2 中出现的字符下标

size_t strlen(const char* str)求出字符串长度

char *strcat(char *dest, const char *src) 把 src 所指向的字符串追加到 dest 所指向的字符串的结尾

//解析http请求行,获得请求方法,目标url及http版本号
http_conn::HTTP_CODE http_conn::parse_request_line(char *text)
{
    //请求行中最先含有空格和\t任一字符的位置并返回
    m_url = strpbrk(text, " \t");
    //没有目标字符 则代表报文格式有问题
    if (!m_url)
    {
        return BAD_REQUEST;
    }
    //用于将前面的数据取出
    *m_url++ = '\0';

    //取出数据 确定请求方式
    char *method = text;
    if (strcasecmp(method, "GET") == 0)
        m_method = GET;
    else if (strcasecmp(method, "POST") == 0)
    {
        m_method = POST;
        cgi = 1;
    }
    else
        return BAD_REQUEST;
    
    //m_url此时跳过了第一个空格或者\t字符,但是后面还可能存在
    //不断后移找到请求资源的第一个字符
    m_url += strspn(m_url, " \t");

    //判断http的版本号
    m_version = strpbrk(m_url, " \t");
    if (!m_version)
        return BAD_REQUEST;
    *m_version++ = '\0';
    m_version += strspn(m_version, " \t");

    //目前社长项目仅支持http1.1
    if (strcasecmp(m_version, "HTTP/1.1") != 0)
        return BAD_REQUEST;
    
    //对请求资源的前七个字符进行判断
    //对某些带有http://的报文进行单独处理
    if (strncasecmp(m_url, "http://", 7) == 0)
    {
        m_url += 7;
        m_url = strchr(m_url, '/');
    }

    //https的情况
    if (strncasecmp(m_url, "https://", 8) == 0)
    {
        m_url += 8;
        m_url = strchr(m_url, '/');
    }

    //不符合规则的报文
    if (!m_url || m_url[0] != '/')
        return BAD_REQUEST;
    
    //当url为/时,显示欢迎界面
    if (strlen(m_url) == 1)
    //把"judge.html"所指向的字符串追加到m_url的末尾
        strcat(m_url, "judge.html");
    
    //主状态机状态转移
    m_check_state = CHECK_STATE_HEADER;
    return NO_REQUEST;
}

HTTP_CODE parse_headers(char *text)

主状态机状态转移为CHECK_STATE_HEADER调用parse_request_line,用以解析请求头部信息

//解析http请求的一个头部信息
http_conn::HTTP_CODE http_conn::parse_headers(char *text)
{
    //判断是空行还是请求头
    if (text[0] == '\0')
    {
        //具体判断是get还是post请求
        if (m_content_length != 0)
        {
            //post请求需要改变主状态机的状态
            m_check_state = CHECK_STATE_CONTENT;
            return NO_REQUEST;
        }
        return GET_REQUEST;
    }
    //解析头部连接字段
    else if (strncasecmp(text, "Connection:", 11) == 0)
    {
        text += 11;

        //跳过空格和\t字符
        text += strspn(text, " \t");
        if (strcasecmp(text, "keep-alive") == 0)
        {
            //判断是否为长连接
            m_linger = true;
        }
    }
    //解析请求头 内容长度字段
    else if (strncasecmp(text, "Content-length:", 15) == 0)
    {
        text += 15;
        text += strspn(text, " \t");
        m_content_length = atol(text);
    }
    //解析请求头部host字段
    else if (strncasecmp(text, "Host:", 5) == 0)
    {
        text += 5;
        text += strspn(text, " \t");
        m_host = text;
    }
    else
    {
        LOG_INFO("oop!unknow header: %s", text);
    }
    return NO_REQUEST;
}

HTTP_CODE parse_content(char *text)

  • 用于解析POST请求,调用parse_content函数解析消息体
  • 用于保存POST请求消息体,为后面的登陆和注册做准备
//判断http请求是否被完整读入
http_conn::HTTP_CODE http_conn::parse_content(char *text)
{
    //判断是否读取了消息体
    if (m_read_idx >= (m_content_length + m_checked_idx))
    {
        text[m_content_length] = '\0';
        //POST请求中最后为输入的用户名和密码
        m_string = text;
        return GET_REQUEST;
    }
    return NO_REQUEST;
}

 do_request() 具体处理函数

process_read返回值是对请求文件的分析结果,一部分是语法错误的BAD_REQUEST,一部分则是我们认可的规则然后作出的对应的响应。

do_request()的具体做法是将网站根目录和url文件拼接,然后通过stat判断文件属性。另外为了提高访问速度,通过mmap进行映射,将普通文件迎神到内存逻辑地址。

char *strcpy(char *dest, const char *src) 把 src 所指向的字符串复制到 dest

返回值:该函数返回一个指向最终的目标字符串 dest 的指针

http_conn::HTTP_CODE http_conn::do_request()
{
    //将初始化的m_real_file赋值为网站根目录
    strcpy(m_real_file, doc_root);
    int len = strlen(doc_root);
    //printf("m_url:%s\n", m_url);
    //找到m_url中/的位置
    const char *p = strrchr(m_url, '/');

    //处理cgi
    //实现登录和注册校验
    if (cgi == 1 && (*(p + 1) == '2' || *(p + 1) == '3'))
    {

        //根据标志判断是登录检测还是注册检测
        char flag = m_url[1];

        char *m_url_real = (char *)malloc(sizeof(char) * 200);
        strcpy(m_url_real, "/");
        strcat(m_url_real, m_url + 2);
        strncpy(m_real_file + len, m_url_real, FILENAME_LEN - len - 1);
        free(m_url_real);

        //将用户名和密码提取出来
        //user=123&passwd=123
        char name[100], password[100];
        int i;
        for (i = 5; m_string[i] != '&'; ++i)
            name[i - 5] = m_string[i];
        name[i - 5] = '\0';

        int j = 0;
        for (i = i + 10; m_string[i] != '\0'; ++i, ++j)
            password[j] = m_string[i];
        password[j] = '\0';

        if (*(p + 1) == '3')
        {
            //如果是注册,先检测数据库中是否有重名的
            //没有重名的,进行增加数据
            char *sql_insert = (char *)malloc(sizeof(char) * 200);
            strcpy(sql_insert, "INSERT INTO user(username, passwd) VALUES(");
            strcat(sql_insert, "'");
            strcat(sql_insert, name);
            strcat(sql_insert, "', '");
            strcat(sql_insert, password);
            strcat(sql_insert, "')");

            if (users.find(name) == users.end())
            {
                m_lock.lock();
                int res = mysql_query(mysql, sql_insert);
                users.insert(pair<string, string>(name, password));
                m_lock.unlock();

                if (!res)
                    strcpy(m_url, "/log.html");
                else
                    strcpy(m_url, "/registerError.html");
            }
            else
                strcpy(m_url, "/registerError.html");
        }
        //如果是登录,直接判断
        //若浏览器端输入的用户名和密码在表中可以查找到,返回1,否则返回0
        else if (*(p + 1) == '2')
        {
            if (users.find(name) != users.end() && users[name] == password)
                strcpy(m_url, "/welcome.html");
            else
                strcpy(m_url, "/logError.html");
        }
    }

    //如果请求资源为/0,表示跳转注册界面
    if (*(p + 1) == '0')
    {
        char *m_url_real = (char *)malloc(sizeof(char) * 200);
        strcpy(m_url_real, "/register.html");
        //将网站目录和/register.html进行拼接,更新到m_real_file中
        strncpy(m_real_file + len, m_url_real, strlen(m_url_real));

        free(m_url_real);
    }
    //如果请求资源为/1,表示跳转登录界面
    else if (*(p + 1) == '1')
    {
        char *m_url_real = (char *)malloc(sizeof(char) * 200);
        strcpy(m_url_real, "/log.html");
        strncpy(m_real_file + len, m_url_real, strlen(m_url_real));

        free(m_url_real);
    }
    else if (*(p + 1) == '5')
    {
        char *m_url_real = (char *)malloc(sizeof(char) * 200);
        strcpy(m_url_real, "/picture.html");
        //将网站目录和/log.html进行拼接,更新到m_real_file中
        strncpy(m_real_file + len, m_url_real, strlen(m_url_real));

        free(m_url_real);
    }
    else if (*(p + 1) == '6')
    {
        char *m_url_real = (char *)malloc(sizeof(char) * 200);
        strcpy(m_url_real, "/video.html");
        strncpy(m_real_file + len, m_url_real, strlen(m_url_real));

        free(m_url_real);
    }
    else if (*(p + 1) == '7')
    {
        char *m_url_real = (char *)malloc(sizeof(char) * 200);
        strcpy(m_url_real, "/fans.html");
        strncpy(m_real_file + len, m_url_real, strlen(m_url_real));

        free(m_url_real);
    }
    else
        strncpy(m_real_file + len, m_url, FILENAME_LEN - len - 1);

    if (stat(m_real_file, &m_file_stat) < 0)
        return NO_RESOURCE;

    if (!(m_file_stat.st_mode & S_IROTH))
        return FORBIDDEN_REQUEST;

    if (S_ISDIR(m_file_stat.st_mode))
        return BAD_REQUEST;

    int fd = open(m_real_file, O_RDONLY);
    m_file_address = (char *)mmap(0, m_file_stat.st_size, PROT_READ, MAP_PRIVATE, fd, 0);
    close(fd);
    return FILE_REQUEST;
}

 代码结构

 

 HTTP请求的完全过程_艾伦lee的博客-CSDN博客

Linux网络编程:状态机_linux 状态机_才文嘉的博客-CSDN博客

从零开始:编写一个Web服务器---HTTP部分详细讲解以及代码实现(一)_oatpp_才文嘉的博客-CSDN博客

从零开始:编写一个Web服务器---HTTP部分详细讲解以及代码实现(二)_才文嘉的博客-CSDN博客

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值