1 - 解析HTTP请求
1-1 URI基础知识
首先了解一下什么是URI:统一资源标志符URI就是在某一规则下能把一个资源独一无二地标识出来,可以理解为一个用于标识某一互联网资源名称的字符串。
常见的URL(就是浏览器中输入的网址,比如http://www.163.com就是一个URL)就是URI的一个子集,URI能够定位网络上的各种资源,包含HTML文档、图像、视频、程序等资源。
URL的主要组成如下:
- 协议
- 存储资源的主机IP地址(有时也包括端口号)
- 主机资源的具体地址
URI的主要组成如下:
- 访问资源的命名机制
- 存放资源的主机名
- 资源自身的名称,由路径表示
可以用下图表示:
对上面的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必须的部分
参考:
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
:指定要请求的资源所在的主机和端口号
- 请求体:也叫报文体。在请求头之后,以空格与请求头分隔。
下面以一个具体的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
参考:
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进行处理