{"id":23274414,"url":"https://github.com/systemxlabs/tinyhttpd","last_synced_at":"2025-08-21T06:32:05.210Z","repository":{"id":65596358,"uuid":"475788913","full_name":"systemxlabs/tinyhttpd","owner":"systemxlabs","description":"[WIP] Tiny http server implemented in C.  C语言实现的简单http服务器","archived":false,"fork":false,"pushed_at":"2023-01-31T02:47:48.000Z","size":32816,"stargazers_count":5,"open_issues_count":0,"forks_count":0,"subscribers_count":3,"default_branch":"master","last_synced_at":"2023-08-07T10:40:00.956Z","etag":null,"topics":["http-server","httpd"],"latest_commit_sha":null,"homepage":"","language":"C","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/systemxlabs.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2022-03-30T08:31:45.000Z","updated_at":"2023-08-07T08:52:24.000Z","dependencies_parsed_at":"2023-02-16T14:45:55.996Z","dependency_job_id":null,"html_url":"https://github.com/systemxlabs/tinyhttpd","commit_stats":null,"previous_names":["systemxlabs/tinyhttpd"],"tags_count":null,"template":null,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/systemxlabs%2Ftinyhttpd","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/systemxlabs%2Ftinyhttpd/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/systemxlabs%2Ftinyhttpd/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/systemxlabs%2Ftinyhttpd/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/systemxlabs","download_url":"https://codeload.github.com/systemxlabs/tinyhttpd/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":230494927,"owners_count":18235047,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["http-server","httpd"],"created_at":"2024-12-19T20:13:10.724Z","updated_at":"2024-12-19T20:13:11.330Z","avatar_url":"https://github.com/systemxlabs.png","language":"C","funding_links":[],"categories":[],"sub_categories":[],"readme":"# tinyhttpd\nC 语言实现简单的 HTTP 服务器。\n- HTTP/1.1 协议：doc/rfc2616.pdf, [RFC 2616](https://datatracker.ietf.org/doc/html/rfc2616)\n- CGI 1.1 协议：doc/rfc3875.pdf, [RFC 3875](https://datatracker.ietf.org/doc/html/rfc3875)\n- FastCGI 协议：doc/FastCGI-Specification.pdf, [FastCGI 协议规范中文版](https://www.infoq.cn/article/vicwtitzvk7b4ynoej3e)\n- TLS 1.2 协议：doc/rfc5246.pdf, [RFC 5246](https://datatracker.ietf.org/doc/html/rfc5246)\n\nTODO 参考资料\n- http://www.kipway.com/kipway_tlschannel.html\n- http://www.kipway.com/kipway_openssl_usage.html\n\n实现功能\n- [x] 多线程处理\n- [x] 支持静态请求\n- [x] 支持CGI协议\n- [x] 支持Python编写CGI脚本\n- [ ] 自定义服务器配置\n- [ ] 自定义404、500等错误页面\n- [ ] epoll 改造\n- [ ] 支持FastCGI协议\n- [ ] 优雅关闭\n- [ ] 反向代理（转发http请求）\n- [ ] 支持Java servlet\n- [ ] 支持TLS协议\n- [ ] sendfile零拷贝\n- [ ] 支持文件上传下载\n\n## 项目结构\n```\n.\n├── CMakeLists.txt\n├── README.md\n├── cgi-bin\n│   ├── hello-cgi.py\n│   └── student.py\n├── client\n│   └── example_client.c\n├── conf\n│   └── tinyhttpd.conf\n├── html\n│   └── index.html\n├── include\n│   └── tinyhttpd.h\n├── src\n│   ├── request.c\n│   ├── response.c\n│   ├── serve_cgi.c\n│   ├── serve_fcgi.c\n│   ├── serve_file.c\n│   ├── server.c\n│   └── util.c\n└── test\n```\n\n- include: 项目头文件\n- src: 项目源文件\n- test: 项目测试文件\n- conf: 项目配置文件\n- html: 服务器静态资源目录\n- cgi-bin: 服务器 CGI 脚本目录\n- client: 客户端程序\n- doc: 项目文档\n\n## 开始运行\n启动服务器\n1. `mkdir build`\n2. `cd build`\n3. `cmake ..`\n4. `make`\n5. `./tinyhttpd`\n\n客户端发送请求\n1. 通过http client发送请求 `./example_client`（同服务器一同构建）\n2. 通过chrome/postman/curl发送请求\n    1. 发送静态请求 `curl \"http://localhost:8888/index.html\" --verbose`\n    2. 发送cgi请求 `curl \"http://localhost:8888/cgi-bin/student.py?name=Tom\"` / `curl -H \"Content-Type: application/json\" -X POST -d '{\"name\":\"John\",\"age\":18,\"grade\":\"B\"}' \"http://localhost:8888/cgi-bin/student.py\"`\n3. 通过telnet测试\n```\n\u003e telnel localhost 8888\nTrying ::1...\ntelnet: connect to address ::1: Connection refused\nTrying 127.0.0.1...\nConnected to localhost.\nEscape character is '^]'.\nGET /index.html HTTP/1.1\nHost: localhost:8888\nUser-Agent: curl/7.77.0\nAccept: */*\n\n\nHTTP/1.1 200 OK\nContent-Type: text/html\nContent-Length: 142\n\n\u003c!DOCTYPE html\u003e\n\u003chtml lang=\"en\"\u003e\n\u003chead\u003e\n    \u003cmeta charset=\"UTF-8\"\u003e\n    \u003ctitle\u003eHello\u003c/title\u003e\n\u003c/head\u003e\n\u003cbody\u003e\nThis is index page.\n\u003c/body\u003e\n\u003c/html\u003eConnection closed by foreign host.\n```\n\n调试TLS协议\n1. `curl --cacert cert/certificate.pem \"https://localhost:8888/index.html\" --trace -`（`--cacert`将自签名证书作为CA根证书验证）\n2. wireshark抓本地包，选择Loopback: Io\n3. 二进制转16进制：`xxd file \u003e file.hex`\n\n证书\n1. 生成自签名v3证书：`openssl req -newkey rsa:2048 -nodes -keyout key.pem -x509 -extensions v3_ca -days 3650 -out certificate.pem`（需要修改/etc/ssl/openssl.conf）\n2. 检查证书：`openssl x509 -text -noout -in certificate.pem`\n\n注意事项\n1. cgi脚本需要赋予执行权限 `chmod +x xxx.py`\n\n## 请求流程\n![tinyhttpd处理流程](https://raw.githubusercontent.com/lewiszlw/tinyhttpd/master/doc/tinyhttpd%E5%A4%84%E7%90%86%E6%B5%81%E7%A8%8B.png)\n\n### 其他\n### 利用python server体验CGI脚本\n1. 项目根目录下执行 `python3 -m http.server --bind localhost --cgi 8000`\n2. 项目根目录下创建 cgi-bin 子目录 `mkdir cgi-bin`\n3. 在 cgi-bin 目录下编写 CGI 脚本 `cd cgi-bin \u0026\u0026 touch hello-cgi.py`\n4. 在浏览器打开 `http://localhost:8000/cgi-bin/hello-cgi.py`\n\n\n## FAQ\n**1.CGI 是什么？FastCGI 又是什么？**\n\n早期的Web服务器，只能响应浏览器发来的HTTP静态资源的请求，并将存储在服务器中的静态资源返回给浏览器。 随着Web技术的发展，逐渐出现了动态技术，\n但是Web服务器并不能够直接运行动态脚本，为了解决Web服务器与外部应用程序（CGI程序或者叫CGI脚本）之间数据互通，\n于是出现了CGI（Common Gateway Interface）通用网关接口。 简单理解，可以认为CGI是Web服务器和运行其上的应用程序进行“交流”的一种约定。\n\nCGI 是 Web 服务器和一个独立的进程之间的协议，它会把HTTP请求Request的Header头设置成进程的环境变量，HTTP请求的Body正文设置成进程的标准输入，\n进程的标准输出设置为HTTP响应Response，包含Header头和Body正文。通过CGI接口，Web服务器就能够获取客户端传递的数据，\n并转交给服务器端的CGI程序处理，然后返回结果给客户端。\n\nCGI 协议采用 fork-and-exec 模式，即来一个请求，fork一个子进程来exec CGI脚本。子进程会设置环境变量并执行CGI脚本，\n父进程会将请求body发送给子进程， 子进程会将CGI脚本执行结果返回给父进程。\n\nCGI程序运行在独立的进程中，并对每个Web请求创建一个进程，在结束时销毁。这种“每个请求一个新进程”的模型使得CGI程序非常容易实现，但效率较差，难以扩展。\n在高负载情况下，进程创建和销毁进程的开销变得很大。此外，由于地址空间无法共享，CGI进程模型限制了资源重用方法，如重用数据库连接、内存缓存等。\n\nFastCGI 的出现是为了解决 CGI fork-and-exec 模式的低效。TODO\n\n**2. Socket 是什么？和 Unix Domain Socket、Websocket 什么区别？**\n\n**3. Java实现的WEB应用是如何跟httpd配合工作的？**\n\n**4. 如何识别请求是动态请求还是静态请求？**\n\n通过path后缀来识别请求类型，如果是静态请求，则直接返回静态资源，如果是动态请求，则调用cgi程序，并将cgi程序的输出作为返回结果。\n\n**5. 如果整个请求是一段段发送的，如何处理？**\n\n\u003e 《HTTP权威指南》\n\u003e 解析请求报文时，Web 服务器会不定期地从网络上接收输入数据。网络连接可能随时都会出现延迟。\n\u003e Web 服务器需要从网络中读取数据，将部分报文数据临时存储在内存中，直到收到足以进行解析的数据并理解其意义为止。\n\n**6. CGI协议父子进程管道通信，父进程始终是同一个，在高并发请求下，多个请求在同一个父进程内通过管道向各自子进程传输请求body时是否会混乱？**\n\n**7. 常见 web server，如tomcat、nginx和apache httpd等，有什么区别？**\n\n\u003e https://www.zhihu.com/question/32212996\n\n\u003e 其实可以说没有什么区别，HTTP Server本质上来说都是这样几件事：\n\u003e \n\u003e 1.监听端口接收（accept）\n\u003e 2.socket连接解析HTTP请求\n\u003e 3.使用通用或专用协议对请求进行分发\n\u003e 4.接收分发的请求产生的运行结果\n\u003e 5.将结果格式化成HTTP Response并写到socket里面\n\u003e 6.关闭连接或者Keep-Alive\n\u003e \n\u003e 区别一方面在于用了什么语言来实现（Tomcat用Java），一方面是分发时支持的具体协议，\n\u003e Tomcat只支持Servlet接口， \n\u003e Apache和nginx支持CGI、FastCGI、反向代理（可以认为是用HTTP协议进行HTTP请求的分发）、静态资源（可以认为是分发到磁盘读写）等，还可以用扩展模块支持其他分发方式（比如WSGI）。\n\u003e 除此以外就都是实现方式的问题了。\n\u003e \n\u003e 我们进一步可以说一下进程内分发和进程外分发的问题，\n\u003e 有些分发方式是进程内分发的，需要通过内存传递一些对象，这种分发方式通常也是绑定语言的，所以一般必须用相应的语言实现（比如Servlet）；某些语言有特殊的FFI（如Python有C API），也可以通过FFI的方式调用。\n\u003e 另一些分发方式是进程外的，只要序列化格式匹配就可以在不同语言之间通用，如FastCGI、HTTP等，这些分发就可以用统一的方法。\n\n**8. CGI协议为什么要fork一个子进程而不是直接在父进程直接执行脚本？**\n\n由于CGI脚本语言不固定，无法保证脚本语言与服务器语言直接在同一进程内可以进行通信。因此采用进程间通信，只需保证消息格式一致即可。\n如果直接在进程内运行CGI脚本，出现问题会导致服务器受影响。\n\n**9. CGI脚本需要输出响应哪些内容？**\n\nCGI脚本响应分为两类：NPH（Non-Parsed Header） 响应 和 CGI响应。\nNPH响应包含完整的HTTP响应，服务器必须确保脚本输出未经修改发送到客户端。\nCGI响应包括响应内容和一些响应头信息（如Content-Type），服务器接受后需要进一步处理。\n\n**10. TLS记录协议长度占2字节，而上层握手协议长度占3字节，握手协议数据实际长度会超过2个字节会怎样？**\n\n**11. TCP四元组（源地址、源端口、目的地址和目的端口）唯一地定义了一条连接，服务端IP地址和端口、客户端IP地址已确定，只有客户端端口未定，但端口数量存在上限，那能否提高客户端并发连接上限？**\n\n**12. TLS握手为什么要通过pre-master-secret推出master-secret，再用master-secret推出key-block，最后用key-block进行对称加密，而不是直接生成key-block进行加密？**\n\n**13. TLS握手为什么一定要用三个随机数，来生成\"会话密钥\"？**\n\n\u003e https://blog.csdn.net/dog250/article/details/5717162\n\u003e \n\u003e \"不管是客户端还是服务器，都需要随机数，这样生成的密钥才不会每次都一样。由于SSL协议中证书是静态的，因此十分有必要引入一种随机因素来保证协商出来的密钥的随机性。\n\u003e 对于RSA密钥交换算法来说，pre-master-key本身就是一个随机数，再加上hello消息中的随机，三个随机数通过一个密钥导出器最终导出一个对称密钥。\n\u003e pre master的存在在于SSL协议不信任每个主机都能产生完全随机的随机数，如果随机数不随机，那么pre master secret就有可能被猜出来，\n\u003e 那么仅适用pre master secret作为密钥就不合适了，因此必须引入新的随机因素，那么客户端和服务器加上pre master secret三个随机数一同生成的密钥就不容易被猜出了，\n\u003e 一个伪随机可能完全不随机，可是是三个伪随机就十分接近随机了，每增加一个自由度，随机性增加的可不是一。\"\n\n\n## 参考\n- https://github.com/EZLippi/Tinyhttpd\n- https://github.com/leezhxing/tiny-httpd\n- https://github.com/reyk/httpd\n- https://github.com/AngryHacker/articles/blob/master/src/code_reading/tinyhttpd.md\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsystemxlabs%2Ftinyhttpd","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fsystemxlabs%2Ftinyhttpd","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsystemxlabs%2Ftinyhttpd/lists"}