在线代码测试小项目

本文介绍了一个基于HTTP协议的小型代码在线测试服务器的实现过程。通过分析HTTP请求与响应的结构,利用多线程处理并发请求,并采用CGI技术在服务器端运行用户提交的代码,最后返回运行结果。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

[小项目]代码在线测试

http是我们生活中最常使用的协议,现如今网络浏览器越来越贴近人们的生活,使得做什么事都很方便,但是想要运行一段代码还得需要在电脑指定的环境下来运行,这在有些情况下让人很抓狂,我在网上也看到过很多代码在线测试的网页,感觉还不错,但是还是不知道这背后具体是怎麽实现的,有幸在网上看到过tinyhttp的源码,便想着自己参照着实现一个小型的http服务器来做一个代码测试服务器。

在做服务器之前先要了解http的相关知识,http是一个大量使用的应用层协议,本身的格式分为首行,请求头,空行和正文。

  • 首行中又可以具体分为请求方法,请求uri,http协议及版本号。请求方法多种多样,但是最常用的还是GET和POST,这两种请求方法都是对服务器进行资源请求,并且都可以给服务器传递参数,不过GET方法是直接在URL之后直接追加参数,POST是将参数写在正文之中,相比之下,POST方法更为安全。请求uri主要是标识了具体想请求的资源,常见的就是一个服务器的主页/index,协议及版本号具体标识了所使用http的版本。
  • 请求头则是一个又一个的键值对,用来说明本次http请求的必要信息,比如请求格式,字符编码,用户信息,标识正文大小的Content-Length等,每一个键值对以\r\n结尾
  • 空行没有任何信息,只是用来分割请求头与正文的一个标识,也是一个\r\n
  • 正文是本次http请求的具体内容,可以存放POST所要提交的数据以及其他内容

既然是服务器,就需要对客户端做出响应,http的响应报文也是有四个部分,状态行,消息头,空行,响应正文。具体内容与请求报文区别不大,这里不再多说

在知道了http结构之后,我们就需要分析如何对http请求做出响应,从浏览器得到的信息是整个http报文,需要从其中提取有效信息构造响应报文,我们的目的是让服务器对所提交的代码在后台运行并返回结果,一个进程肯定满足不了我们的需求,所以http服务器可以使用多进程或者多线程,既然服务器要将代码的结果返回必须要在服务器中运行,这就需要使用CGI技术

  • CGI(Common Gateway Interface)是www最终要的技术之一,是CGI程序和服务器之间传递信息的过程,浏览器可以从服务器上下载资源,并且也可以向服务器提交资源(比如代码),这样http服务器与浏览器之间可以通过CGI来进行交互,GET方式如果没有传入参数就可以按照一般的方式进行返回资源,但是如果有参数传入,http就需要按照CGI方式处理参数,并将执行结果返回给浏览器,POST方法一般都需要使用CGI来处理。

常规模式http响应流程
在这里插入图片描述
CGI模式http响应流程:
在这里插入图片描述
清楚了大概流程也就可以动手写代码了

///////////////////////////////////////////////////////////////
void usage(const char* );
int startup(int );
int getLine(int, char*, int);
void clearHeaer(int );
void show_400(int );
void show_404(int );
void show_500(int );
void echoErrMsg(int , int );
int exec_cgi(int , char*, char*, char* );
int echo_www(int , char*, int );
int handlerRequest(int );
///////////////////////////////////////////////////////////////

void usage(const char *proc)
{
	printf("Usage %s [port]\n", proc);
}

int startup(int port)
{
	int sock = socket(AF_INET, SOCK_STREAM, 0);
	if(sock < 0){
		perror("socket");
		exit(2);
	}

	int opt = 1;
	setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));

	struct sockaddr_in local;
	local.sin_family = AF_INET;
	local.sin_port = htons(port);
	local.sin_addr.s_addr = htonl(INADDR_ANY);

	if(bind(sock, (struct sockaddr*)&local, sizeof(local)) < 0){
		perror("bind");
		exit(3);
	}

	if(listen(sock, 5) < 0){
		perror("listen");
		exit(4);
	}

	return sock;
}

int getLine(int sock, char line[], int len)
{
	char c = '\0';
	int i = 0;
	while(c != '\n' && i < len-1){
		recv(sock, &c, 1, 0);
		if(c == '\r'){
			recv(sock, &c, 1, MSG_PEEK); //窥探功能
			if(c == '\n'){
				recv(sock, &c, 1, 0); //覆盖 \r 为 \n
			}else{
				c = '\n';
			}
		}
		//\r\n \r->\n
		line[i++] = c;
	}
	line[i] = '\0';

	return i;
}

void clearHeaer(int sock) //清理头部
{
	char line[MAX];
	do{
		getLine(sock, line, sizeof(line));
	}while(strcmp("\n", line));
}

