如何实现HTTPSERVER
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, 0, 0 ); /* 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 :一个测试例子。
Rust编程语言群 1036955113
java新手自学群 626070845
java/springboot/hadoop/JVM 群 4915800
Hadoop/mongodb(搭建/开发/运维)Q群481975850
GOLang Q1群:6848027
GOLang Q2群:450509103
GOLang Q3群:436173132
GOLang Q4群:141984758
GOLang Q5群:215535604
C/C++/QT群 1414577
单片机嵌入式/电子电路入门群群 306312845
MUD/LIB/交流群 391486684
Electron/koa/Nodejs/express 214737701
大前端群vue/js/ts 165150391
操作系统研发群:15375777
汇编/辅助/破解新手群:755783453
大数据 elasticsearch 群 481975850
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。
java新手自学群 626070845
java/springboot/hadoop/JVM 群 4915800
Hadoop/mongodb(搭建/开发/运维)Q群481975850
GOLang Q1群:6848027
GOLang Q2群:450509103
GOLang Q3群:436173132
GOLang Q4群:141984758
GOLang Q5群:215535604
C/C++/QT群 1414577
单片机嵌入式/电子电路入门群群 306312845
MUD/LIB/交流群 391486684
Electron/koa/Nodejs/express 214737701
大前端群vue/js/ts 165150391
操作系统研发群:15375777
汇编/辅助/破解新手群:755783453
大数据 elasticsearch 群 481975850
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· SQL Server 2025 AI相关能力初探
· Linux系列:如何用 C#调用 C方法造成内存泄露
· AI与.NET技术实操系列(二):开始使用ML.NET
· 记一次.NET内存居高不下排查解决与启示
· 探究高空视频全景AR技术的实现原理
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南