#include <stdio.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <ctype.h>
#include <strings.h>
#include <string.h>
#include <sys/stat.h>
//#include <pthread.h>
#include <sys/wait.h>
#include <stdlib.h>
#include <stdint.h>
#define ISspace(x) isspace((int)(x))
#define SERVER_STRING "Server: jdbhttpd/0.1.0\r\n"
#define STDIN 0
#define STDOUT 1
#define STDERR 2
void accept_request(void *);
void bad_request(int);
void cat(int, FILE *);
void cannot_execute(int);
void error_die(const char *);
void execute_cgi(int, const char *, const char *, const char *);
int get_line(int, char *, int);
void headers(int, const char *);
void not_found(int);
void serve_file(int, const char *);
int startup(u_short *);
void unimplemented(int);
void accept_request(void * arg){
/*
arg:一个已连接的套接字描述符
该函数接收客户端的请求,返回响应
*/
int client=(intptr_t)arg;
char buf[1024];
size_t numchars;
char method[255];
char url[255];
char path[512];
size_t i,j;
struct stat st;
int cgi=0;
char*query_string=NULL;
numchars=get_line(client,buf,sizeof(buf));
i=0;j=0;
while(!ISspace(buf[i])&&i<sizeof(method)-1){ //读取请求报文的方法后面加\0
method[i]=buf[i];
i++;
}
j=i;
method[i]='\0';
if(strcasecmp(method,"GET")&&strcasecmp(method,"POST")){ //如果方法不是GET和POST,发送501响应报文,返回
unimplemented(client);
return;
}
if(strcasecmp(method,"POST")==0)
cgi=1;
i=0;
while(ISspace(buf[j])&&(j<numchars))
j++;
while(!ISspace(buf[j])&&(i<sizof(url)-1)&&(j<numchars)){ //读取url
url[i]=buf[j];
i++;j++;
}
url[i]='\0';
if(strcasecmp(method,"GET")==0){
query_string=url;
while((*query_string!='?')&&(*query_string!='\0')){ //query_string 指向‘?’或‘\0'
query_string++;
}
if(*query_string=='?'){ //如果query_string所指字符为’?‘则将其设置为\0然后指向下一个字符
cgi=1;
*query_string='\0';
query_string++;
}
}
sprintf(path,"thdocs%s",url); //将url写入path,thdocs为该源文件目录下的thodocs文件夹,如path为thodocs/或thodocs/index.html
if(path[strlen(path)-1]=='/') //如果path最后一个字符为/则将入后缀index.html
strcat(path,"index.html");
if(stat(path,&st)==-1){
while((numchars>0)&&strcmp("\n",buf)) //获取文件的状态存于 st中,如果获取失败则向客户端发送没发现文件
numchars=get_line(client,buf,sizeof(buf));
not_found(client);
}
else{
if((st.st_mode & s_IFMT)==S_IFDIR) //如果path指向的是目录则添加/index.html
strcat(path,"/index.html");
if((st.st_mode & S_IXUSR)||(st.st_mode& S_IXGRP)||(st.st_mode & S_IXOTH)) //查看是否具有可执行权限,如果有则设cgi为1
cgi=1; //如果方法为POST或方法为GET但是url中包含?或url所指文件有可执行权限则设置cgi为1否则设置为0
if(!cgi)
serve_file(client,path); //如果cgi为0直接将访问的文件返回
else //否则执行访问文件将文件执行结果返回给客户端
execute_cgi(client,path,method,query_string);
}
close(client);
}
void serve_file(int client,const char *filename){
/*
client:客户套接字描述符
filename:访问文件路径
该函数发送响应报文,报文主体为访问文件
*/
FILE *resource=NULL;
int numchars=1;
char buf[1024];
buf[0]='A';
buf[1]='\0';
while((numchars>0)&&strcmp(buf,"\n")) //略过请求报文头部直到读到空行或读不到东西
{
numchars=get_line(client,buf,sizof(buf));
}
resource=fopen(filename,"r");
if(resource==NULL) //如果打不开资源则向客户端发送找不到文件报文
not_found(client);
else{
header(client,filename); //发送报文首部
cat(client,resource); //读文件,将文件写入到报文主体然后发送
}
fclose(resource);
}
void headers(int client,const char *filename){
/*
client:客户端套接字描述符
filename:访问文件路径
该函数发送响应报文的头部
*/
char buf[1024];
(void)filename;
strcpy(buf, "HTTP/1.0 200 OK\r\n");
send(client, buf, strlen(buf), 0);
strcpy(buf, SERVER_STRING);
send(client, buf, strlen(buf), 0);
sprintf(buf, "Content-Type: text/html\r\n");
send(client, buf, strlen(buf), 0);
strcpy(buf, "\r\n");
send(client, buf, strlen(buf), 0);
}
void cat(int client, FILE *resource)
{
/*
client:客户端套接字描述符
resource:访问文件指针
该函数读取文件并作为响应报文的主体发送给客户端
*/
char buf[1024];
fgets(buf, sizeof(buf), resource);
while (!feof(resource))
{
send(client, buf, strlen(buf), 0);
fgets(buf, sizeof(buf), resource);
}
}
void execute_cgi(int client,const char *path,const char* method,const char *query_string){
/*
client:客户套接字描述符
path:访问文件路径
method:请求报文方法
query_string:
该函数执行所访问文件,将文件的输出发送给客户端
*/
char buf[1024];
int cgi_output[2];
int cgi_input[2];
pid_t pid;
int status;
int i;
char c;
int numchars=1;
int content_length=-1;
buf[0]='A';buf[1]='\0';
if(strcasecmp(method,"GET")==0){
while(numchars>0&&strcmp(buf,"\n")) //略过报文头部到达报文主体
numchars=get_line(client,buf,sizeof(buf));
}
else if(strcasecmp(method,"POST")==0){
numchars=get_line(client,buf,sizeof(buf));
while(numchars>0&&strcmp(buf,"\n")){ //略过报文头部,并且检查有无content-length首部字段,如果有则解析出内容长度
buf[15]='\0';
if(strcmp(buf,"Content-Length:")==0)
content_length=atoi(buf+16);
numchars=getline(client,buf,sizeof(buf));
}
if(content_length==-1) //如果请求报文没有content-length首部字段则发送给客户端bad_request响应报文
bad_request(client);
return;
}
else{ //若果请求报文是其他方法则不处理,本服务器只能处理GET和POST两种方法
}
if(pipe(cgi_output)<0){connot_execute(client);return;} //生成用于进程间通信的管道用于父进程向子进程(所访问的文件)传送消息
if(pipe(cgi_input)<0){connot_execute(client);return;} //生成用于进程间通信的管道用于子进程向父进程传送消息
sprintf(buf,"HTTP/1.0 200 OK\r\n"); //发送响应报文首部
send(client,buf,strlen(buf),0);
if((pid=fork())<0){connot_execute(client);return;} //创建子进程
if(pid==0){ //子进程
char meth_env[255];
char query_env[255];
char length_env[255];
dup2(cgi_input[0],STDIN); //将输入管道的读端口和标准输出绑定(重定向)
dup2(cgi_output[1],STDOUT); //将输出管道的写端口和标准输出绑定
close(cgi_input[1]); //关闭输入管道的写端口
close(cgi_output[0]); //关闭输出管道的读端口
sprintf(meth_env,"REQUEST_METHOD=%s",method);
putenv(meth_env); //添加新环境变量
if(strcasecmp(method,"GET")==0){
sprintf(query_env,"QUERY_STRING=%s",query_string);
putenv(query_env); //如果是GET方法,添加此环境变量
}
else{
sprintf(length_env,"CONTENT_LENGTH=%d",content_length);
putenv(length_env); //如果是POST方法,添加此环境变量
}
execl(path,NULL); //执行访问文件
exit(0);
}
else{ //父进程
close(cgi_input[0]); //关闭输入管道读端口
close(cgi_output[1]); //关闭输入管道写端口
if(strcasecmp(method,"POST")==0){ //如果是POST方法,则将客户端发过来的请求报文的主体传给正在执行的访问文件进程
for(i=0;i<content_length;i++){
recv(client,&c,1,0);
write(cgi_input[1],&c,1);
}
}
while(read(cgi_output[0],&c,1)>0) //接收访问文件运行产生的信息,并将其发送给客户端
send(client,&c,1,0);
close(cgi_output[0]); //关闭输出管道读端口
close(cgi_input[1]); //关闭输入管道写端口
waitpid(pid,&status,0); //等待子进程,处理僵尸进程
}
}
void bad_request(int client)
{
/*
client:客户套接字接口
发送400响应报文
*/
char buf[1024];
sprintf(buf, "HTTP/1.0 400 BAD REQUEST\r\n");
send(client, buf, sizeof(buf), 0);
sprintf(buf, "Content-type: text/html\r\n");
send(client, buf, sizeof(buf), 0);
sprintf(buf, "\r\n");
send(client, buf, sizeof(buf), 0);
sprintf(buf, "<P>Your browser sent a bad request, ");
send(client, buf, sizeof(buf), 0);
sprintf(buf, "such as a POST without a Content-Length.\r\n");
send(client, buf, sizeof(buf), 0);
}
void unimplemented(int client){
/*
client:客户套接字接口
发送501响应报文
*/
char buf[1024];
sprintf(buf,"HTTP/1.0 501 Method Not Impementid\r\n");
send(client,buf,strlen(buf),0);
sprintf(buf,SERVER_STRING);
send(client,buf,strlen(buf),0);
sprintf(buf,"Content-Type: text/html\r\n");
send(client,buf,strlen(buf),0);
sprintf(buf,"\r\n");
send(client,buf,strlen(buf),0);
sprintf(buf, "<HTML><TITLE>Not Found</TITLE>\r\n");
send(client, buf, strlen(buf), 0);
sprintf(buf, "<BODY><P>The server could not fulfill\r\n");
send(client, buf, strlen(buf), 0);
sprintf(buf, "your request because the resource specified\r\n");
send(client, buf, strlen(buf), 0);
sprintf(buf, "is unavailable or nonexistent.\r\n");
send(client, buf, strlen(buf), 0);
sprintf(buf, "</BODY></HTML>\r\n");
send(client, buf, strlen(buf), 0);
}
void cannot_execute(int client){
/*
client:客户套接字接口
发送500响应报文
*/
char buf[1024];
sprintf(buf, "HTTP/1.0 500 Internal Server Error\r\n");
send(client, buf, strlen(buf), 0);
sprintf(buf, "Content-type: text/html\r\n");
send(client, buf, strlen(buf), 0);
sprintf(buf, "\r\n");
send(client, buf, strlen(buf), 0);
sprintf(buf, "<P>Error prohibited CGI execution.\r\n");
send(client, buf, strlen(buf), 0);
}
void not_found(int client)
{
/*
client:客户套接字接口
发送404响应报文
*/
char buf[1024];
sprintf(buf, "HTTP/1.0 404 NOT FOUND\r\n");
send(client, buf, strlen(buf), 0);
sprintf(buf, SERVER_STRING);
send(client, buf, strlen(buf), 0);
sprintf(buf, "Content-Type: text/html\r\n");
send(client, buf, strlen(buf), 0);
sprintf(buf, "\r\n");
send(client, buf, strlen(buf), 0);
sprintf(buf, "<HTML><TITLE>Not Found</TITLE>\r\n");
send(client, buf, strlen(buf), 0);
sprintf(buf, "<BODY><P>The server could not fulfill\r\n");
send(client, buf, strlen(buf), 0);
sprintf(buf, "your request because the resource specified\r\n");
send(client, buf, strlen(buf), 0);
sprintf(buf, "is unavailable or nonexistent.\r\n");
send(client, buf, strlen(buf), 0);
sprintf(buf, "</BODY></HTML>\r\n");
send(client, buf, strlen(buf), 0);
}
void erro_die(const char *sc){
/*
sc:错误原因
该函数打印错误原因并退出
*/
perror(sc);
exit(1);
}
int get_line(int sock,char *buf,int size){
/*
sock:客户套接字描述符
buf:用来存放一行字符
size:buf数组大小
读取一行以\0结尾
*/
int i=0;
char c='0';
int n;
while((i<size-1)&&(c!='\n')){ //当入到\n、\r\n、\r时将其换为\n最后加上\0作为一行
n=recv(sock,&c,1,0);
if(n>0){
if(c=='\r'){
n=recv(sock,&c,1,MSG_PEEK);
if(n>0&&c=='\n')
recv(sock,&c,1,0);
else
c='\n';
}
buf[i]=c;
i++;
}
else
c='\n';
}
buf[i]='\0';
return i;
}
int startup(u_short *port){
/*
port:服务器套接字要绑定的tcp端口
创建套接字,并对其进行绑定和转换为监听态
*/
int httpd=0; //服务器套接字描述符
int on=1;
struct sockaddr_in name; //待绑定套接字地址结构
httpd=socket(PF_INET,SOCK_STREAM,0); //创建套接字
if(httpd==-1){
error_die("socket");
}
memset(&name,0,sizeof(name)); //套接字地址清零
name.sin_family=AF_INET;
name.sin_port=htons(*port); //为何要转换为网络字节序???
name.sin_addr.s_addr=htonl(INADDR_ANY);
if((setsockopt(httpd,SOL_SOCKET,SO_REUSEADDR,&on,sizeof(on))<0)) //设置套接字选项
error_die("setsockopt failed");
if(bind(httpd,(struct sockaddr *) &name,sizeof(name))<0) //绑定套接字地址
error_die("bind");
if(*port == 0){ //如果为默认端口,取其端口号
socklen_t namelen=sizeof(name);
if(getsockname(httpd,(struct sockaddr*)&name,&namelen)==-1)
error_die("getsockname");
*port=ntohs(name.sin_port);
}
if(listen(httpd,5)<0) //设置为监听套接字
error_die("listen");
return(httpd);
}
int main(){
int server_sock=-1; //服务器套接字描述符
u_short port=4000;
int client_sock=-1; //已连接套接字
struct sockaddr_in client_name; //客户套接字结构
socklen_t client_name_len=sizeof(client_name);
server_sock=startup(&port); //创建套接字并设置为监听态,返回监听套接字描述符,同时返回绑定的端口号
printf("httpd runing on port %d\n",port);
while(1){
client_sock=accept(server_sock,(struct sockaddr*)&client_name,&client_name); //接收一个客户连接
if(client_sock==-1)
erro_die("accept");
accept_request(&client_sock); //对客户进行服务
}
close(server_sock); //关闭服务器套接字
return 0;
}