void show_400(int sock) 
{
	char line[MAX];
	struct stat st;
	sprintf(line, "HTTP/1.0 400 Bad Request\r\n");
	send(sock, line, strlen(line), 0);
	sprintf(line, "Content-Type: text/html;charset=utf-8\r\n");
	send(sock, line, strlen(line), 0);
	sprintf(line, "\r\n");
	send(sock, line, strlen(line), 0);

	int fd = open(PAGE_400, O_RDONLY);

	stat(PAGE_400, &st);
	sendfile(sock, fd, NULL, st.st_size);
	close(fd);
}

void show_404(int sock)
{
	char line[MAX];
	struct stat st;
	sprintf(line, "HTTP/1.0 404 Not Found\r\n");
	send(sock, line, strlen(line), 0);
	sprintf(line, "Content-Type: text/html;charset=uft-8\r\n");
	send(sock, line, strlen(line), 0);
	sprintf(line, "\r\n");
	send(sock, line, strlen(line), 0);

	int fd = open(PAGE_404, O_RDONLY);

	stat(PAGE_404, &st);
	sendfile(sock, fd, NULL, st.st_size);
	close(fd);
}

void show_500(int sock) 
{
	char line[MAX];
	struct stat st;
	sprintf(line, "HTTP/1.0 500 Internal Server Error\r\n");
	send(sock, line, strlen(line), 0);
	sprintf(line, "Content-Type: text/html;charset=utf-8\r\n");
	send(sock, line, strlen(line), 0);
	sprintf(line, "\r\n");
	send(sock, line, strlen(line), 0);

	int fd = open(PAGE_500, O_RDONLY);

	stat(PAGE_500, &st);
	sendfile(sock, fd, NULL, st.st_size);
	close(fd);
}

void echoErrMsg(int sock, int status_code)
{
	switch(status_code){
		case 400:
			show_400(sock);
			break;
		case 404:
			show_404(sock);
			break;
		case 500:
			show_500(sock);
			break;
		default:
			break;
	}
}

int exec_cgi(int sock, char *method, char *path, char *query_string)
{
	char line[MAX];
	int content_length = -1;
	char method_env[MAX/32];
	char query_string_env[MAX];
	char content_length_env[MAX/8];

	if(strcasecmp(method, "GET") == 0){
		clearHeaer(sock); //清理头部
	}
	else{ //POST
		do{
			getLine(sock, line, sizeof(line));
			//Content-Length: xxx
			if(strncasecmp(line, "Content-Length: ", 16) == 0){
				content_length = atoi(line + 16);
			}
		}while(strcmp("\n", line));
		if(content_length == -1){
			return 400;
		}
	}

	int input[2];
	int output[2];

	//管道站在子进程角度
	pipe(input);
	pipe(output);

	pid_t id = fork();
	if(id < 0){
		LOG(ERROR, "fork error");
		return 500;
	}
	else if(id == 0){
		close(input[1]);
		close(output[0]);

		dup2(input[0], 0);
		dup2(output[1], 1);
		dup2(output[1], 2);

		sprintf(method_env, "METHOD=%s", method);
		putenv(method_env);
		if(strcasecmp(method, "GET") == 0){
			sprintf(query_string_env, "QUERY_STRING=%s", query_string);
			putenv(query_string_env);
		}
		else{
			sprintf(content_length_env, "CONTENT_LENGTH=%d", content_length);
			putenv(content_length_env);
		}

		execl(path, path, NULL);
		exit(1);
	}
	else{
		close(input[0]);
		close(output[1]);

		sprintf(line, "HTTP/1.0 200 OK\r\n");
		send(sock, line, strlen(line), 0);
		sprintf(line, "Content-type: text/html;charset=utf-8\r\n");
		send(sock, line, strlen(line), 0);
		sprintf(line, "\r\n");
		send(sock, line, strlen(line), 0);

		int i = 0;
		char c;
		if(strcasecmp(method, "POST") == 0){
			for(; i < content_length; i++){
				recv(sock, &c, 1, 0);
				write(input[1], &c, 1);
			}
		}

		while(read(output[0], &c, 1) > 0){
			send(sock, &c, 1, 0);
		}

		waitpid(id, NULL, 0);
		close(input[1]);
		close(output[0]);
	}

	return 200;
}

