`
阿尔萨斯
  • 浏览: 4186893 次
社区版块
存档分类
最新评论

自己动手写 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核心代码来描述这个过程:

  1. /**
  2. *初始化服务器端的Socket连接,等待连接,连接成功后fork新进程处理
  3. *
  4. */
  5. staticvoidInitServerListen(unsignedintport,unsignedintmax_client){
  6. intserversock,clientsock;
  7. structsockaddr_inserver_addr,client_addr;
  8. charcurrtime[32];
  9. /*CreatetheTCPsocket*/
  10. if((serversock=socket(PF_INET,SOCK_STREAM,IPPROTO_TCP))<0){
  11. die("Failedtocreatesocket");
  12. }
  13. /*Constructtheserversockaddr_instructure*/
  14. memset(&server_addr,0,sizeof(server_addr));/*Clearstruct*/
  15. server_addr.sin_family=AF_INET;/*Internet/IP*/
  16. server_addr.sin_addr.s_addr=htonl(INADDR_ANY);/*Incomingaddr*/
  17. server_addr.sin_port=htons(port);/*serverport*/
  18. /*Bindtheserversocket*/
  19. if(bind(serversock,(structsockaddr*)&server_addr,sizeof(server_addr))<0){
  20. die("Failedtobindtheserversocket");
  21. }
  22. /*Listenontheserversocket*/
  23. if(listen(serversock,max_client)<0){
  24. die("Failedtolistenonserversocket");
  25. }
  26. /*Printlisteningmessage*/
  27. getdate(currtime);
  28. fprintf(stdout,"[%s]Startserverlisteningatport%d.../n",currtime,port);
  29. fprintf(stdout,"[%s]Waitingclientconnection.../n",currtime);
  30. /*Rununtilcancelled*/
  31. while(1){
  32. unsignedintclientlen=sizeof(client_addr);
  33. memset(currtime,0,sizeof(currtime));
  34. getdate(currtime);
  35. /*Waitforclientconnection*/
  36. if((clientsock=accept(serversock,(structsockaddr*)&client_addr,&clientlen))<0){
  37. die("Failedtoacceptclientconnection");
  38. }
  39. /*Usechildprocessnewconnection*/
  40. if(fork()==0){
  41. HandleClient(clientsock,client_addr);
  42. }else{
  43. wait(NULL);
  44. }
  45. /*Notuseclosesocketconnection*/
  46. close(clientsock);
  47. }
  48. }
  49. /**
  50. *获取一个连接,读取连接客户端发送的请求数据,把请求数据叫给请求解析函数进行解析
  51. *
  52. */
  53. staticvoidHandleClient(intclient_sock,structsockaddr_inclient_addr){
  54. charbuf[REQUEST_MAX_SIZE];
  55. if(read(client_sock,buf,REQUEST_MAX_SIZE)<0){
  56. SendError(client_sock,500,"InternalServerError","","Clientrequestnotsuccess.");
  57. die("readsock");
  58. }
  59. ParseRequest(client_sock,client_addr,buf);
  60. close(client_sock);
  61. exit(0);
  62. }
  63. /**
  64. *解析一个请求,解析出GET/HEAD方法,需要请求的文件,协议版本等,构造成结构体提交给处理函数
  65. *
  66. */
  67. staticintParseRequest(intclient_sock,structsockaddr_inclient_addr,char*req){
  68. char**buf,**method_buf,**query_buf,currtime[32],cwd[1024],tmp_path[1536],pathinfo[512],path[256],file[256],log[1024];
  69. intline_total,method_total,query_total,i;
  70. structst_request_inforequest_info;
  71. /*Splitclientrequest*/
  72. getdate(currtime);
  73. explode(req,'/n',&buf,&line_total);
  74. /*Printlogmessage*/
  75. memset(log,0,sizeof(log));
  76. sprintf(log,"[%s]%s%s/n",currtime,inet_ntoa(client_addr.sin_addr),buf[0]);
  77. WriteLog(log);
  78. /*Checkrequestisempty*/
  79. if(strcmp(buf[0],"/n")==0||strcmp(buf[0],"/r/n")==0){
  80. SendError(client_sock,400,"BadRequest","","Can'tparserequest.");
  81. }
  82. /*Checkmethodisimplement*/
  83. explode(buf[0],'',&method_buf,&method_total);
  84. if(strcmp(strtolower(method_buf[0]),"get")!=0&&strcmp(strtolower(method_buf[0]),"head")!=0){
  85. SendError(client_sock,501,"NotImplemented","","Thatmethodisnotimplemented.");
  86. }
  87. explode(method_buf[1],'?',&query_buf,&query_total);
  88. /*Makerequestdata*/
  89. getcwd(cwd,sizeof(cwd));
  90. strcpy(pathinfo,query_buf[0]);
  91. substr(query_buf[0],0,strrpos(pathinfo,'/')+1,path);
  92. substr(query_buf[0],strrpos(pathinfo,'/')+1,0,file);
  93. /*Padrequeststruct*/
  94. memset(&request_info,0,sizeof(request_info));
  95. strcat(cwd,pathinfo);
  96. request_info.method=method_buf[0];
  97. request_info.pathinfo=pathinfo;
  98. request_info.query=(query_total==2?query_buf[1]:"");
  99. request_info.protocal=strtolower(method_buf[2]);
  100. request_info.path=path;
  101. request_info.file=file;
  102. request_info.physical_path=cwd;
  103. /*Isadirectorypaddefaultindexpage*/
  104. memset(tmp_path,0,sizeof(tmp_path));
  105. strcpy(tmp_path,cwd);
  106. if(is_dir(tmp_path)){
  107. strcat(tmp_path,g_dir_index);
  108. if(file_exists(tmp_path)){
  109. request_info.physical_path=tmp_path;
  110. }
  111. }
  112. /*Debugmessage*/
  113. if(g_is_debug){
  114. fprintf(stderr,"[Request]/n");
  115. for(i=0;i<line_total;i++){
  116. fprintf(stderr,"%s/n",buf[i]);
  117. }
  118. }
  119. /*Processclientrequest*/
  120. ProcRequest(client_sock,client_addr,request_info);
  121. return0;
  122. }
  123. /**
  124. *处理函数按照解析出来的请求内容进行数据返回,返回文件/目录列表或者提示错误
  125. *
  126. */
  127. staticintProcRequest(intclient_sock,structsockaddr_inclient_addr,structst_request_inforequest_info){
  128. charbuf[128];
  129. /*Fileisexistorhasaccesspermission*/
  130. if(!file_exists(request_info.physical_path)){
  131. memset(buf,0,sizeof(buf));
  132. sprintf(buf,"File%snotfound.",request_info.pathinfo);
  133. SendError(client_sock,404,"NotFound","",buf);
  134. }
  135. if(access(request_info.physical_path,R_OK)!=0){
  136. memset(buf,0,sizeof(buf));
  137. sprintf(buf,"File%sisprotected.",request_info.pathinfo);
  138. SendError(client_sock,403,"Forbidden","",buf);
  139. }
  140. /*Checktargetisregularfileordirectory*/
  141. if(is_file(request_info.physical_path)==1){
  142. SendFile(client_sock,request_info.physical_path,request_info.pathinfo);
  143. }elseif(is_dir(request_info.physical_path)==1){
  144. /*Isadirectorychoosebrowsedirlist*/
  145. if(g_is_browse){
  146. SendDirectory(client_sock,request_info.physical_path,request_info.pathinfo);
  147. }else{
  148. memset(buf,0,sizeof(buf));
  149. sprintf(buf,"File%sisprotected.",request_info.pathinfo);
  150. SendError(client_sock,403,"Forbidden","",buf);
  151. }
  152. }else{
  153. memset(buf,0,sizeof(buf));
  154. sprintf(buf,"File%sisprotected.",request_info.pathinfo);
  155. SendError(client_sock,403,"Forbidden","",buf);
  156. }
  157. return0;
  158. }

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

【 结束 】

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

参考文档

分享到:
评论

相关推荐

    简单的PHP web server(适合学习写web server)

    在linux下面,自动动手用socket实现了一个简单的php web server。经过测试,可以使用。

    自己动手写轻量级ORM(C#)

    最近在看反射,突然想写一个ORM工具,要轻量级的,不要配置文档,先不管效率,就是一个小工具,在项目初期方便挂数据库。 我的目标就是在数据库中建个表,在项目中写个模型,然后用上这个ORM工具,就能实现数据库的...

    sql server数据库字典自动生成工具

    因为工作需要,找了几个数据库字典生成工具,感觉要么就是大高大上,操作复杂,安装复杂,要么就是要注册不可免费用,我就自己动手写了一个,用查询字典和报表技术来实现的,借助于微软的DLL配置连接串,感觉比较...

    server project

    自己动手写的简单Web服务器(附源码)!供参考

    自己写的RTSP Client,与Live555交互

    自己动手写的Client,通过拼装协议字段,用socket于Live555的RTSP Server交互 http://blog.csdn.net/longlong530/article/details/9102205

    基于C#动手实现网络服务器Web Server

    最近在学习网络原理,突然萌发出自己实现一个网络服务器的想法,并且由于第三代小白机器人的开发需要,我把之前使用python、PHP写的那部分代码都迁移到了C#(别问我为什么这么喜欢C#),之前使用PHP就是用来处理网络...

    mongoose+LUA

    前端时间需要一个嵌入式的webserver,但...想到那么就开始动手写,由于之前利用的是cgilua,那么就做一个cgilua的子集吧,尽量兼容。 具体请参考http://blog.csdn.net/DragonCheng/archive/2010/09/01/5855023.aspx

    用SQL实现统计报表中的小计与合计的方法详解

    决定自己动手写。思路有三个:1.很多用GROUPPING和ROLLUP来实现。 优点:实现代码简洁,要求对GROUPPING和ROLLUP很深的理解。 缺点:低版本的Sql Server不支持。2.游标实现。 优点:思路逻辑简洁。 缺点:复杂和...

    server-u 汉化版

    汉化包中有我自己写的一篇很浅显的文章,仅供大家参考 文件名为 ServU使用教学.mht,直接用游览器打开就行了 另外有个 FTP Serv-U.chm 是我从网上收集到的,有人把 许多文章集成做了个 CHM 的教学,很不错:)

    C#实现HTTP协议迷你服务器(两种方法)

    本文以两种稍微有差别的方式用C#语言实现HTTP协议的服务器类,之所以写这些,也是为了自己能更深刻了解HTTP底层运作。要完成高性能的Web服务功能,通常都是需要写入到服务,如IIS,Apache Tomcat,但是众所周知的Web...

    【JavaScript源代码】如何用nodejs给C#写一个数据表的实体类生成工具.docx

    如何用nodejs给C#写一个数据表的实体类生成工具  虽然微软提供了T4模板,但是我... 但是支持不够就自己动手丰衣足食嘛。 我们使用ejs这个模板引擎来做生成器。 npm install ejs 然后用查询出表结构: b.query('des

    netty3实现的websocket服务

    此项目是学习的目的,使用netty3实现了websocket服务器和简单的web server。通过研读netty的源代码并自己动手写DEMO,能快速领悟netty的原理以及开发基于netty的项目

    SQLServer性能调优之执行计划第一次实践

    执行计划可以辅助我们写出高效率的T-SQL代码,同时也可以找出现有T-SQL代码的问题,还可以监控数据库!当然,最后如何使用执行计划还是取决于我们自己了,但是不管怎么样,我们首先学会解析执行计划中所包含的信息,...

    Google地图坐标拾取器(HTML版,含国服和国际服版本)

    客户是做出国留学的公司,运营人员在录入国外信息时需要填写坐标,于是动手给客户写了一个这个小工具。 在翻阅以前的项目的时候忽然看到了这个小工具,觉得还不错,就共享给大家。 使用方法: 单击地图即可获取...

    用java写的MyDB数据库管理器演示程序

    MyDB是我在2009年12月底开始动手写作的,是一个纯java写的数据库管理器,目前只是一个实现基本功能的演示性框架,用来验证功能的可实现性。 整个应用包括三个部分,数据库服务器,数据库客户端,数据库驱动。目前...

    人人sdk-python

    人人官网提供的sdk-python真是无力吐槽了,小哥只能自己动手,写了人人的sdk,目前只能完成认证和发布状态(我的需求就这么多),不过你可以模仿我的publish_status函数完成其他的功能!!具体的demo请翻看我的csdn-...

    Freedom:自己DIY一个具有ACID的数据库

    自己动手写SQL执行引擎前言在阅读了大量关于数据库的资料后,笔者情不自禁产生了一个造数据库轮子的想法。来验证一下自己对于数据库底层原理的掌握是否牢靠。在笔者的github中给这个database起名为Freedom。整体结构...

    数据库课程设计---某期刊的在线投稿审稿管理系统

    2)进行系统需求分析和系统设计,写出系统分析和设计报告。 3)设计数据模型并进行优化,确定数据库结构、功能结构和系统安全性和完整性要求。 4)完成数据库定义工作,实现系统数据的数据录入和数据处理。

    city-selector:一个用于选择城市的简单Web演示

    想到做这个,是因为无意中在github上看到了这一个仓库,做的就是一个城市选择控件,是用vue写的,说的是阿里的一道题目,然后想想自己闲着也是闲着,就动手用react又重新做了一遍。 演示 地址: github: 整体效果...

Global site tag (gtag.js) - Google Analytics