关闭

htttp服务器及完整注释

标签: http
88人阅读 评论(0) 收藏 举报
分类:


#include "httpd.h"

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

static void not_found(int client)
{
}
void print_debug(const char * msg)
{
#ifdef _DEBUG_
	printf("%s\n", msg);
#endif
}

static void bad_request(int client)
{
	print_debug("enter our fault...\n");
	char buf[1024];
	sprintf(buf, "HTTP/1.0 400 BAD REQUEST\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, "<html></br><p>your enter message is a bad request</p></br></html>\r\n");
	send(client, buf, strlen(buf), 0);
}


void print_log(const char *fun, int line, int err_no,  const char *err_str)
{
	printf("[%s: %d] [%d] [%s]\n", fun, line, err_no, err_str);
}

void clear_header(int client)
{
	char buf[1024];
	memset(buf, '\0', sizeof(buf));
	int ret = 0;
	do{
		ret = get_line(client, buf, sizeof(buf));
	}while(ret > 0 && strcmp(buf, "\n") != 0 );
}

//return num char, success
//return <= 0, failed
int get_line(int sock, char *buf, int max_len)
{
	if( !buf || max_len < 0){
		return -1;
	}
	int i = 0;
	int n = 0;
	char c = '\0';
	while( i < max_len-1 && c != '\n' ){
		n = recv(sock, &c, 1, 0);
		if(n > 0){//success
			if( c == '\r' ){
				n = recv(sock, &c, 1,MSG_PEEK);
				if( n > 0 && c == '\n' ){//windows
					recv(sock, &c, 1, 0);//delete
				}else{
					c = '\n';
				}
			}
			buf[i++] = c;
		}else{//failed
			c = '\n';
		}
	}
	buf[i] = '\0';
	return i;
}

void echo_error_to_client(int client, int error_code)
{
	switch(error_code){
		case 400://request error
			bad_request(client);
			break;
		case 404://not found
			not_found(client);
			break;
		case 500://server error
	//		server_error(client);
			break;
		case 503://server unavailable
//			server_unavailable(client);
			break;
		//.....................
		default:
//			default_error(client);
			break;
	}
}

void echo_html(int client, const char *path, unsigned int file_size)
{
	if( !path ){
		return;
	}
	int in_fd = open(path, O_RDONLY);//以只读的形式打开
	if(in_fd < 0){
		print_debug("open index.html error");
		//echo_error_to_client();
		return;
	}
	print_debug("open index.html success");
	char echo_line[1024];
	memset(echo_line, '\0', sizeof(echo_line));
	strncpy(echo_line, HTTP_VERSION, strlen(HTTP_VERSION)+1);
	strcat(echo_line, " 200 OK");
	strcat(echo_line, "\r\n\r\n");
	send(client, echo_line,strlen(echo_line), 0);
	print_debug("send echo head success");
	if( sendfile(client, in_fd, NULL, file_size) < 0 ){
		print_debug("send_file error");
		//echo_error_to_client();
		close(in_fd);
		return;
	}
	print_debug("sendfile success");
	close(in_fd);
}

void exe_cgi(int sock_client, const char *path, const char *method,const char *query_string)
{
	print_debug("enter cgi\n");
	char buf[_COMM_SIZE_];
	int numchars = 0;
	int content_length = -1;
	//pipe
	int cgi_input[2] = {0, 0};
	int cgi_output[2] = {0, 0};
	//child proc
	pid_t id;

	print_debug(method);

	if(strcasecmp(method, "GET") == 0){//GET
		clear_header(sock_client);
	}else{//POST
		do{
			memset(buf, '\0', sizeof(buf));
			numchars = get_line(sock_client, buf, sizeof(buf));
			if(strncasecmp(buf, "Content-Length:", strlen("Content-Length:")) == 0){
				//函数定义:int strncasecmp(const char *s1, const char *s2, size_t n)
                //函数说明:strncasecmp()用来比较参数s1和s2字符串前n个字符,比较时会自动忽略大小写的差异。
				content_length = atoi(&buf[16]);
			}
		}while(numchars > 0 && strcmp(buf, "\n") != 0);//一行一行的比较直到寻找content_length
		if( content_length == -1 ){
			//echo_error_to_client();
			return;
		}
	}

	
	memset(buf, '\0', sizeof(buf));
	strcpy(buf, HTTP_VERSION);
	strcat(buf, " 200 OK\r\n\r\n");
	send(sock_client, buf, strlen(buf), 0);

	if( pipe(cgi_input) == -1 ){//pipe error
		//echo_error_to_client();
		return;
	}
	if( pipe(cgi_output) == -1 ){
		close(cgi_input[0]);
		close(cgi_input[1]);
		//echo_error_to_client();
		return;
	}

	if( (id = fork()) < 0){//fork error
		close(cgi_input[0]);
		close(cgi_input[1]);
		close(cgi_output[0]);
		close(cgi_output[1]);
		//echo_error_to_client();
		return;
	}else if( id == 0 ){//child
		char query_env[_COMM_SIZE_/10];
		char method_env[_COMM_SIZE_];
		char content_len_env[_COMM_SIZE_];
		memset(method_env, '\0', sizeof(method_env));
		memset(query_env, '\0', sizeof(query_env));
		memset(content_len_env, '\0', sizeof(content_len_env));

		close(cgi_input[1]);
		close(cgi_output[0]);
		//redir
		dup2(cgi_input[0], 0);
		dup2(cgi_output[1], 1);

		sprintf(method_env, "REQUEST_METHOD=%s", method);
		putenv(method_env);
		if(strcasecmp("GET", method) == 0){//POST
			sprintf(query_env, "QUERY_STRING=%s", query_string);
			putenv(query_env);
		}else{//POST
			sprintf(content_len_env, "CONTENT_LENGTH=%d", content_length);
			putenv(content_len_env);
		}

		execl(path, path, NULL);//exec函数族
		exit(1);
	}else{//father
		close(cgi_input[0]);
		close(cgi_output[1]);
		int i = 0;
		char c = '\0';
		if(strcasecmp("POST", method) == 0){
			for(; i < content_length; i++ ){
				recv(sock_client, &c, 1, 0);
				write(cgi_input[1], &c, 1);
			}
		}
		while( read(cgi_output[0], &c, 1) > 0 ){
			send(sock_client, &c, 1, 0);
		}
		close(cgi_input[1]);
		close(cgi_output[0]);

		waitpid(id, NULL, 0);
	}
}

//GET && POST
void *accept_request(void *arg)//这个arg实际上是由socket强转过来的那个参数
{

	print_debug("get a new connect...\n");
	pthread_detach(pthread_self());//detach //let the thread free automaticaly
	int sock_client = (int)arg;//将客户端套接字恢复回来
	//for test
	//echo_error_to_client(sock_client, 400);
	//close(sock_client);
	//return;
	
	int  cgi = 0;
	char *query_string = NULL;
	char method[_COMM_SIZE_/10];
	char url[_COMM_SIZE_];
	char buffer[_COMM_SIZE_];
	char path[_COMM_SIZE_];
	memset(method, '\0', sizeof(method));
	memset(url, '\0', sizeof(url));
	memset(buffer, '\0', sizeof(buffer));
	memset(path, '\0', sizeof(path));

//#ifdef _DEBUG_
//	//success  > 0
//	//else <= 0
//	while(get_line(sock_client, buffer, sizeof(buffer)) > 0){
//		printf("%s", buffer);
//		fflush(stdout);
//	}
//	printf("\n");
//#endif

	if(get_line(sock_client, buffer, sizeof(buffer)) < 0){//从客户端一行一行读取数据
		//echo_error_to_client();
		return NULL;
	}

	int i = 0; 
	int j = 0;//buffer line index
	while( !isspace(buffer[j]) &&\//从消息中获取方法保存到method
			i < sizeof(method)-1 &&\
			j < sizeof(buffer)){
		method[i] = buffer[j];
		i++, j++;
	}

	if( strcasecmp(method, "GET") && strcasecmp(method, "POST")){
		//echo_error_to_client();
		return NULL;
	}

	//clear space point useful url start
	while( isspace(buffer[j]) &&\//如果发现是空行就跳到下一行,指向url开始的地方
			j < sizeof(buffer)){
		j++;
	}

	//get url
	i = 0;
	while(!isspace(buffer[j]) &&\//获取url
			i < sizeof(url)-1 &&\
			j < sizeof(buffer)){
		url[i] = buffer[j];
		i++;j++;
	}

	print_debug(method);//打印方法和路径
	print_debug(url);

	if(strcasecmp(method, "POST") == 0){//如果是POST方法,就让cgi=1,是不是POST方法就直接可用呢
		cgi = 1;
	}

	if(strcasecmp(method, "GET") == 0){//那我们就可以稍微处理一下啦
		query_string = url;
		while( *query_string != '?' && *query_string != '\0'){
			query_string++;
		}
		
		if( *query_string == '?' ){//url = /XXX/XXX + arg
			*query_string = '\0';
			query_string++;
			cgi = 1;
		}
	}
	
	sprintf(path, "htdocs%s", url);//将url所在文件包装成路径保存在path中
	
	if(path[strlen(path)-1] == '/'){
		strcat(path, MAIN_PAGE);//最后在附上主页就好了
	}

	print_debug(path);

	struct stat st;
	if( stat(path, &st) < 0 ){ //failed, does not exist  // stat()用来将参数path 所指的文件状态, 复制到参数st所指的结构中
		print_debug("miss cgi");
		clear_header(sock_client);
		//echo_error_to_client();
	}else{//file exist!
		if(S_ISDIR(st.st_mode)){//如果是个目录文件追加/
			strcat(path, "/");
			strcat(path, MAIN_PAGE);
		}else if(st.st_mode & S_IXUSR ||\
				 st.st_mode & S_IXGRP ||\
				 st.st_mode & S_IXOTH){   //文件S_IRUSR (S_IREAD) 00400 文件所有者具可读取权限
                                         //S_IWUSR (S_IWRITE)00200 文件所有者具可写入权限
                                        //S_IXUSR (S_IEXEC) 00100 文件所有者具可执行权限
			cgi = 1;
		}else{
			//do nothing
		}
		if(cgi){
			exe_cgi(sock_client, path, method, query_string);
		}else{
			clear_header(sock_client);
			print_debug("begin enter our echo_html");
			echo_html(sock_client, path, st.st_size);
		}
	}
	close(sock_client);
	return NULL;
}

//if success return sock
//else exit process
int start(short port)
{
	int listen_sock = socket(AF_INET, SOCK_STREAM, 0);
	if(listen_sock == -1){
		print_log(__FUNCTION__, __LINE__, errno, strerror(errno));
		exit(1);
	}

	//reuse port//定义一个地址空间
	int flag = 1;
	setsockopt(listen_sock, SOL_SOCKET, SO_REUSEADDR, &flag, sizeof(flag));

	struct sockaddr_in local;
	local.sin_family = AF_INET;
	local.sin_port   = htons(port);//host -> net
	local.sin_addr.s_addr   = htonl(INADDR_ANY);
	socklen_t len = sizeof(local);
//将地址空间和定义的套接字绑定在一起
	if(bind(listen_sock, (struct sockaddr*)&local, len) == -1){
		print_log(__FUNCTION__, __LINE__, errno, strerror(errno));
		exit(2);
	}
//不断监听处理套接字的请求
	if(listen(listen_sock, _BACK_LOG_) == -1){
		print_log(__FUNCTION__, __LINE__, errno, strerror(errno));
		exit(3);
	}
//监听成功后返回监听套接字
	return listen_sock; //sucess
}

//./httpd port
int main(int argc, char *argv[])
{
	if(argc != 2){
		usage(argv[0]);
		exit(1);
	}
	//daemon();
	int port = atoi(argv[1]);
	int sock = start(port);//listen socket
	
	struct sockaddr_in client;
	socklen_t len = 0;
	while(1){
		int new_sock = accept(sock, (struct sockaddr*)&client, &len);//这个返回的新的套接字称为链接套接字,不同客户端的请求返回的
		                                                        //新socket自然不一样
		if( new_sock < 0 ){//accept error
			print_log(__FUNCTION__, __LINE__, errno, strerror(errno));
			continue;
		}
		pthread_t new_thread;//一旦有请求就定义一个新的线程去解决问题
		pthread_create(&new_thread, NULL, accept_request, (void*)new_sock);//转成void*后变成参数传给accept_request
	}
	return 0;
}






0
0

查看评论
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场
    个人资料
    • 访问:26968次
    • 积分:617
    • 等级:
    • 排名:千里之外
    • 原创:39篇
    • 转载:10篇
    • 译文:0篇
    • 评论:2条
    最新评论