[小项目]代码在线测试
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响应流程
![在这里插入图片描述](https://img-blog.csdnimg.cn/20190222170925291.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3lpa2Fvemh1ZGFwYW8=,size_16,color_FFFFFF,t_70)
CGI模式http响应流程:
![在这里插入图片描述](https://img-blog.csdnimg.cn/20190222172213919.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3lpa2Fvemh1ZGFwYW8=,size_16,color_FFFFFF,t_70)
清楚了大概流程也就可以动手写代码了
///
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