用有限狀態機處理http請求

#include<stdio.h>
#include<sys/socket.h>
#include<sys/types.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
#include<arpa/inet.h>
//主狀態機有兩種狀態,當前正在分析請求行,當前正在分析頭部字段
enum CHECK_STATE{CHECK_STATE_REQUESTLINE = 0,CHECK_STATE_HEADER};
//從狀態機三種狀態,讀取完整一行,行出錯,行數據讀取不完整
enum LINE_STATUS{ LINE_OK = 0,LINE_BAD,LINE_OPEN};
/*服務器處理結果:NO_REQUEST表示請求不完整,需要繼續讀取客戶數據;GET_REQUEST表示獲得了一個完整的客戶端請求;
BAD_REQUEST表示客戶請求有語法錯誤;FORBIDDEN_REQUEST表示客戶對資源沒有足夠的訪問權限;INTERNAL_ERROR表示服務器內部錯誤;
 CLOSE_CONNECTION表示客戶端已關閉連接*/
enum HTTP_CODE{NO_REQUEST,GET_REQUEST,BAD_REQUEST,FORBIDDEN_REQUEST,INTERNAL_ERROR,CLOSED_CONNECTION};
//從狀態機解析出一行的內容,buf指向緩衝區,check_index指向當前正分析的字節,read_index指向緩衝區數據尾部的下一個字節
LINE_STATUS parse_line(char* buf,int& check_index,int& read_index)
{
	char temp;
	for(;check_index < read_index;check_index++)
	{
		//獲得當前要分析的字節
		temp = buf[check_index];
		if(temp == '\r')
		{
			if((check_index + 1) == read_index)
			{
				return LINE_OPEN;
			}
			else if(buf[check_index + 1] == '\n')
			{
				//將\r\n轉換爲\0方便後續處理
				buf[check_index++] = '\0';
				buf[check_index++] = '\0';
				return LINE_OK;
			}
			return LINE_BAD;
		}
		else if(temp == '\n')
		{
			if((check_index > 0) && buf[check_index - 1] == '\r')
			{
				//將\r\n轉換爲\0方便後續處理
				buf[check_index - 1] = '\0';
				buf[check_index++] = '\0';
				return LINE_OK;
			}
			return LINE_BAD;
		}
	}
	return LINE_OPEN;
}
//分析請求行
HTTP_CODE parse_requestline(char *temp,CHECK_STATE& checkstate)
{
	char *url = strpbrk(temp," \t");//返回temp字符串中第一個出現空白字符或者'/t'字符的位置
	if(!url)//若請求行中沒有空白字符或者'/t'字符,則http請求必有問題
	{
		return BAD_REQUEST;
	}
	*url++ = '\0';

	//分析請求方法是否爲GET方法
	char *p_1 = temp;
	if(strcasecmp(p_1,"GET") == 0)//忽略大小寫比較字符串
	{
		printf("The request method is GET\n");
	}
	else return BAD_REQUEST;

	url += strspn(url," \t");//清除前面多餘的空格
	char *p_2 = strpbrk(url," \t");
	if(!p_2)
	{
		return BAD_REQUEST;
	}
	*p_2++ = '\0';

	//分析協議版本字段的正確性
	p_2 += strspn(p_2," \t");
	if(strcasecmp(p_2,"HTTP/1.1") == 0)
	{
		printf("協議版本爲:%s\n",p_2);
	}
	else return BAD_REQUEST;

	//分析url的正確性
	if(strncasecmp(url,"http://",7) == 0)
	{
		url += 7;
		url = strchr(url,'/');
	}
	if(!url || url[0] != '/')//若http://後面沒有/字符或者url前七個字節不是"http://",則語法錯誤
	{
		return BAD_REQUEST;
	}
	printf("The request URL is: %s\n",url);

	//HTTP請求行處理完畢,狀態轉移到頭部字段進行分析
	checkstate = CHECK_STATE_HEADER;
	return NO_REQUEST;
}
//分析頭部字段
HTTP_CODE parse_headers(char *temp)
{
	if(temp[0] == '\0')//若出現空行,則得到了一個正確的http請求
		return GET_REQUEST;
	else if(strncasecmp(temp,"Host:",5) == 0)//處理host頭部字段
	{
		temp += 5;
		temp += strspn(temp," \t");
		printf("the request host is: %s\n",temp);
	}
	else{//不處理其他頭部字段
		printf("I can not handle this header\n");
	}
	return NO_REQUEST;
}
/*分析http請求入口函數*/
HTTP_CODE parse_content(char *buf,int &check_index,int &read_index,CHECK_STATE& checkstate,int& start_line)
{
	LINE_STATUS linestatus = LINE_OK;	//當前行的讀取狀態
	HTTP_CODE httpcode;					//http請求的處理狀態
	
	while( (linestatus = parse_line(buf,check_index,read_index)) == LINE_OK)	//讀取一行數據
	{
		char *temp = buf + start_line;	//定位行在buf中的位置
		start_line = check_index;		//更新行的位置
		switch(checkstate)				
		{
			case CHECK_STATE_REQUESTLINE:	//第一個狀態分析請求行
			{
				httpcode = parse_requestline(temp,checkstate);
				if(httpcode == BAD_REQUEST)
					return BAD_REQUEST;
				break;
			}
			case CHECK_STATE_HEADER:		//第二個狀態,分析頭部字段
			{
				httpcode = parse_headers(temp);
				if(httpcode == GET_REQUEST)
				{
					return GET_REQUEST;
				}
				break;
			}
			default:
			{
				return INTERNAL_ERROR;
			}
		}
	}
	//若沒有讀取到一個完整的行,則表示還需要繼續讀取客戶端數據才能進一步分析
	if(linestatus == LINE_OPEN)
	{
		return NO_REQUEST;
	}
	else{
		return BAD_REQUEST;
	}
}
int main(int argc,char *argv[])
{
	if(argc < 3)
	{
		printf("usage: %s ip port\n",argv[0]);
		exit(1);
	}
	char* ip = argv[1];
	int port = atoi(argv[2]);

	int listenfd,clifd;
	struct sockaddr_in ser_addr,cli_addr;
	socklen_t cli_len = sizeof(cli_addr);

	listenfd = socket(AF_INET,SOCK_STREAM,0);

	ser_addr.sin_family = AF_INET;
	ser_addr.sin_port = htons(port);
	inet_pton(AF_INET,ip,&ser_addr.sin_addr);
	bind(listenfd,(struct sockaddr*)&ser_addr,sizeof(ser_addr));

	listen(listenfd,3);
	clifd = accept(listenfd,(struct sockaddr*)&cli_addr,&cli_len);
	if(clifd == -1)
	{
		perror("accept error:");
		exit(1);
	}

	char buf[BUFSIZ];
	int read_num = 0;
	int read_index = 0;//當前已經讀入多少字節的客戶數據
	int check_index = 0;//當前已經分析完了多少字節的客戶數據
	int start_line = 0;//行在buf中的起始位置
	CHECK_STATE checkstate = CHECK_STATE_REQUESTLINE;//設置主狀態機的初始狀態
	while(1)//循環讀取數據並分析
	{
		read_num =read(clifd,buf+read_index,BUFSIZ-read_index);
		if(read_num == -1)
		{
			perror("read error:");
			break;
		}
		else if(read_num == 0)
		{
			printf("remote client has closed the connection\n");
			break;
		}
		read_index += read_num;

		//分析目前已經獲得的所有數據
		HTTP_CODE httpcode = parse_content(buf,check_index,read_index,checkstate,start_line);
		if(httpcode == NO_REQUEST)		//沒有獲得完整的數據
		{
			continue;
		}
		else if(httpcode == GET_REQUEST)//得到一個完整的、正確的HTTP請求
		{
			printf("we get a GET_REQUEST\n");
			break;
		}
		else{							//其他情況爲發生錯誤
			printf("we get a bad_request\n");
			break;
		}
	}
	close(clifd);
	close(listenfd);
	return 0;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章