WebServer代码解读(4)【解析HTTP请求】【解析请求行/请求头】【URI基础知识】

1 - 解析HTTP请求

1-1 URI基础知识

首先了解一下什么是URI:统一资源标志符URI就是在某一规则下能把一个资源独一无二地标识出来,可以理解为一个用于标识某一互联网资源名称的字符串。

常见的URL(就是浏览器中输入的网址,比如http://www.163.com就是一个URL)就是URI的一个子集,URI能够定位网络上的各种资源,包含HTML文档、图像、视频、程序等资源。

URL的主要组成如下:

  • 协议
  • 存储资源的主机IP地址(有时也包括端口号)
  • 主机资源的具体地址

URI的主要组成如下:

  • 访问资源的命名机制
  • 存放资源的主机名
  • 资源自身的名称,由路径表示

可以用下图表示:

img

对上面的URI进行分析:

  • 协议protocol:“http:”,这代表网页使用的是HTTP协议。在Internet中可以使用多种协议,如HTTP,FTP等等。在"HTTP"后面的“//”为分隔符
  • 主机名host(主机域名hostname+端口号port)
    • hostname:该URL的域名部分为“www.imooc.com”。一个URL中,也可以使用IP地址作为域名使用。
    • port:“8080”,跟在域名后面的是端口,域名和端口之间使用“:”作为分隔符。端口不是一个URL必须的部分,如果省略端口部分,将采用默认端口
  • 虚拟目录:从域名后的第一个“/”开始到最后一个“/”为止,是虚拟目录部分。虚拟目录也不是一个URL必须的部分。本例中没有虚拟目录。
  • 文件名:从域名后的最后一个“/”开始到“?”为止,是文件名部分,如果没有“?”,则是从域名后的最后一个“/”开始到“#”为止,是文件部分,如果没有“?”和“#”,那么从域名后的最后一个“/”开始到结束,都是文件名部分。例子中的文件名是"list.php"
  • 参数(search):从“?”开始到“#”为止之间的部分为参数部分,又称搜索部分、查询部分。可以允许有多个参数,参数与参数之间用“&”作为分隔符。
  • :从“#”开始到最后,都是锚部分。锚部分也不是一个URL必须的部分

参考:

URI和URL区别以及相对路径和绝对路径的区别

uri详解

HTTP协议、URL、URI、请求响应—讲解很详细

1-2 HTTP请求消息

client向server发送的HTTP请求(request)包含以下格式:

  • 请求行(request line):包含请求方法(GET/POST/HEAD/PUT/DELETE/CONNECT/OPTIONS/TRACE)、URL字段HTTP协议版本。请求行结束后,以’\r’进入下一行(请求头)。
  • 请求头(header):也叫报文头,有若干个属性,形式为key:value。**server根据请求头获取client信息。**属性主要有:
    • User-Agent:告诉server,client使用的操作系统、浏览器名称与版本
    • Accept:告诉server,client要接受什么类型的响应
    • Referer:表示当前请求是从哪个URL访问server的
    • Host:指定要请求的资源所在的主机和端口号
  • 请求体:也叫报文体。在请求头之后,以空格与请求头分隔。
img

下面以一个具体的request例子(GET)来分析:

GET /books/?sex=man&name=Professional HTTP/1.1
Host: www.wrox.com
User-Agent: Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.7.6) Gecko/20050225 Firefox/1.0.1
Connection: Keep-Alive

请求行:GET是请求方法,/books/?sex=man&name=Professional 是URL字段即要访问的资源,HTTP/1.1是HTTP版本

请求头:www.wrox.com是要GET信息的目的主机地址,Mozilla/5.0指的应该是火狐浏览器

下面再给一个POST例子:

POST / HTTP/1.1
Host: www.wrox.com
User-Agent: Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.7.6)
Gecko/20050225 Firefox/1.0.1
Content-Type: application/x-www-form-urlencoded
Content-Length: 40
Connection: Keep-Alive
(----此处空一行----)
name=Professional%20Ajax&publisher=Wiley

参考:

HTTP协议、URL、URI、请求响应—讲解很详细

http请求包含哪些

HTTP请求头和响应头详解

1-3 解析请求行

解析请求行的信息,包含请求方法(主要针对GET和POST)、URI、HTTP版本号

