[原创]自己动手写 HTTP Server

自己动手写 HTTP Server

 

【 原理 】 一般来说,HTTP Server 也是我们常说的Web服务器,大名鼎鼎的 Apache,还是微软的 IIS (Internet Information Server),开源领域的有 Lighttpd 和最近风头正劲的 Nginx 都是典型的Web服务器。最近想想能不能做一个Web服务器,就提供简单的功能,但是速度很快,能够作为一个专门处理 HTML/CSS/JS/Image 的Web服务器,那样能够让静态资源文件迅速的被访问到,如果有反向代理功能就更帅了,当然了,要是有Cache功能啥的,并且能够编写自定义插件(扩展)就很完美了。。。YY中。。。

基于这个思想,我就花十天时间使用标准C写了一个千行代码小型的HTTP Server,当然目前还不具有反向代理和扩展功能,只是能够简单的支持 HTML/CSS/JS/Image 静态资源文件的访问。HTTP Server 的名字叫做 tmhttpd - TieMa (Tiny&Mini) HTTP Server,小巧,代码少,执行速度快,目前具有的功能包括:  

  •  Support GET/HEAD method
  •  The common MIME types.
  •  Support Self custom default index page
  •  Directory listings.
  •  Support access log
  •  Support Self custom port and max clients
  •  Use fork mode accept new conntion
  •  Support daemon type work
  • more ..

目前已经发布了 tmhttpd-1.0.0_alpha 版本,包括for Unix/Linux (在 Ubuntu/Fedoar/FreeBSD 下编译通过) 和 在cygwin环境下编译的 for Windows 版本,下面地址有下载:

下面大致来聊聊怎么写一个 HTTP Server,先看看一个HTTP请求的流程:

HTTP Server process

大致就是 客户端的浏览器(IE、Firefox、Opera、Lynx、Curl、Robot ...) 访问到Web服务器的端口(一般是80),然后端口接受到请求后开始解析请求,如果请求不正确或者非法则直接返回指定的错误代码(可以参考 RFC 1945),如果请求合法,那么就查找相应用户请求的文件,把文件读取后发送给客户端,关闭连接,请求结束。

【 实现 】

贴部分tmhttpd核心代码来描述这个过程:

/**

 * 初始化服务器端的Socket连接,等待连接,连接成功后fork新进程处理

 *

 */

static void InitServerListen( unsigned int port, unsigned int max_client ){

    int serversock, clientsock;

    struct sockaddr_in server_addr, client_addr;

	char currtime[32];



    /* Create the TCP socket */

    if ((serversock = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP)) < 0){

        die("Failed to create socket");

    }



    /* Construct the server sockaddr_in structure */

    memset(&server_addr, 0, sizeof(server_addr));       /* Clear struct */

    server_addr.sin_family = AF_INET;                  /* Internet/IP */

    server_addr.sin_addr.s_addr = htonl(INADDR_ANY);   /* Incoming addr */

    server_addr.sin_port = htons(port);          /* server port */



    /* Bind the server socket */

    if (bind(serversock, (struct sockaddr *) &server_addr, sizeof(server_addr)) < 0){

        die("Failed to bind the server socket");

    }

    /* Listen on the server socket */

    if (listen(serversock, max_client) < 0){

        die("Failed to listen on server socket");

    }



	/* Print listening message */

	getdate(currtime);

	fprintf(stdout, "[%s] Start server listening at port %d .../n", currtime, port);

	fprintf(stdout, "[%s] Waiting client connection .../n", currtime);





    /* Run until cancelled */

    while (1){

        unsigned int clientlen = sizeof(client_addr);

		memset(currtime, 0, sizeof(currtime));

		getdate(currtime);



        /* Wait for client connection */

        if ((clientsock = accept(serversock, (struct sockaddr *) &client_addr, &clientlen)) < 0){

            die("Failed to accept client connection");

        }



        /* Use child process new connection */

        if ( fork() == 0 ){

			HandleClient(clientsock, client_addr);

		} else {

			wait(NULL); 

		}



		/* Not use close socket connection */

        close(clientsock);

    }



}







/**

 * 获取一个连接,读取连接客户端发送的请求数据,把请求数据叫给请求解析函数进行解析

 *

 */

static void HandleClient( int client_sock, struct sockaddr_in client_addr ){

    char buf[REQUEST_MAX_SIZE];



	if ( read(client_sock, buf, REQUEST_MAX_SIZE) < 0){

		SendError( client_sock, 500, "Internal Server Error", "", "Client request not success." );

		die("read sock");

	}

	ParseRequest( client_sock, client_addr, buf );



    close(client_sock);

    exit(0);

}





/**

 * 解析一个请求,解析出GET/HEAD方法,需要请求的文件,协议版本等,构造成结构体提交给处理函数

 *

 */

