linux C有限状态机解析HTTP请求

先看main函数执行的功能很简单,前半段是建立TCP连接的常规操作,每次读取4096字节的数据到缓存区,解析这4096个数据,根据解析状态返回相应结果。

int main(int argc, char** argv) 
{
	if (argc <= 2) {
		printf("usage: %s ip_address port_number\n", basename(argv[0]));
		return 1;
	}
	const char* ip = argv[1];         // ip
	int port = atoi(argv[2]);         // 端口
	struct sockaddr_in address;
	bzero(&address, sizeof(address));
	address.sin_family = AF_INET;
	address.sin_port = htons(port);
	inet_pton(AF_INET, ip, &address.sin_addr);

	int listenfd = socket(AF_INET, SOCK_STREAM, 0);
	assert(listenfd >= 0);

	int reuse = 1;
	int ret = setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof(reuse));

	ret = bind(listenfd, (struct sockaddr*) & address, sizeof(address));
	assert(ret != -1);

	ret = listen(listenfd, 5);
	assert(ret != -1);

	struct sockaddr_in client_address;
	socklen_t client_addrlength = sizeof(client_address);
	int fd = accept(listenfd, (struct sockaddr*) & client_address, &client_addrlength);
	if (fd < 0) printf("errno is: %d\n", errno);
	else {
		char buffer[BUFFER_SIZE];
		memset(buffer, 0, sizeof(buffer));
		int data_read = 0;          // 已经接收的字符数
		int read_index = 0;         // 已经读取了多少字节
		int checked_index = 0;      // 已经分析完了多少字节
		int start_line = 0;         // 行在buffer中的起始位置
		CHECK_STATE checkstate = CHECK_STATE_REQUESTLINE;   // 正在分析请求行
		while (1) {
			// 读取BUFFER_SIZE大小的数据到缓存区buffer
			data_read = recv(fd, buffer + read_index, BUFFER_SIZE - read_index, 0);
			if (data_read == -1) {
				printf("reading failed\n");
				break;
			}
			else if (data_read == 0) {
				printf("remote client has closed the connection\n");
				break;
			}
			read_index += data_read;
			// 解析缓存区的数据,获得HTTP状态码
			HTTP_CODE result = parse_content(buffer, checked_index, checkstate, read_index, start_line);
			if (result == NO_REQUEST) continue;
			else if (result == GET_REQUEST) {
				// 如果请求状态正确,发送响应正确的字符串给浏览器
				send(fd, szret[0], strlen(szret[0]), 0);
				break;
			}
			else {
				// 如果请求状态错误,发送响应错误的字符串给浏览器
				send(fd, szret[1], strlen(szret[1]), 0);
				break;
			}
		}
		close(fd);
	}
	close(listenfd);
	return 0;
}

szert是字符串数组,比较简单

static const char* szret[] = { "I get a correct result\n", "Something wrong\n" };

这里的状态定义有

//主状态机的两种状态,当前正在分析请求行和正在分析头部字段
enum CHECK_STATE { CHECK_STATE_REQUESTLINE = 0, CHECK_STATE_HEADER };

HTTP状态码有以下几种

//服务器处理HTTP请求的结果:NO_REQUEST表示请求不完整,需要读取客户数据;
//                          GET_REQUEST表示获得了一个完整的客户请求;
//                          BAD_REQUEST表示客户请求有语法错误;
//                          FORBIDDEN_REQUEST表示客户对资源没有足够的访问权限
//                          INTERNAL_ERROR表示服务器内部错误;
//                          CLOSED_CONNECTION表示客户端已经关闭连接。
enum HTTP_CODE { NO_REQUEST, GET_REQUEST, BAD_REQUEST, FORBIDDEN_REQUEST, INTERNAL_ERROR, CLOSED_CONNECTION };
HTTP_CODE parse_content(char* buffer, int& checked_index, CHECK_STATE& checkstate, int& read_index, int& start_line) {
	LINE_STATUS linestatus = LINE_OK;              // 从状态机状态
	HTTP_CODE retcode = NO_REQUEST;                // HTTP code
	while ((linestatus = parse_line(buffer, checked_index, read_index)) == LINE_OK) {
		//当解析完一行后
		char* temp = buffer + start_line;
		start_line = checked_index;
		switch (checkstate) {
		case CHECK_STATE_REQUESTLINE: {
			// 正在分析请求行
			retcode = parse_requestline(temp, checkstate);
			if (retcode == BAD_REQUEST) return BAD_REQUEST;
			break;
		}
		case CHECK_STATE_HEADER: {
			// 正在分析头部字段
			retcode = parse_headers(temp);
			if (retcode == BAD_REQUEST) return BAD_REQUEST;
			else if (retcode == GET_REQUEST) return GET_REQUEST;
			break;
		}
		default: {
			return INTERNAL_ERROR;
		}
		}
	}
	if (linestatus == LINE_OPEN) return NO_REQUEST;         // 如果分析成功。
	else return BAD_REQUEST;
}

 

