- 浏览: 4186893 次
最新评论
自己动手写 HTTP Server
自己动手写 HTTP Server
-
作者:heiyeluren
-
时间:2008-06-22
【 原理 】 一般来说,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 版本,下面地址有下载:
- Unix/Linux: http://heiyeluren.googlecode.com/files/tmhttpd-1.0.0_alpha.tar.gz
- Windows: http://heiyeluren.googlecode.com/files/tmhttpd-1.0.0_alpha-win32.zip
下面大致来聊聊怎么写一个 HTTP Server,先看看一个HTTP请求的流程:
大致就是 客户端的浏览器(IE、Firefox、Opera、Lynx、Curl、Robot ...) 访问到Web服务器的端口(一般是80),然后端口接受到请求后开始解析请求,如果请求不正确或者非法则直接返回指定的错误代码(可以参考 RFC 1945),如果请求合法,那么就查找相应用户请求的文件,把文件读取后发送给客户端,关闭连接,请求结束。
【 实现 】
贴部分tmhttpd核心代码来描述这个过程:
- /**
- *初始化服务器端的Socket连接,等待连接,连接成功后fork新进程处理
- *
- */
- staticvoidInitServerListen(unsignedintport,unsignedintmax_client){
- intserversock,clientsock;
- structsockaddr_inserver_addr,client_addr;
- charcurrtime[32];
- /*CreatetheTCPsocket*/
- if((serversock=socket(PF_INET,SOCK_STREAM,IPPROTO_TCP))<0){
- die("Failedtocreatesocket");
- }
- /*Constructtheserversockaddr_instructure*/
- memset(&server_addr,0,sizeof(server_addr));/*Clearstruct*/
- server_addr.sin_family=AF_INET;/*Internet/IP*/
- server_addr.sin_addr.s_addr=htonl(INADDR_ANY);/*Incomingaddr*/
- server_addr.sin_port=htons(port);/*serverport*/
- /*Bindtheserversocket*/
- if(bind(serversock,(structsockaddr*)&server_addr,sizeof(server_addr))<0){
- die("Failedtobindtheserversocket");
- }
- /*Listenontheserversocket*/
- if(listen(serversock,max_client)<0){
- die("Failedtolistenonserversocket");
- }
- /*Printlisteningmessage*/
- getdate(currtime);
- fprintf(stdout,"[%s]Startserverlisteningatport%d.../n",currtime,port);
- fprintf(stdout,"[%s]Waitingclientconnection.../n",currtime);
- /*Rununtilcancelled*/
- while(1){
- unsignedintclientlen=sizeof(client_addr);
- memset(currtime,0,sizeof(currtime));
- getdate(currtime);
- /*Waitforclientconnection*/
- if((clientsock=accept(serversock,(structsockaddr*)&client_addr,&clientlen))<0){
- die("Failedtoacceptclientconnection");
- }
- /*Usechildprocessnewconnection*/
- if(fork()==0){
- HandleClient(clientsock,client_addr);
- }else{
- wait(NULL);
- }
- /*Notuseclosesocketconnection*/
- close(clientsock);
- }
- }
- /**
- *获取一个连接,读取连接客户端发送的请求数据,把请求数据叫给请求解析函数进行解析
- *
- */
- staticvoidHandleClient(intclient_sock,structsockaddr_inclient_addr){
- charbuf[REQUEST_MAX_SIZE];
- if(read(client_sock,buf,REQUEST_MAX_SIZE)<0){
- SendError(client_sock,500,"InternalServerError","","Clientrequestnotsuccess.");
- die("readsock");
- }
- ParseRequest(client_sock,client_addr,buf);
- close(client_sock);
- exit(0);
- }
- /**
- *解析一个请求,解析出GET/HEAD方法,需要请求的文件,协议版本等,构造成结构体提交给处理函数
- *
- */
- staticintParseRequest(intclient_sock,structsockaddr_inclient_addr,char*req){
- char**buf,**method_buf,**query_buf,currtime[32],cwd[1024],tmp_path[1536],pathinfo[512],path[256],file[256],log[1024];
- intline_total,method_total,query_total,i;
- structst_request_inforequest_info;
- /*Splitclientrequest*/
- getdate(currtime);
- explode(req,'/n',&buf,&line_total);
- /*Printlogmessage*/
- memset(log,0,sizeof(log));
- sprintf(log,"[%s]%s%s/n",currtime,inet_ntoa(client_addr.sin_addr),buf[0]);
- WriteLog(log);
- /*Checkrequestisempty*/
- if(strcmp(buf[0],"/n")==0||strcmp(buf[0],"/r/n")==0){
- SendError(client_sock,400,"BadRequest","","Can'tparserequest.");
- }
- /*Checkmethodisimplement*/
- 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,"NotImplemented","","Thatmethodisnotimplemented.");
- }
- explode(method_buf[1],'?',&query_buf,&query_total);
- /*Makerequestdata*/
- 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);
- /*Padrequeststruct*/
- 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;
- /*Isadirectorypaddefaultindexpage*/
- 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;
- }
- }
- /*Debugmessage*/
- if(g_is_debug){
- fprintf(stderr,"[Request]/n");
- for(i=0;i<line_total;i++){
- fprintf(stderr,"%s/n",buf[i]);
- }
- }
- /*Processclientrequest*/
- ProcRequest(client_sock,client_addr,request_info);
- return0;
- }
- /**
- *处理函数按照解析出来的请求内容进行数据返回,返回文件/目录列表或者提示错误
- *
- */
- staticintProcRequest(intclient_sock,structsockaddr_inclient_addr,structst_request_inforequest_info){
- charbuf[128];
- /*Fileisexistorhasaccesspermission*/
- if(!file_exists(request_info.physical_path)){
- memset(buf,0,sizeof(buf));
- sprintf(buf,"File%snotfound.",request_info.pathinfo);
- SendError(client_sock,404,"NotFound","",buf);
- }
- if(access(request_info.physical_path,R_OK)!=0){
- memset(buf,0,sizeof(buf));
- sprintf(buf,"File%sisprotected.",request_info.pathinfo);
- SendError(client_sock,403,"Forbidden","",buf);
- }
- /*Checktargetisregularfileordirectory*/
- if(is_file(request_info.physical_path)==1){
- SendFile(client_sock,request_info.physical_path,request_info.pathinfo);
- }elseif(is_dir(request_info.physical_path)==1){
- /*Isadirectorychoosebrowsedirlist*/
- if(g_is_browse){
- SendDirectory(client_sock,request_info.physical_path,request_info.pathinfo);
- }else{
- memset(buf,0,sizeof(buf));
- sprintf(buf,"File%sisprotected.",request_info.pathinfo);
- SendError(client_sock,403,"Forbidden","",buf);
- }
- }else{
- memset(buf,0,sizeof(buf));
- sprintf(buf,"File%sisprotected.",request_info.pathinfo);
- SendError(client_sock,403,"Forbidden","",buf);
- }
- return0;
- }
/** * 初始化服务器端的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请求,能够作为一个反向代理存在,能够具有缓存功能,能够缓存请求 过的文件到内存中,能够具有插件扩展功能等等,都是需要进一步提升的。
参考文档:
- RFC 1945: http://www.w3.org/Protocols/rfc1945/rfc1945
- RFC 2616: http://www.w3.org/Protocols/rfc2616/rfc2616.html
相关推荐
在linux下面,自动动手用socket实现了一个简单的php web server。经过测试,可以使用。
最近在看反射,突然想写一个ORM工具,要轻量级的,不要配置文档,先不管效率,就是一个小工具,在项目初期方便挂数据库。 我的目标就是在数据库中建个表,在项目中写个模型,然后用上这个ORM工具,就能实现数据库的...
因为工作需要,找了几个数据库字典生成工具,感觉要么就是大高大上,操作复杂,安装复杂,要么就是要注册不可免费用,我就自己动手写了一个,用查询字典和报表技术来实现的,借助于微软的DLL配置连接串,感觉比较...
自己动手写的简单Web服务器(附源码)!供参考
自己动手写的Client,通过拼装协议字段,用socket于Live555的RTSP Server交互 http://blog.csdn.net/longlong530/article/details/9102205
最近在学习网络原理,突然萌发出自己实现一个网络服务器的想法,并且由于第三代小白机器人的开发需要,我把之前使用python、PHP写的那部分代码都迁移到了C#(别问我为什么这么喜欢C#),之前使用PHP就是用来处理网络...
前端时间需要一个嵌入式的webserver,但...想到那么就开始动手写,由于之前利用的是cgilua,那么就做一个cgilua的子集吧,尽量兼容。 具体请参考http://blog.csdn.net/DragonCheng/archive/2010/09/01/5855023.aspx
决定自己动手写。思路有三个:1.很多用GROUPPING和ROLLUP来实现。 优点:实现代码简洁,要求对GROUPPING和ROLLUP很深的理解。 缺点:低版本的Sql Server不支持。2.游标实现。 优点:思路逻辑简洁。 缺点:复杂和...
汉化包中有我自己写的一篇很浅显的文章,仅供大家参考 文件名为 ServU使用教学.mht,直接用游览器打开就行了 另外有个 FTP Serv-U.chm 是我从网上收集到的,有人把 许多文章集成做了个 CHM 的教学,很不错:)
本文以两种稍微有差别的方式用C#语言实现HTTP协议的服务器类,之所以写这些,也是为了自己能更深刻了解HTTP底层运作。要完成高性能的Web服务功能,通常都是需要写入到服务,如IIS,Apache Tomcat,但是众所周知的Web...
如何用nodejs给C#写一个数据表的实体类生成工具 虽然微软提供了T4模板,但是我... 但是支持不够就自己动手丰衣足食嘛。 我们使用ejs这个模板引擎来做生成器。 npm install ejs 然后用查询出表结构: b.query('des
此项目是学习的目的,使用netty3实现了websocket服务器和简单的web server。通过研读netty的源代码并自己动手写DEMO,能快速领悟netty的原理以及开发基于netty的项目
执行计划可以辅助我们写出高效率的T-SQL代码,同时也可以找出现有T-SQL代码的问题,还可以监控数据库!当然,最后如何使用执行计划还是取决于我们自己了,但是不管怎么样,我们首先学会解析执行计划中所包含的信息,...
客户是做出国留学的公司,运营人员在录入国外信息时需要填写坐标,于是动手给客户写了一个这个小工具。 在翻阅以前的项目的时候忽然看到了这个小工具,觉得还不错,就共享给大家。 使用方法: 单击地图即可获取...
MyDB是我在2009年12月底开始动手写作的,是一个纯java写的数据库管理器,目前只是一个实现基本功能的演示性框架,用来验证功能的可实现性。 整个应用包括三个部分,数据库服务器,数据库客户端,数据库驱动。目前...
人人官网提供的sdk-python真是无力吐槽了,小哥只能自己动手,写了人人的sdk,目前只能完成认证和发布状态(我的需求就这么多),不过你可以模仿我的publish_status函数完成其他的功能!!具体的demo请翻看我的csdn-...
自己动手写SQL执行引擎前言在阅读了大量关于数据库的资料后,笔者情不自禁产生了一个造数据库轮子的想法。来验证一下自己对于数据库底层原理的掌握是否牢靠。在笔者的github中给这个database起名为Freedom。整体结构...
2)进行系统需求分析和系统设计,写出系统分析和设计报告。 3)设计数据模型并进行优化,确定数据库结构、功能结构和系统安全性和完整性要求。 4)完成数据库定义工作,实现系统数据的数据录入和数据处理。
想到做这个,是因为无意中在github上看到了这一个仓库,做的就是一个城市选择控件,是用vue写的,说的是阿里的一道题目,然后想想自己闲着也是闲着,就动手用react又重新做了一遍。 演示 地址: github: 整体效果...