static int ParseRequest( int client_sock, struct sockaddr_in client_addr, char *req ){

	char **buf, **method_buf, **query_buf, currtime[32], cwd[1024], tmp_path[1536], pathinfo[512], path[256], file[256], log[1024];

	int line_total, method_total, query_total, i;

	struct st_request_info request_info;



	/* Split client request */

	getdate(currtime);

	explode(req, '/n', &buf, &line_total);



	/* Print log message  */

	memset(log, 0, sizeof(log));

	sprintf(log, "[%s] %s %s/n", currtime, inet_ntoa(client_addr.sin_addr), buf[0]);

	WriteLog(log);



	/* Check request is empty */

	if (strcmp(buf[0], "/n") == 0 || strcmp(buf[0], "/r/n") == 0){

		SendError( client_sock, 400, "Bad Request", "", "Can't parse request." );

	}



	/* Check method is implement */

	explode(buf[0], ' ', &method_buf, &method_total);

	if ( strcmp( strtolower(method_buf[0]), "get") != 0 &&  strcmp( strtolower(method_buf[0]), "head") != 0 ){

		SendError( client_sock, 501, "Not Implemented", "", "That method is not implemented." );

	}

	explode(method_buf[1], '?', &query_buf, &query_total);



	/* Make request data */	

	getcwd(cwd, sizeof(cwd));

	strcpy(pathinfo, query_buf[0]);

	substr(query_buf[0], 0, strrpos(pathinfo, '/')+1, path);

	substr(query_buf[0], strrpos(pathinfo, '/')+1, 0, file);

	

	/* Pad request struct */

	memset(&request_info, 0, sizeof(request_info));

	strcat(cwd, pathinfo);



	request_info.method			= method_buf[0];

	request_info.pathinfo		= pathinfo;

	request_info.query			= (query_total == 2 ? query_buf[1] : "");

	request_info.protocal		= strtolower(method_buf[2]);

	request_info.path			= path;

	request_info.file			= file;

	request_info.physical_path	= cwd;



	/* Is a directory pad default index page */

	memset(tmp_path, 0, sizeof(tmp_path));

	strcpy(tmp_path, cwd);

	if ( is_dir(tmp_path) ){

		strcat(tmp_path, g_dir_index);

		if ( file_exists(tmp_path) ){

			request_info.physical_path = tmp_path;

		}

	}

	

	/* Debug message */

	if ( g_is_debug ){

		fprintf(stderr, "[ Request ]/n");

		for(i=0; i<line_total; i++){

			fprintf(stderr, "%s/n", buf[i]);

		}

	}



	/* Process client request */

	ProcRequest( client_sock, client_addr, request_info );



	return 0;

}





/**

 * 处理函数按照解析出来的请求内容进行数据返回,返回文件/目录列表或者提示错误

 *

 */

static int ProcRequest( int client_sock, struct sockaddr_in client_addr, struct st_request_info request_info ){

	char buf[128];



	/* File is exist or has access permission */

	if ( !file_exists( request_info.physical_path ) ){

		memset(buf, 0, sizeof(buf));

		sprintf(buf, "File %s not found.", request_info.pathinfo);

		SendError( client_sock, 404, "Not Found", "", buf);

	}

	if ( access(request_info.physical_path, R_OK) != 0 ){

		memset(buf, 0, sizeof(buf));

		sprintf(buf, "File %s is protected.", request_info.pathinfo);

		SendError( client_sock, 403, "Forbidden", "", buf);

	}



	/* Check target is regular file or directory */

	if ( is_file(request_info.physical_path) == 1 ){

		SendFile( client_sock,  request_info.physical_path, request_info.pathinfo );



	} else if ( is_dir(request_info.physical_path) == 1 ){ 

		/* Is a directory choose browse dir list */

		if ( g_is_browse ){

			SendDirectory( client_sock, request_info.physical_path, request_info.pathinfo );



		} else {

			memset(buf, 0, sizeof(buf));

			sprintf(buf, "File %s is protected.", request_info.pathinfo);

			SendError( client_sock, 403, "Forbidden", "", buf);

		}



	} else {

		memset(buf, 0, sizeof(buf));

		sprintf(buf, "File %s is protected.", request_info.pathinfo);

		SendError( client_sock, 403, "Forbidden", "", buf);		

	}



	return 0;

}

  主要核心的函数就是这四个,如果要了解其他函数,包括字符串处理,HTTP头信息发送,错误发送,读取文件,遍历目录等等请下载源码回去研究。源码下载地址:http://heiyeluren.googlecode.com/files/tmhttpd-1.0.0_alpha.tar.gz

 

【 结束 】

其实还有很多功能能够增加的,比如性能,采用 select 或者 epool 肯定要比单纯的fork性能更好,另外增加处理动态内容,比如CGI,能够接受POST请求,能够作为一个反向代理存在,能够具有缓存功能,能够缓存请求过的文件到内存中,能够具有插件扩展功能等等,都是需要进一步提升的。

参考文档:  

 

 

  • 2
    点赞
  • 64
    收藏
    觉得还不错? 一键收藏
  • 32
    评论
提供的源码资源涵盖了Java应用等多个领域,每个领域都包含了丰富的实例和项目。这些源码都是基于各自平台的最新技术和标准编,确保了在对应环境下能够无缝运行。同时,源码中配备了详细的注释和文档,帮助用户快速理解代码结构和实现逻辑。 适用人群: 适合毕业设计、课程设计作业。这些源码资源特别适合大学生群体。无论你是计算机相关专业的学生,还是对其他领域编程感兴趣的学生,这些资源都能为你提供宝贵的学习和实践机会。通过学习和运行这些源码,你可以掌握各平台开发的基础知识,提升编程能力和项目实战经验。 使用场景及目标: 在学习阶段,你可以利用这些源码资源进行课程实践、课外项目或毕业设计。通过分析和运行源码,你将深入了解各平台开发的技术细节和最佳实践,逐步培养起自己的项目开发和问题解决能力。此外,在求职或创业过程中,具备跨平台开发能力的大学生将更具竞争力。 其他说明: 为了确保源码资源的可运行性和易用性,特别注意了以下几点:首先,每份源码都提供了详细的运行环境和依赖说明,确保用户能够轻松搭建起开发环境;其次,源码中的注释和文档都非常完善,方便用户快速上手和理解代码;最后,我会定期更新这些源码资源,以适应各平台技术的最新发展和市场需求。 所有源码均经过严格测试,可以直接运行,可以放心下载使用。有任何使用问题欢迎随时与博主沟通,第一时间进行解答!

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 32
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值