从状态机的状态

//从状态机的三种可能状态,即行的读取状态:读取到一个完整的行、行出错和行数据暂且不完整
enum LINE_STATUS { LINE_OK = 0, LINE_BAD, LINE_OPEN };

HTTP请求都是\r\n结尾的。

//从状态机,用于解析一行内容
/*
buffer 是缓冲区字符串
checked_index 已经分析完的字符个数
read_index 已经读取字符个数
*/
LINE_STATUS parse_line(char* buffer, int& checked_index, int& read_index) {
	//checked_id_index指向buffer的正在分析的字节,read_index指向buffer中的最后一个字节的下一个字节
	//即从0~checked_index是已分析完毕,checked_index~read_index-1待分析
	char temp;
	for (; checked_index < read_index; ++checked_index) {
		temp = buffer[checked_index];
		//如果当前是回车符,则说明可能读取到了一个完整行
		//如果是'\n',即换行符,也说明可能读取到了一个完整行
		if (temp == '\r') {
			//如果当前是本行最后一个字符,则说明不完整,需要更多数据
			//如果下一个字符是'\n'则说明读取到了完整的行
			//否则说明HTTP请求存在语法问题
			if (checked_index + 1 == read_index) {
				return LINE_OPEN;
			}
			else if (buffer[checked_index + 1] == '\n') {
				buffer[checked_index++] = '\0';
				buffer[checked_index++] = '\0';
				return LINE_OK;
			}
			else return LINE_BAD;
		}
		else if (temp == '\n') {
			if ((checked_index > 1) && (buffer[checked_index - 1] == '\r')) {
				buffer[checked_index - 1] = '\0';
				buffer[checked_index++] = '\0';
				return LINE_OK;
			}
			return LINE_BAD;
		}
	}
	//如果到最后也没有发现'\r'字符,则返回LINE_OPEN表示需要读取更多数据分析
	return LINE_OPEN;
}

分析请求行,关键是解析出来method,url和http协议对应的版本。

//分析请求行
HTTP_CODE parse_requestline(char* temp, CHECK_STATE& checkstate) {
	//如果请求行中没有空格和'\t'字符则说明HTTP请求有问题
	//strpbrk返回前面缓冲区第一个在后面字符集合中的字符位置
	char* url = strpbrk(temp, " \t");
	if (!url) return BAD_REQUEST;
	*url++ = '\0';

	//strcasecmp与strcmp的区别就是不区分大小写
	char* method = temp;
	if (strcasecmp(method, "GET") == 0) printf("The request method is GET\n");
	else return BAD_REQUEST;

	//strspn函数统计缓冲区前面多少个连续字符在字符集合中
	url += strspn(url, "\t");
	char* version = strpbrk(url, " \t");
	if (!version) return BAD_REQUEST;

	*version++ = '\0';
	version += strspn(version, " \t");

	//strchr函数返回缓冲区里第一个后面字符的位置
	if (strcasecmp(version, "HTTP/1.1") != 0) {
		url += 7;
		url = strchr(url, '/');
	}

	if (!url || url[0] != '/') return BAD_REQUEST;
	printf("The request URL is: %s\n", url);
	checkstate = CHECK_STATE_HEADER;
	return NO_REQUEST;
}

分析头部请求行数

//分析头部
HTTP_CODE parse_headers(char* temp) {
	//遇到空行说明得到了一个正确的HTTP请求
	if (temp[0] == '\0') return GET_REQUEST;
	else if (strncasecmp(temp, "Host:", 5) == 0) {
		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;
}

从状态机的状态分析

字符串解析过程

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值