loop_in_codes

低调做技术__欢迎移步我的独立博客 codemaro.com 微博 kevinlynx

实现自己的http server

Write your own http server

author : Kevin Lynx

Why write your own?

    看这个问题的人证明你知道什么是http server,世界上有很多各种规模的http server,为什么要自己实现一个?其实没什么
理由。我自己问自己,感觉就是在自己娱乐自己,或者说只是练习下网络编程,或者是因为某日我看到某个库宣称自己附带一个小
型的http server时,我不知道是什么东西,于是就想自己去实现一个。

What's httpd ?

    httpd就是http daemon,这个是类unix系统上的名称,也就是http server。httpd遵循HTTP协议,响应HTTP客户端的request,
然后返回response。
    那么,什么是HTTP协议?最简单的例子,就是你的浏览器与网页服务器之间使用的应用层协议。虽然官方文档说HTTP协议可以
建立在任何可靠传输的协议之上,但是就我们所见到的,HTTP还是建立在TCP之上的。
    httpd最简单的response是返回静态的HTML页面。在这里我们的目标也只是一个响应静态网页的httpd而已(也许你愿意加入CGI
特性)。

More details about HTTP protocol

    在这里有必要讲解HTTP协议的更多细节,因为我们的httpd就是要去解析这个协议。
    关于HTTP协议的详细文档,可以参看rfc2616。但事实上对于实现一个简单的响应静态网页的httpd来说,完全没必要读这么一
分冗长的文档。在这里我推荐<HTTP Made Really Easy>,以下内容基本取自于本文档。

- HTTP协议结构
  HTTP协议无论是请求报文(request message)还是回应报文(response message)都分为四部分:
  * 报文头 (initial line )
  * 0个或多个header line
  * 空行(作为header lines的结束)
  * 可选body
  HTTP协议是基于行的协议,每一行以\r\n作为分隔符。报文头通常表明报文的类型(例如请求类型),报文头只占一行;header line
  附带一些特殊信息,每一个header line占一行,其格式为name:value,即以分号作为分隔;空行也就是一个\r\n;可选body通常
  包含数据,例如服务器返回的某个静态HTML文件的内容。举个例子,以下是一个很常见的请求报文,你可以截获浏览器发送的数据
  包而获得:

    1  GET /index.html HTTP/1.1
    2  Accept-Language: zh-cn
    3  Accept-Encoding: gzip, deflate
    4  User-Agent: Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1; .NET CLR 2.0.50727; MAXTHON 2.0)
    5  Host: localhost
    6  Connection: Keep-Alive
    7
  我为每一行都添加了行号,第1行就是initial line,2-6行是header lines,7行是一个header line的结束符,没有显示出来。
  以下是一个回应报文:
    1  HTTP/1.1 200 OK
    2  Server: klhttpd/0.1.0
    3  Content-Type: text/html
    4  Content-Length: 67
    5
    6  <head><head><title>index.html</title></head><body>index.html</body>
  第6行就是可选的body,这里是index.html这个文件的内容。

- HTTP request method
  因为我们做的事服务器端,所以我们重点对请求报文做说明。首先看initial line,该行包含几个字段,每个字段用空格分开,例
  如以上的GET /index.html HTTP/1.1就可以分为三部分:GET、/index.html、HTTP/1.1。其中第一个字段GET就是所谓的request
  method。它表明请求类型,HTTP有很多method,例如:GET、POST、HEAD等。

  就我们的目标而言,我们只需要实现对GET和HEAD做响应即可。

  GET是最普遍的method,表示请求一个资源。什么是资源?诸如HTML网页、图片、声音文件等都是资源。顺便提一句,HTTP协议
  中为每一个资源设置一个唯一的标识符,就是所谓的URI(更宽泛的URL)。
  HEAD与GET一样,不过它不请求资源内容,而是请求资源信息,例如文件长度等信息。

- More detail
  继续说说initial line后面的内容:
  对应于GET和HEAD两个method,紧接着的字段就是资源名,其实从这里可以看出,也就是文件名(相对于你服务器的资源目录),例
  如这里的/index.html;最后一个字段表明HTTP协议版本号。目前我们只需要支持HTTP1.1和1.0,没有多大的技术差别。

  然后是header line。我们并不需要关注每一个header line。我只罗列有用的header line :
  - Host : 对于HTTP1.1而言,请求报文中必须包含此header,如果没有包含,服务器需要返回bad request错误信息。
  - Date : 用于回应报文,用于客户端缓存数据用。
  - Content-Type : 用于回应报文,表示回应资源的文件类型,以MIME形式给出。什么是MIME?它们都有自己的格式,例如:
    text/html, image/jpg, image/gif等。
  - Content-Length : 用于回应报文,表示回应资源的文件长度。

body域很简单,你只需要将一个文件全部读入内存,然后附加到回应报文段后发送即可,即使是二进制数据。