int echo_www(int sock, char *path, int size)
{
	char line[MAX];
	clearHeaer(sock);

	int fd = open(path, O_RDONLY);
	if(fd < 0){
		return 404;
	}

	sprintf(line, "HTTP/1.0 200 OK\r\n");
	send(sock, line, strlen(line), 0);
	
	if(strcasecmp("html", path+strlen(path)-4) == 0){
	    sprintf(line, "Content-type: text/html;charset=utf-8\r\n");
    } else if (strcasecmp("js", path+strlen(path)-2) == 0){
	    sprintf(line, "Content-type: application/x-javascript;charset=utf-8\r\n");
	} else if (strcasecmp("css", path+strlen(path)-3) == 0){
	    sprintf(line, "Content-type: text/css;charset=utf-8\r\n");
	}
	send(sock, line, strlen(line), 0);
	
	sprintf(line, "\r\n");
	send(sock, line, strlen(line), 0);

	//发送正文,文件内容  sendfile, 拷贝两个文件描述符的内容
	sendfile(sock, fd, NULL, size);

	close(fd);

	return 200;
}

int handlerRequest(int sock) //请求行
{
	int status_code = 200;
	char line[MAX];
	char method[MAX/16] = {0};
	char url[MAX] = {0};
	char path[MAX];
	int cgi = 0;
	char *query_string = NULL;

	getLine(sock, line, sizeof(line));
	//printf("%s", line);

	LOG(INFO, "Start parsing the first line");

	size_t i = 0;
	size_t j = 0;
	while(i < sizeof(method)-1 && j < sizeof(line) && \
          !isspace(line[j])){
		method[i] = line[j];
        i++, j++;
	}
	method[i] = '\0';

	while(j < sizeof(line) && isspace(line[j])){
		j++;
	}

	i = 0;
	while(i < sizeof(url)-1 && j < sizeof(line) && !isspace(line[j])){
		url[i] = line[j];
        i++, j++;
	}
	url[i] = '\0'; 
	//printf("method: %s, url: %s\n", method, url);
	LOG(INFO, "frst line parse done");

	//忽略大小写比较
	//有传参,模式改为cgi
	//get 有传参,是通过url传参
	//post 传参是将参数填充至报文的正文部分, 传敏感数据时使用
	if(strcasecmp(method, "GET") == 0){
	}
	else if(strcasecmp(method, "POST") == 0){
		cgi = 1;
	}
	else{ //method error
		status_code = 400;
		clearHeaer(sock); //清理头部
		LOG(WARNING, "method error");
		goto end;
	}
	LOG(INFO, "method parse done");

	if(strcasecmp(method, "GET") == 0){
		query_string = url;
		while(*query_string){
			if(*query_string == '?'){
				cgi = 1; //传参设置
				*query_string = '\0';
				query_string++;
				break;
			}
			query_string++;
		}
	}

	//   [wwwroot]/a/b/c\0x=100&y=200
	sprintf(path, "wwwroot%s", url); //格式化输出到path
	if(path[strlen(path)-1] == '/'){
		strcat(path, HOME_PAGE); //给出默认首页
	}

	struct stat st;
	//判断访问资源是否存在, 函数stat
	if(stat(path, &st) < 0){
		status_code = 404;
		clearHeaer(sock);
		LOG(WARNING, "path not found");
		goto end;
	}
	else{
		//   [wwwroot]/a/b/c \0 x=100&y=200
		if(S_ISDIR(st.st_mode)){
			strcat(path, "/");
			strcat(path, HOME_PAGE);
		}else if((st.st_mode&S_IXUSR) || (st.st_mode&S_IXGRP) || (st.st_mode&S_IXOTH)){
			cgi = 1;
		}

		//method, path, cgi, get->query_string
		LOG(INFO, "starting responce");
		if(cgi == 1){
			status_code = exec_cgi(sock, method, path, query_string);
		}else{
			status_code = echo_www(sock, path, st.st_size); //响应资源, 此处是get方法
		}
	}
	

end:
	//status_code 204:请求资源存在,但是为空(成功)
	//			  206:局部请求
	//			  301:永久性重定向
	//			  302:临时性重定向
	//			  400:请求报文中存在语法错误
	//			  403:请求资源被服务器拒绝
	//			  404:请求资源不存在(error)
	//			  500:服务器执行发生错误
	//			  503:服务器处于超负载或停机维护状态
	if(status_code != 200){
		echoErrMsg(sock, status_code);
	}
	LOG(INFO, "close sock");
	close(sock);
	return status_code;
}

int main(int argc, char *argv[])
{
	if(argc != 2){
		usage(argv[0]);
		return 1;
	}

	signal(SIGPIPE, SIG_IGN);

	LOG(INFO, "Start server");
	int listen_sock = startup(atoi(argv[1]));


	for( ; ; ){
		struct sockaddr_in client;
		socklen_t len = sizeof(client);
		int sock = accept(listen_sock, (struct sockaddr*)&client, &len);
		if(sock < 0){
			perror("accept");
			continue;
		}

		LOG(INFO, "get a new link, handlerRequest...");

		Task t_;
		threadPoll *tp_ = new threadPoll();
		tp_->initPthread();
		t_.setTask(sock, handlerRequest);
		tp_->pushTask(t_);
	}
}

项目地址:https://github.com/Gzmy/project/tree/master/codeOnline

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值