首先看一下HTTP请求报文的格式
不同于TCP或者IP报文的首部,由于HTTP的报文首部是文本形式的,我们就无法按字节来提取HTTP首部中的字段。所以要对HTTP请求报文做解析,目的是从文本中提取各字段的值。
从上图可以看出,HTTP请求报文由首部和正文两部分组成,他们由一个空行隔开,在这里我们仅对报文首部做解析。首部又有请求行和请求头两部分。
从图中可以看出报文的各字段都由一些特殊的字符分割,很容易可以联想到在编译原理中构造词法分析器时使用的有限状态自动机,将对于不同字段的识别过程设置为不同的状态,每当识别到分割字符时,就改变当前状态。这样将整个报文首部逐字符扫描一遍后,就可以解析出每个字段了。
状态间的转换过程很容易通过代码理解,这里就不给出相应的状态转换图了。下面给出程序代码,在这里仅对GET、POST和HEAD方法做了识别,其他方法则识别为UNKNOWN。
参考资料:
nginx-0.1.0源码
开源项目zaver https://github.com/zyearn/zaver/blob/master/src/http_parse.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#define BUFFER_SIZE 4096
#define HTTP_INVALID_METHOD 1
#define HTTP_INVALID_REQUEST 2
#define HTTP_INVALID_HEAD 3
#define HTTP_UNKNOWN 1
#define HTTP_GET 2
#define HTTP_HEAD 3
#define HTTP_POST 4
#define CR '\r'
#define LF '\n'
//请求头结构
typedef struct http_request_header_s{
void *key;
void *key_end;
void *value;
void *value_end;
struct http_request_header_s *next;
} http_request_header_t;
//请求结构
typedef struct {
int fd;
char buffer[BUFFER_SIZE];
size_t check_index;
size_t read_index;
int state;
//请求行
void *request_start;
void *request_end;
int method;
void *method_start;
void *method_end;
void *uri;
void *uri_end;
int major_digit;
int minor_digit;
//请求头部
http_request_header_t *header_list; //头部字段链表
void *cur_key;
void *cur_key_end;
void *cur_value;
void *cur_value_end;
} http_request_t;
//向请求头链表添加新结点
void http_header_list_add(http_request_header_t **head, http_request_header_t *p)
{
if(*head == NULL){
*head = p;
p->next = NULL;
return;
}
http_request_header_t *t = (*head)->next;
(*head)->next = p;
p->next = t;
}
//解析请求行
int http_parse_request_line(http_request_t *request)
{
enum {
sw_start = 0,
sw_method,
sw_spaces_before_uri,
sw_after_slash_in_uri,
sw_http,
sw_http_H,
sw_http_HT,
sw_http_HTT,
sw_http_HTTP,
sw_first_major_digit,
sw_major_digit,
sw_first_minor_digit,
sw_minor_digit,
sw_spaces_after_digit,
sw_almost_done
} state;
state = request->state;
char ch, *p;
size_t pi;
for(pi = request->check_index; pi < request->read_index; pi++){
p = &request->buffer[pi];
ch = *p;
switch(state){
case sw_start :{
request->request_start = p;
if(ch == CR || ch == LF)
break;
if(ch == ' ')
break;
if(ch < 'A' || ch > 'Z')
return HTTP_INVALID_REQUEST;
request->method_start = p;
state = sw_method;
break;
}
case sw_method :{