- 回应报文
  之前提到的一个回应报文例子很典型,我们以其为例讲解。首先是initial line,第一个字段表明HTTP协议版本,可以直接以请求
  报文为准(即请求报文版本是多少这里就是多少);第二个字段是一个status code,也就是回应状态,相当于请求结果,请求结果
  被HTTP官方事先定义,例如200表示成功、404表示资源不存在等;最后一个字段为status code的可读字符串,你随便给吧。

  回应报文中最好跟上Content-Type、Content-Length等header。

具体实现
    正式写代码之前我希望你能明白HTTP协议的这种请求/回应模式,即客户端发出一个请求,然后服务器端回应该请求。然后继续
这个过程(HTTP1.1是长连接模式,而HTTP1.0是短连接,当服务器端返回第一个请求时,连接就断开了)。
    这里,我们无论客户端,例如浏览器,发出什么样的请求,请求什么资源,我们都回应相同的数据:

               

/* 阻塞地接受一个客户端连接 */
        SOCKET con 
= accept( s, 00 ); 
        
/* recv request */
        
char request[1024= 0 };
        ret 
= recv( con, request, sizeof( request ), 0 );
        printf( request );
        
/* whatever we recv, we send 200 response */
        
{
            
char content[] = "<head><head><title>index.html</title></head><body>index.html</body>";
            
char response[512];
            sprintf( response, 
"HTTP/1.1 200 OK\r\nContent-Type: text/html\r\nContent-Length: %d\r\n\r\n%s", strlen( content ), content );
            ret 
= send( con, response, strlen( response ), 0 );
        }

        closesocket( con ); 

 

    程序以最简单的阻塞模式运行,我们可以将重点放在协议的分析上。运行程序,在浏览器里输入http://localhost:8080/index.html
,然后就可以看到浏览器正常显示content中描述的HTML文件。假设程序在8080端口监听。

   现在你基本上明白了整个工作过程,我们可以把代码写得更全面一点,例如根据GET的URI来载入对应的文件然后回应给客户端。
其实这个很简单,只需要从initial line里解析出(很一般的字符串解析)URI字段,然后载入对应的文件即可。例如以下函数:

void http_response( SOCKET con, const char *request )
{
    
/* get the method */
    
char *token = strtok( request, " " );
    
char *uri = strtok( 0" " );
    
char file[64];
    sprintf( file, 
".%s", uri ); 

    
{
        
/* load the file content */
        FILE 
*fp = fopen( file, "rb" );
        
if( fp == 0 )
        
{
            
/* response 404 status code */
            
char response[] = "HTTP/1.1 404 NOT FOUND\r\n\r\n";
            send( con, response, strlen( response ), 
0 );
        }

        
else
        
{
            
/* response the resource */
            
/* first, load the file */
            
int file_size ;
            
char *content;
            
char response[1024];
            fseek( fp, 
0, SEEK_END );
            file_size 
= ftell( fp );
            fseek( fp, 
0, SEEK_SET );
            content 
= (char*)malloc( file_size + 1 );
            fread( content, file_size, 
1, fp );
            content[file_size] 
= 0

            sprintf( response, 
"HTTP/1.1 200 OK\r\nContent-Type: text/html\r\nContent-Length: %d\r\n\r\n%s", file_size, content );
            send( con, response, strlen( response ), 
0 );
            free( content );
        }

    }

}
 



其他

    要将这个简易的httpd做完善,我们还需要注意很多细节。包括:对不支持的method返回501错误;对于HTTP1.1要求有Host这个
header;为了支持客户端cache,需要添加Date header;支持HEAD请求等。

    相关下载中我提供了一个完整的httpd library,纯C的代码,在其上加上一层资源载入即可实现一个简单的httpd。在这里我将
对代码做简要的说明:
    evbuffer.h/buffer.c : 取自libevent的buffer,用于缓存数据;
    klhttp-internal.h/klhttp-internal.c :主要用于处理/解析HTTP请求,以及创建回应报文;
    klhttp-netbase.h/klhttp-netbase.c :对socket api的一个简要封装,使用select模型;
    klhttp.h/klhttp.c :库的最上层,应用层主要与该层交互,这一层主要集合internal和netbase。
    test_klhttp.c :一个测试例子。

相关下载:
    klhttpd
    文中相关代码

参考资料:

http://www.w3.org/Protocols/rfc2616/rfc2616.html
http://jmarshall.com/easy/http/
http://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html

posted on 2008-07-30 16:14 Kevin Lynx 阅读(29719) 评论(21)  编辑 收藏 引用 所属分类: network

评论

# re: 实现自己的http server 2008-07-30 23:52 zx

其实对于Server来说,最难的部分是如何能尽快更多的响应Client的请求,这里多线程似乎作用不大,最好的方式是用Async模式运行,而且还要用到IO的overlapped……  回复  更多评论   