int requestData::parse_URI()
{
    
    
    string &str = content;
    // 读到完整的请求行再开始解析请求
    int pos = str.find('\r', now_read_pos);
    if (pos < 0)
    {
    
    
        return PARSE_URI_AGAIN;
    }
    // 将请求行取出(单独保存,并在原request中删掉)
    string request_line = str.substr(0, pos);
    if (str.size() > pos + 1)
        str = str.substr(pos + 1);
    else 
        str.clear();
    // Method:判断当前request是GET还是POST
    pos = request_line.find("GET");
    if (pos < 0)
    {
    
    
        pos = request_line.find("POST");
        if (pos < 0)
        {
    
    
            return PARSE_URI_ERROR;
        }
        else
        {
    
    
            method = METHOD_POST;
        }
    }
    else
    {
    
    
        method = METHOD_GET;
    }
    //printf("method = %d\n", method);
    
    // filename:找到要访问的URI
    pos = request_line.find("/", pos);
    if (pos < 0)
    {
    
    
        return PARSE_URI_ERROR;
    }
    else
    {
    
    
        int _pos = request_line.find(' ', pos);
        if (_pos < 0)
            return PARSE_URI_ERROR;
        else
        {
    
    	//URI以空格与HTTP版本号分隔,取出URI=>file_name
            if (_pos - pos > 1)
            {
    
    
                file_name = request_line.substr(pos + 1, _pos - pos - 1);
                int __pos = file_name.find('?');//URI的文件名从第一个 / 开始到第一个 ? 结束
                if (__pos >= 0)
                {
    
    
                    file_name = file_name.substr(0, __pos);
                }
            }
                
            else//默认文件名
                file_name = "index.html";
        }
        pos = _pos;
    }
    //cout << "file_name: " << file_name << endl;
    // HTTP 版本号:在请求行的结尾,“HTTP/1.1”,版本号有3个字符
    pos = request_line.find("/", pos);
    if (pos < 0)
    {
    
    
        return PARSE_URI_ERROR;
    }
    else
    {
    
    
        if (request_line.size() - pos <= 3)
        {
    
    
            return PARSE_URI_ERROR;
        }
        else
        {
    
    
            string ver = request_line.substr(pos + 1, 3);
            if (ver == "1.0")
                HTTPversion = HTTP_10;
            else if (ver == "1.1")
                HTTPversion = HTTP_11;
            else
                return PARSE_URI_ERROR;
        }
    }
    state = STATE_PARSE_HEADERS;
    return PARSE_URI_SUCCESS;
}

1-4 解析请求头

解析请求头,就是读取请求头中的各个属性(key:value形式),保存到一个map中方便取用

int requestData::parse_Headers()
{
    
    
    string &str = content;
    int key_start = -1, key_end = -1, value_start = -1, value_end = -1;
    int now_read_line_begin = 0;
    bool notFinish = true;
    for (int i = 0; i < str.size() && notFinish; ++i)
    {
    
    
        switch(h_state)
        {
    
    
            case h_start://记录key(如Host/User-Agent之类)开始位置
            {
    
    
                if (str[i] == '\n' || str[i] == '\r')
                    break;
                h_state = h_key;
                key_start = i;
                now_read_line_begin = i;
                break;
            }
            case h_key://记录key结束位置
            {
    
    
                if (str[i] == ':')//key以“: ”与value分隔,注意冒号后面有空格
                {
    
    
                    key_end = i;
                    if (key_end - key_start <= 0)
                        return PARSE_HEADER_ERROR;
                    h_state = h_colon;
                }
                else if (str[i] == '\n' || str[i] == '\r')
                    return PARSE_HEADER_ERROR;
                break;  
            }
            case h_colon://读取value
            {
    
    
                if (str[i] == ' ')
                {
    
    
                    h_state = h_spaces_after_colon;
                }
                else
                    return PARSE_HEADER_ERROR;
                break;  
            }
            case h_spaces_after_colon://记录value开始位置
            {
    
    
                h_state = h_value;
                value_start = i;
                break;  
            }
            case h_value://记录value结束位置
            {
    
    
                if (str[i] == '\r')
                {
    
    
                    h_state = h_CR;
                    value_end = i;
                    if (value_end - value_start <= 0)
                        return PARSE_HEADER_ERROR;
                }
                else if (i - value_start > 255)
                    return PARSE_HEADER_ERROR;
                break;  
            }
            case h_CR://将key value分别保存
            {
    
    
                if (str[i] == '\n')
                {
    
    
                    h_state = h_LF;
                    string key(str.begin() + key_start, str.begin() + key_end);
                    string value(str.begin() + value_start, str.begin() + value_end);
                    headers[key] = value;
                    now_read_line_begin = i;
                }
                else
                    return PARSE_HEADER_ERROR;
                break;  
            }
            case h_LF:
            {
    
    
                if (str[i] == '\r')
                {
    
    
                    h_state = h_end_CR;
                }
                else
                {
    
    
                    key_start = i;
                    h_state = h_key;
                }
                break;
            }
            case h_end_CR:
            {
    
    
                if (str[i] == '\n')
                {
    
    
                    h_state = h_end_LF;
                }
                else
                    return PARSE_HEADER_ERROR;
                break;
            }
            case h_end_LF://所有属性读取完毕
            {
    
    
                notFinish = false;
                key_start = i;
                now_read_line_begin = i;
                break;
            }
        }
    }
    //请求头读取完毕,从请求中删除
    if (h_state == h_end_LF)
    {
    
    
        str = str.substr(now_read_line_begin);
        return PARSE_HEADER_SUCCESS;
    }
    str = str.substr(now_read_line_begin);
    return PARSE_HEADER_AGAIN;
}

2 - 下一步工作

从HTTP请求中解析处请求行、请求头以后,下一步就是根据不同的请求类型,对request进行处理

Guess you like

Origin blog.csdn.net/weixin_44484715/article/details/117445768