#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;
}
用有限狀態機處理http請求
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.