# re: 实现自己的http server 2008-07-31 00:08 x-matrix

@zx
这只是个练习而已,何必苛求。
  回复  更多评论   

# re: 实现自己的http server 2008-07-31 08:56 Kevin Lynx

@zx
高并发数不见得要使用async(不明白你所谓的Async模式是什么?我假设是异步IO),也不见得要使用Overlapped IO (我也不明白你说的"IO的overlapped",我假设是windows下的Overlapped IO),一个event-driven的网路模型就很不错了。

@x-matrix
本文的重点在于对HTTP协议的解析,而不是开发高并发网络框架。:)  回复  更多评论   

# re: 实现自己的http server 2008-07-31 16:15 土仔

很赞同你的这种做法(精神?),其实做出一个东西有时只是为了能熟悉它的一些特性和满足自己的娱乐精神。  回复  更多评论   

# re: 实现自己的http server 2008-08-01 14:57 Strive

当然,看过 http://home.xxsyzx.com/home/ 这个的话,才真正能满足自己的娱乐精神。。  我一向都是报着做着好玩来玩这种程序。  回复  更多评论   

# re: 实现自己的http server 2008-08-01 16:08 Kevin Lynx

@Strive
很巧, 我恰好看过这个网站,感觉是个少年天才的主页。之前搜索QQ协议,发现的。还下了他写的QQ客户端,不过有问题,我登陆Q后,就被T了。  回复  更多评论   

# re: 实现自己的http server 2008-08-04 19:37 sinodragon21

http://home.xxsyzx.com/home/ 看了Strive和楼主提到的这个URL,感觉真的很不可思议!太有才了~!Unbelievable!  回复  更多评论   

# re: 实现自己的http server 2008-08-04 20:19 矩阵操作

HTTP协议最麻烦是cookie那部分,以前写过,感觉好恶。。。

btw,也去看了这个http://home.xxsyzx.com/home/ ,靠,NB,这个webqq确实搞得不错,我在上面聊了,很正常啊,没有被T。回头研究研究。  回复  更多评论   

# re: 实现自己的http server 2008-08-04 20:27 francis

我很少相信天才,这一次不得不信。。。 而且是少年天才。。。  回复  更多评论   

# re: 实现自己的http server 2008-08-04 20:53 矩阵操作

看了代码,结构设计和编写质量还可以,居然达到鄙人参加工作8年才达到的实力。。。。。代码里面几乎没有注释。。。

现在的年轻人,太厉害了。确实是个天才  回复  更多评论   

# re: 实现自己的http server 2008-09-04 11:12 dikatour

我觉得最后一段代码有些问题。
因为读出的文件内容都是二进制读出,文件内容可能包含'\0', 因此sprinf会丢弃'\0'后面的内容。  回复  更多评论   

# re: 实现自己的http server 2008-09-04 13:53 Kevin Lynx

@dikatour
这个测试例子确实可能出现这样的问题。但是在klhttpd中则不会存在,response的内容都交给应用层去做。

  回复  更多评论   

# re: 实现自己的http server 2009-06-01 10:20 zwp

我也写过类似的程序,功能比lz这个稍微丰富一些,呵呵:)
http://sites.google.com/site/cxtools/socket-1/MiniWebServ.rar  回复  更多评论   

# re: 实现自己的http server[未登录] 2010-02-04 19:12 tess

我觉得这个程序有点问题,用浏览器连上服务器以后,程序就进入死循环了,一直进行写操作,而根本没数据可写。最近在学网络开发,感谢楼主提供怎么好的例子:)。  回复  更多评论   

# re: 实现自己的http server 2010-10-25 11:56 xx

Just Test  回复  更多评论   

# re: 实现自己的http server 2010-11-13 21:18 xx

@土仔
想当初Linus也是刚刚买了太新机子,想试试386保护模式下编程而已,没想到写着写着就成linux了。。。  回复  更多评论   

# re: 实现自己的http server[未登录] 2011-10-16 23:13 cc

就这个。。。也好意思拿来显摆。。。  回复  更多评论   

# re: 实现自己的http server 2013-07-29 22:29 yyy

我也写了一个WEB服务器:
http://www.crsky.com/soft/37864.html  回复  更多评论   

# re: 实现自己的http server 2013-07-30 09:26 业汶

学会了写WEB服务器的思路,谢谢楼主。  回复  更多评论   

# re: 实现自己的http server[未登录] 2013-11-12 14:23 Fly

@yyy
你好!看了你写的web服务器,挺不错的。代码能否发一份给我呢?谢谢!
346296192@qq.com  回复  更多评论   

# re: 实现自己的http server 2014-03-20 16:58 io哦i

dfsdfs  回复  更多评论   


只有注册用户登录后才能发表评论。
网站导航: 博客园   IT新闻   BlogJava   知识库   博问   管理