Add:FxHttpServer module.

This commit is contained in:
Fancy code 2024-02-27 22:08:38 -08:00
parent 8a66f07266
commit 24de40d2f6
67 changed files with 12369 additions and 23 deletions

View File

@ -1,9 +1,9 @@
# **关于使用git爬取、上传文件以及创建本地分支的方法总结** # 1. **关于使用git爬取、上传文件以及创建本地分支的方法总结**
## 背景 ## 1.1. 背景
为了使一个有多人的开发团队协作的项目拥有统一的规范管理不至于杂乱无章一个可用、好用的代码管理工具的引入是必要的。目前为止笔者所接触到较多的两大代码管理工具分别是gitee和github本篇规范着重介绍gitee。与任何工具一样相使用好gitee就必然需要按照相对应的方法和步骤。本规范用于介绍在gitee上进行文件获取、上传以及本地分支的创建和提交的方法以及以上步骤遇到问题时的解决方法。 为了使一个有多人的开发团队协作的项目拥有统一的规范管理不至于杂乱无章一个可用、好用的代码管理工具的引入是必要的。目前为止笔者所接触到较多的两大代码管理工具分别是gitee和github本篇规范着重介绍gitee。与任何工具一样相使用好gitee就必然需要按照相对应的方法和步骤。本规范用于介绍在gitee上进行文件获取、上传以及本地分支的创建和提交的方法以及以上步骤遇到问题时的解决方法。
## 操作方法/步骤及其解析 ## 1.2. 操作方法/步骤及其解析
* 爬取文件git clone (所爬取的地址) * 爬取文件git clone (所爬取的地址)
例如git clone https://gitee.com/xxxxxx/git-test.git 例如git clone https://gitee.com/xxxxxx/git-test.git
@ -40,13 +40,4 @@ remote: Total 13 (delta 0), reused 0 (delta 0), pack-reused 0
> git commit -m "fenzhi test.("本次提交内容的行为的备注信息,在一切上传行为中,该步骤都不可省略) > git commit -m "fenzhi test.("本次提交内容的行为的备注信息,在一切上传行为中,该步骤都不可省略)
> git pull origin KAIFA(将服务器中的最新内容下拉与本地的内容进行比较此处的KAIFA可以替换为读者希望下拉到的分支请注意直至下一步之前该步骤是上传前最后的纠错机会请务必确保无误后再操作下一步) > git pull origin KAIFA(将服务器中的最新内容下拉与本地的内容进行比较此处的KAIFA可以替换为读者希望下拉到的分支请注意直至下一步之前该步骤是上传前最后的纠错机会请务必确保无误后再操作下一步)
> git push origin xxxxxx:KAIFA(将缓存区的内容上传至服务器此处的KAIFA可以替换为读者希望上传到的分支xxxxx为操作者新创建的本地分区) > git push origin xxxxxx:KAIFA(将缓存区的内容上传至服务器此处的KAIFA可以替换为读者希望上传到的分支xxxxx为操作者新创建的本地分区)
> git log(该步骤可以将操作者所进行过的步骤以log的形式列出来以便操作者找到关键的信息,该步骤建议无论读者完成任何大型操作之后都进行一次,以便检查自己的操作有无失误) > git log(该步骤可以将操作者所进行过的步骤以log的形式列出来以便操作者找到关键的信息,该步骤建议无论读者完成任何大型操作之后都进行一次,以便检查自己的操作有无失误)

89
doc/vscode_ssh_guide.md Normal file
View File

@ -0,0 +1,89 @@
# 1. vscode使用ssh链接虚拟机服务器
# 2. 前言
   在vscode使用ssh工具远程登录虚拟机服务器进行代码编辑。
| 内容 | 时间 | 作者 | 备注 |
|----|----|----|----|
| 首版 | 2024-2-26 | xjz | - |
## 2.1. Windows系统
* 安装ssh
   直接安装git工具即可支持ssh
安装完后确认:
```
xiaojiazhu@ubuntu:~/project/rkipc/battery/ipc-rk1106/ipc-sdk/cmake-shell$ ssh
usage: ssh [-46AaCfGgKkMNnqsTtVvXxYy] [-B bind_interface]
[-b bind_address] [-c cipher_spec] [-D [bind_address:]port]
[-E log_file] [-e escape_char] [-F configfile] [-I pkcs11]
[-i identity_file] [-J [user@]host[:port]] [-L address]
[-l login_name] [-m mac_spec] [-O ctl_cmd] [-o option] [-p port]
[-Q query_option] [-R address] [-S ctl_path] [-W host:port]
[-w local_tun[:remote_tun]] destination [command]
```
* vscode安装ssh插件
   使用 Remote - SSH 插件
* ssh密钥配置
1. Windows生成密钥
2. 把xxx.pub文件内容拷贝到虚拟机的ssh目录下的authorized_keys文件
此处是:
```
xiaojiazhu@ubuntu:~/project/.ssh$ pwd
/home/xiaojiazhu/project/.ssh
xiaojiazhu@ubuntu:~/project/.ssh$ ls
authorized_keys id_rsa id_rsa.pub
```
这样设置后每次登录ssh无需手动输入密码
3. 在Windows远程登录虚拟机
参考命令ssh xiaojiazhu@192.168.1.29
```
PS C:\Users\xjz\.ssh> ssh xiaojiazhu@192.168.1.29
Welcome to Ubuntu 20.04.6 LTS (GNU/Linux 5.15.0-94-generic x86_64)
* Documentation: https://help.ubuntu.com
* Management: https://landscape.canonical.com
* Support: https://ubuntu.com/advantage
Expanded Security Maintenance for Applications is not enabled.
75 updates can be applied immediately.
To see these additional updates run: apt list --upgradable
9 additional security updates can be applied with ESM Apps.
Learn more about enabling ESM Apps service at https://ubuntu.com/esm
New release '22.04.3 LTS' available.
Run 'do-release-upgrade' to upgrade to it.
Your Hardware Enablement Stack (HWE) is supported until April 2025.
*** System restart required ***
Last login: Sun Feb 25 17:20:04 2024 from 192.168.1.29
xiaojiazhu@ubuntu:~$
```
## 2.2. vscode设置
配置文件参考
```
Host dgiot // 自定义的主机名
HostName 192.168.1.29 // 远端的IP地址
User xiaojiazhu 用户名
Port 22
IdentityFile "C:\Users\xjz\.ssh\id_ed25519" // 本端的私钥文件路径
ForwardAgent yes // 忽略
```
多个远端IP复制即可。

View File

@ -1,3 +1,4 @@
add_subdirectory(sqlite3/sqlite-3430000) add_subdirectory(sqlite3/sqlite-3430000)
add_subdirectory(goahead-5.2.0) add_subdirectory(goahead-5.2.0)
add_subdirectory(httpserver.h-master/src)

16
external/httpserver.h-master/.gitignore vendored Normal file
View File

@ -0,0 +1,16 @@
http-server
valgrind-log.txt
test-results.txt
valgrind-results.txt
*.dSYM/
http-server-cpp
test-results-cpp.txt
http-server-unit
a.out
test/main.cpp
src/parser.c
hs-integration
html
latex
build
.cache

View File

@ -0,0 +1,14 @@
enable_testing()
cmake_minimum_required(VERSION 3.13)
# set the project name
project(httpserver.h)
add_subdirectory(src)
add_subdirectory(test/unit)
add_subdirectory(test/functional)
add_test(NAME Unit COMMAND httpsrv-unit)

2658
external/httpserver.h-master/Doxyfile vendored Normal file

File diff suppressed because it is too large Load Diff

21
external/httpserver.h-master/LICENSE vendored Normal file
View File

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2019 Jeremy Williams
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

30
external/httpserver.h-master/Makefile vendored Normal file
View File

@ -0,0 +1,30 @@
.PHONY: test clean format check-format debug
test: test-unit test-functional test-functional-cpp
test-unit: debug
./build/test/unit/unit-test-runner
test-functional: debug
./test/functional/functional-test-runner
test-functional-cpp: debug
./test/functional/functional-test-runner -cpp
debug: build
cd build; \
cmake -DCMAKE_BUILD_TYPE=Debug -DCMAKE_C_INCLUDE_WHAT_YOU_USE="include-what-you-use;-Xiwyu;--mapping_file=$(shell pwd)/iwyu.imp" ..; \
make; \
cd ..;
build:
mkdir build
format:
find src -name "*.[h|c]" -exec sh -c 'clang-format --style=LLVM $$0 > $$0.frmt; mv $$0.frmt $$0' {} \;
check-format:
clang-format --style=LLVM --dry-run -Werror src/*.c
clean:
@rm -rf build

222
external/httpserver.h-master/README.md vendored Normal file
View File

@ -0,0 +1,222 @@
[![Build Status](https://travis-ci.com/jeremycw/httpserver.h.svg?branch=master)](https://travis-ci.com/jeremycw/httpserver.h)
See `httpserver.h` for API documentation
# Description
httpserver.h is a single header C library for building event driven non-blocking HTTP servers
Supports Linux with epoll and BSD/Mac with kqueue.
# Example
```c
#define HTTPSERVER_IMPL
#include "httpserver.h"
#define RESPONSE "Hello, World!"
void handle_request(struct http_request_s* request) {
struct http_response_s* response = http_response_init();
http_response_status(response, 200);
http_response_header(response, "Content-Type", "text/plain");
http_response_body(response, RESPONSE, sizeof(RESPONSE) - 1);
http_respond(request, response);
}
int main() {
struct http_server_s* server = http_server_init(8080, handle_request);
http_server_listen(server);
}
```
See full documentation in `httpserver.h`
# Benchmark
I ran some micro-benchmarks with httpserver.h and NGINX serving a simple Hello, World!
The purpose here was just to get a performance reference point, not to try and make
any statement of superiority since httpserver.h is not a competitor or replacement for
NGINX.
## With `keep-alive`
`ab -k -c 200 -n 100000 http://localhost:8080/`
### NGINX 74441.26 requests/sec
```
Server Software: nginx/1.14.0
Server Hostname: localhost
Server Port: 8000
Document Path: /
Document Length: 13 bytes
Concurrency Level: 200
Time taken for tests: 1.343 seconds
Complete requests: 100000
Failed requests: 0
Keep-Alive requests: 99052
Total transferred: 20995260 bytes
HTML transferred: 1300000 bytes
Requests per second: 74441.26 [#/sec] (mean)
Time per request: 2.687 [ms] (mean)
Time per request: 0.013 [ms] (mean, across all concurrent requests)
Transfer rate: 15262.83 [Kbytes/sec] received
Connection Times (ms)
min mean[+/-sd] median max
Connect: 0 0 17.8 0 1029
Processing: 0 2 8.8 1 148
Waiting: 0 2 8.8 1 148
Total: 0 3 21.4 1 1150
Percentage of the requests served within a certain time (ms)
50% 1
66% 2
75% 2
80% 2
90% 2
95% 2
98% 3
99% 35
100% 1150 (longest request)
```
### httpserver.h 123907.91 requests/sec
```
Server Software:
Server Hostname: localhost
Server Port: 8080
Document Path: /
Document Length: 13 bytes
Concurrency Level: 200
Time taken for tests: 0.807 seconds
Complete requests: 100000
Failed requests: 0
Keep-Alive requests: 100000
Total transferred: 13400000 bytes
HTML transferred: 1300000 bytes
Requests per second: 123907.91 [#/sec] (mean)
Time per request: 1.614 [ms] (mean)
Time per request: 0.008 [ms] (mean, across all concurrent requests)
Transfer rate: 16214.51 [Kbytes/sec] received
Connection Times (ms)
min mean[+/-sd] median max
Connect: 0 0 0.1 0 4
Processing: 1 2 0.3 2 6
Waiting: 1 2 0.3 2 6
Total: 1 2 0.4 2 6
Percentage of the requests served within a certain time (ms)
50% 2
66% 2
75% 2
80% 2
90% 2
95% 2
98% 3
99% 3
100% 6 (longest request)
```
## With `Connection: close`
`ab -c 200 -n 100000 http://localhost:8080/`
### NGINX 15773.47 requests/sec
```
Server Software: nginx/1.14.0
Server Hostname: localhost
Server Port: 8000
Document Path: /
Document Length: 13 bytes
Concurrency Level: 200
Time taken for tests: 6.340 seconds
Complete requests: 100000
Failed requests: 0
Total transferred: 20500000 bytes
HTML transferred: 1300000 bytes
Requests per second: 15773.47 [#/sec] (mean)
Time per request: 12.680 [ms] (mean)
Time per request: 0.063 [ms] (mean, across all concurrent requests)
Transfer rate: 3157.77 [Kbytes/sec] received
Connection Times (ms)
min mean[+/-sd] median max
Connect: 0 4 40.6 3 1123
Processing: 1 8 46.7 4 508
Waiting: 1 7 46.7 3 508
Total: 3 12 64.5 6 1460
Percentage of the requests served within a certain time (ms)
50% 6
66% 7
75% 7
80% 8
90% 8
95% 9
98% 10
99% 503
100% 1460 (longest request)
```
### httpserver.h 27605.45 requests/sec
```
Server Software:
Server Hostname: localhost
Server Port: 8080
Document Path: /
Document Length: 13 bytes
Concurrency Level: 200
Time taken for tests: 3.622 seconds
Complete requests: 100000
Failed requests: 0
Total transferred: 12900000 bytes
HTML transferred: 1300000 bytes
Requests per second: 27605.45 [#/sec] (mean)
Time per request: 7.245 [ms] (mean)
Time per request: 0.036 [ms] (mean, across all concurrent requests)
Transfer rate: 3477.64 [Kbytes/sec] received
Connection Times (ms)
min mean[+/-sd] median max
Connect: 2 3 0.5 3 6
Processing: 1 4 0.7 4 8
Waiting: 1 3 0.8 3 7
Total: 4 7 0.6 7 11
Percentage of the requests served within a certain time (ms)
50% 7
66% 7
75% 8
80% 8
90% 8
95% 8
98% 9
99% 9
100% 11 (longest request)
```
### NGINX conf
```
worker_processes 1;
http {
server {
listen 8000;
location / {
add_header Content-Type text/plain;
return 200 'Hello, World!';
}
}
}
```

View File

@ -0,0 +1,77 @@
[
{
"directory": "/Users/jeremywilliams/code/httpserver.h/build/src",
"command": "/Library/Developer/CommandLineTools/usr/bin/cc -DKQUEUE -I/Users/jeremywilliams/code/httpserver.h/src -I/Users/jeremywilliams/code/httpserver.h/build/src -g -isysroot /Library/Developer/CommandLineTools/SDKs/MacOSX12.3.sdk -Wall -Wextra -Werror -fsanitize=address -fsanitize=undefined -fno-sanitize-recover=all -o CMakeFiles/evhttpserv.dir/api.c.o -c /Users/jeremywilliams/code/httpserver.h/src/api.c",
"file": "/Users/jeremywilliams/code/httpserver.h/src/api.c"
},
{
"directory": "/Users/jeremywilliams/code/httpserver.h/build/src",
"command": "/Library/Developer/CommandLineTools/usr/bin/cc -DKQUEUE -I/Users/jeremywilliams/code/httpserver.h/src -I/Users/jeremywilliams/code/httpserver.h/build/src -g -isysroot /Library/Developer/CommandLineTools/SDKs/MacOSX12.3.sdk -Wall -Wextra -Werror -fsanitize=address -fsanitize=undefined -fno-sanitize-recover=all -o CMakeFiles/evhttpserv.dir/connection.c.o -c /Users/jeremywilliams/code/httpserver.h/src/connection.c",
"file": "/Users/jeremywilliams/code/httpserver.h/src/connection.c"
},
{
"directory": "/Users/jeremywilliams/code/httpserver.h/build/src",
"command": "/Library/Developer/CommandLineTools/usr/bin/cc -DKQUEUE -I/Users/jeremywilliams/code/httpserver.h/src -I/Users/jeremywilliams/code/httpserver.h/build/src -g -isysroot /Library/Developer/CommandLineTools/SDKs/MacOSX12.3.sdk -Wall -Wextra -Werror -fsanitize=address -fsanitize=undefined -fno-sanitize-recover=all -o CMakeFiles/evhttpserv.dir/io_events.c.o -c /Users/jeremywilliams/code/httpserver.h/src/io_events.c",
"file": "/Users/jeremywilliams/code/httpserver.h/src/io_events.c"
},
{
"directory": "/Users/jeremywilliams/code/httpserver.h/build/src",
"command": "/Library/Developer/CommandLineTools/usr/bin/cc -DKQUEUE -I/Users/jeremywilliams/code/httpserver.h/src -I/Users/jeremywilliams/code/httpserver.h/build/src -g -isysroot /Library/Developer/CommandLineTools/SDKs/MacOSX12.3.sdk -Wall -Wextra -Werror -fsanitize=address -fsanitize=undefined -fno-sanitize-recover=all -Wno-unused-variable -o CMakeFiles/evhttpserv.dir/parser.c.o -c /Users/jeremywilliams/code/httpserver.h/build/src/parser.c",
"file": "/Users/jeremywilliams/code/httpserver.h/build/src/parser.c"
},
{
"directory": "/Users/jeremywilliams/code/httpserver.h/build/src",
"command": "/Library/Developer/CommandLineTools/usr/bin/cc -DKQUEUE -I/Users/jeremywilliams/code/httpserver.h/src -I/Users/jeremywilliams/code/httpserver.h/build/src -g -isysroot /Library/Developer/CommandLineTools/SDKs/MacOSX12.3.sdk -Wall -Wextra -Werror -fsanitize=address -fsanitize=undefined -fno-sanitize-recover=all -o CMakeFiles/evhttpserv.dir/read_socket.c.o -c /Users/jeremywilliams/code/httpserver.h/src/read_socket.c",
"file": "/Users/jeremywilliams/code/httpserver.h/src/read_socket.c"
},
{
"directory": "/Users/jeremywilliams/code/httpserver.h/build/src",
"command": "/Library/Developer/CommandLineTools/usr/bin/cc -DKQUEUE -I/Users/jeremywilliams/code/httpserver.h/src -I/Users/jeremywilliams/code/httpserver.h/build/src -g -isysroot /Library/Developer/CommandLineTools/SDKs/MacOSX12.3.sdk -Wall -Wextra -Werror -fsanitize=address -fsanitize=undefined -fno-sanitize-recover=all -o CMakeFiles/evhttpserv.dir/request_util.c.o -c /Users/jeremywilliams/code/httpserver.h/src/request_util.c",
"file": "/Users/jeremywilliams/code/httpserver.h/src/request_util.c"
},
{
"directory": "/Users/jeremywilliams/code/httpserver.h/build/src",
"command": "/Library/Developer/CommandLineTools/usr/bin/cc -DKQUEUE -I/Users/jeremywilliams/code/httpserver.h/src -I/Users/jeremywilliams/code/httpserver.h/build/src -g -isysroot /Library/Developer/CommandLineTools/SDKs/MacOSX12.3.sdk -Wall -Wextra -Werror -fsanitize=address -fsanitize=undefined -fno-sanitize-recover=all -o CMakeFiles/evhttpserv.dir/respond.c.o -c /Users/jeremywilliams/code/httpserver.h/src/respond.c",
"file": "/Users/jeremywilliams/code/httpserver.h/src/respond.c"
},
{
"directory": "/Users/jeremywilliams/code/httpserver.h/build/src",
"command": "/Library/Developer/CommandLineTools/usr/bin/cc -DKQUEUE -I/Users/jeremywilliams/code/httpserver.h/src -I/Users/jeremywilliams/code/httpserver.h/build/src -g -isysroot /Library/Developer/CommandLineTools/SDKs/MacOSX12.3.sdk -Wall -Wextra -Werror -fsanitize=address -fsanitize=undefined -fno-sanitize-recover=all -o CMakeFiles/evhttpserv.dir/server.c.o -c /Users/jeremywilliams/code/httpserver.h/src/server.c",
"file": "/Users/jeremywilliams/code/httpserver.h/src/server.c"
},
{
"directory": "/Users/jeremywilliams/code/httpserver.h/build/src",
"command": "/Library/Developer/CommandLineTools/usr/bin/cc -DKQUEUE -I/Users/jeremywilliams/code/httpserver.h/src -I/Users/jeremywilliams/code/httpserver.h/build/src -g -isysroot /Library/Developer/CommandLineTools/SDKs/MacOSX12.3.sdk -Wall -Wextra -Werror -fsanitize=address -fsanitize=undefined -fno-sanitize-recover=all -o CMakeFiles/evhttpserv.dir/write_socket.c.o -c /Users/jeremywilliams/code/httpserver.h/src/write_socket.c",
"file": "/Users/jeremywilliams/code/httpserver.h/src/write_socket.c"
},
{
"directory": "/Users/jeremywilliams/code/httpserver.h/build/test/unit",
"command": "/Library/Developer/CommandLineTools/usr/bin/cc -DKQUEUE -I/Users/jeremywilliams/code/httpserver.h/src -g -isysroot /Library/Developer/CommandLineTools/SDKs/MacOSX12.3.sdk -o CMakeFiles/evhs-unit.dir/munit.c.o -c /Users/jeremywilliams/code/httpserver.h/test/unit/munit.c",
"file": "/Users/jeremywilliams/code/httpserver.h/test/unit/munit.c"
},
{
"directory": "/Users/jeremywilliams/code/httpserver.h/build/test/unit",
"command": "/Library/Developer/CommandLineTools/usr/bin/cc -DKQUEUE -I/Users/jeremywilliams/code/httpserver.h/src -g -isysroot /Library/Developer/CommandLineTools/SDKs/MacOSX12.3.sdk -o CMakeFiles/evhs-unit.dir/test_parser.c.o -c /Users/jeremywilliams/code/httpserver.h/test/unit/test_parser.c",
"file": "/Users/jeremywilliams/code/httpserver.h/test/unit/test_parser.c"
},
{
"directory": "/Users/jeremywilliams/code/httpserver.h/build/test/unit",
"command": "/Library/Developer/CommandLineTools/usr/bin/cc -DKQUEUE -I/Users/jeremywilliams/code/httpserver.h/src -g -isysroot /Library/Developer/CommandLineTools/SDKs/MacOSX12.3.sdk -o CMakeFiles/evhs-unit.dir/test_read_socket.c.o -c /Users/jeremywilliams/code/httpserver.h/test/unit/test_read_socket.c",
"file": "/Users/jeremywilliams/code/httpserver.h/test/unit/test_read_socket.c"
},
{
"directory": "/Users/jeremywilliams/code/httpserver.h/build/test/unit",
"command": "/Library/Developer/CommandLineTools/usr/bin/cc -DKQUEUE -I/Users/jeremywilliams/code/httpserver.h/src -g -isysroot /Library/Developer/CommandLineTools/SDKs/MacOSX12.3.sdk -o CMakeFiles/evhs-unit.dir/test_write_socket.c.o -c /Users/jeremywilliams/code/httpserver.h/test/unit/test_write_socket.c",
"file": "/Users/jeremywilliams/code/httpserver.h/test/unit/test_write_socket.c"
},
{
"directory": "/Users/jeremywilliams/code/httpserver.h/build/test/unit",
"command": "/Library/Developer/CommandLineTools/usr/bin/cc -DKQUEUE -I/Users/jeremywilliams/code/httpserver.h/src -g -isysroot /Library/Developer/CommandLineTools/SDKs/MacOSX12.3.sdk -o CMakeFiles/evhs-unit.dir/main.c.o -c /Users/jeremywilliams/code/httpserver.h/test/unit/main.c",
"file": "/Users/jeremywilliams/code/httpserver.h/test/unit/main.c"
},
{
"directory": "/Users/jeremywilliams/code/httpserver.h/build/test/integration",
"command": "/Library/Developer/CommandLineTools/usr/bin/cc -DKQUEUE -I/Users/jeremywilliams/code/httpserver.h/src -g -isysroot /Library/Developer/CommandLineTools/SDKs/MacOSX12.3.sdk -o CMakeFiles/evhs-int.dir/main.c.o -c /Users/jeremywilliams/code/httpserver.h/test/integration/main.c",
"file": "/Users/jeremywilliams/code/httpserver.h/test/integration/main.c"
}
]

2818
external/httpserver.h-master/httpserver.h vendored Normal file

File diff suppressed because it is too large Load Diff

6
external/httpserver.h-master/iwyu.imp vendored Normal file
View File

@ -0,0 +1,6 @@
[
{ include: ["<bits/types/struct_itimerspec.h>", "private", "<time.h>", "public"] },
{ include: ["<sys/fcntl.h>", "private", "<fcntl.h>", "public"] },
{ include: ["<sys/signal.h>", "private", "<signal.h>", "public"] },
{ include: ["<sys/errno.h>", "private", "<errno.h>", "public"] }
]

View File

@ -0,0 +1,77 @@
add_library(
httpsrv
api.c connection.c io_events.c ${CMAKE_CURRENT_BINARY_DIR}/parser.c
read_socket.c request_util.c respond.c server.c write_socket.c
)
set_property(TARGET httpsrv PROPERTY C_STANDARD 99)
target_compile_options(
httpsrv
PRIVATE -Wall -Wextra -Werror
# $<$<CONFIG:DEBUG>:-fsanitize=address -fsanitize=undefined -fno-sanitize-recover=all>
)
# target_link_options(
# httpsrv
# PUBLIC $<$<CONFIG:DEBUG>:-fsanitize=address -fsanitize=undefined -fno-sanitize-recover=all>
# )
set_source_files_properties(
${CMAKE_CURRENT_BINARY_DIR}/parser.c
PROPERTIES COMPILE_FLAGS -Wno-unused-variable
)
find_program(M4 m4)
if(NOT M4)
message(FATAL_ERROR "m4 not found. Please install before continuing.")
endif()
add_custom_target(
httpserver.h
ALL
COMMAND ${M4} -I${CMAKE_CURRENT_SOURCE_DIR} ${CMAKE_CURRENT_SOURCE_DIR}/httpserver.m4 > ${CMAKE_CURRENT_BINARY_DIR}/httpserver.h
DEPENDS api.h api.c buffer_util.h common.h connection.h connection.c
io_events.h io_events.c parser.h ${CMAKE_CURRENT_BINARY_DIR}/parser.c
read_socket.h read_socket.c request_util.h request_util.c respond.h respond.c
server.h server.c write_socket.h write_socket.c httpserver.m4
)
find_program(RAGEL ragel)
if(NOT RAGEL)
message(FATAL_ERROR "ragel not found. Please install before continuing.")
endif()
add_custom_command(
OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/parser.c
COMMAND ${RAGEL} ${CMAKE_CURRENT_SOURCE_DIR}/parser.rl -o ${CMAKE_CURRENT_BINARY_DIR}/parser.c
DEPENDS parser.rl
)
target_include_directories(
httpsrv
PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}
)
include(CheckFunctionExists)
check_function_exists(epoll_wait EPOLL)
check_function_exists(kqueue KQUEUE)
# target_compile_definitions(httpsrv PRIVATE $<$<CONFIG:DEBUG>:DEBUG>)
if(KQUEUE)
target_compile_definitions(httpsrv PRIVATE KQUEUE)
endif()
if(EPOLL)
target_compile_definitions(httpsrv PRIVATE EPOLL)
endif()
message("${PLATFORM_PATH}/cmake-shell/external${SUBMODULE_PATH_OF_IPC_SDK}/httpserver.h-master/src/libhttpsrv.a")
add_custom_command(
TARGET httpsrv
POST_BUILD
COMMAND cp ${PLATFORM_PATH}/cmake-shell${SUBMODULE_PATH_OF_IPC_SDK}/external/httpserver.h-master/src/libhttpsrv.a ${EXTERNAL_LIBS_OUTPUT_PATH}
WORKING_DIRECTORY ${PLATFORM_PATH}/cmake-shell/
)

132
external/httpserver.h-master/src/api.c vendored Normal file
View File

@ -0,0 +1,132 @@
#include <stdlib.h>
#ifndef HTTPSERVER_IMPL
#include "api.h"
#include "buffer_util.h"
#include "common.h"
#include "io_events.h"
#include "request_util.h"
#include "respond.h"
#include "server.h"
#endif
int http_request_has_flag(http_request_t *request, int flag) {
return HTTP_FLAG_CHECK(request->flags, flag);
}
int http_server_loop(http_server_t *server) { return server->loop; }
http_server_t *http_server_init(int port, void (*handler)(http_request_t *)) {
#ifdef KQUEUE
return hs_server_init(port, handler, hs_on_kqueue_server_event, NULL);
#else
return hs_server_init(port, handler, hs_on_epoll_server_connection_event,
hs_on_epoll_server_timer_event);
#endif
}
void http_request_free_buffer(http_request_t *request) {
_hs_buffer_free(&request->buffer, &request->server->memused);
}
void *http_request_userdata(http_request_t *request) { return request->data; }
void http_request_set_userdata(http_request_t *request, void *data) {
request->data = data;
}
void http_server_set_userdata(struct http_server_s *serv, void *data) {
serv->data = data;
}
void *http_request_server_userdata(struct http_request_s *request) {
return request->server->data;
}
int http_request_iterate_headers(http_request_t *request, http_string_t *key,
http_string_t *val, int *iter) {
return hs_request_iterate_headers(request, key, val, iter);
}
http_string_t http_request_header(http_request_t *request, char const *key) {
return hs_request_header(request, key);
}
void http_request_connection(http_request_t *request, int directive) {
hs_request_set_keep_alive_flag(request, directive);
}
http_string_t http_request_chunk(struct http_request_s *request) {
return hs_request_chunk(request);
}
http_response_t *http_response_init() { return hs_response_init(); }
void http_response_header(http_response_t *response, char const *key,
char const *value) {
return hs_response_set_header(response, key, value);
}
void http_response_status(http_response_t *response, int status) {
hs_response_set_status(response, status);
}
void http_response_body(http_response_t *response, char const *body,
int length) {
hs_response_set_body(response, body, length);
}
void http_respond(http_request_t *request, http_response_t *response) {
hs_request_respond(request, response, hs_request_begin_write);
}
void http_respond_chunk(http_request_t *request, http_response_t *response,
void (*cb)(http_request_t *)) {
hs_request_respond_chunk(request, response, cb, hs_request_begin_write);
}
void http_respond_chunk_end(http_request_t *request,
http_response_t *response) {
hs_request_respond_chunk_end(request, response, hs_request_begin_write);
}
http_string_t http_request_method(http_request_t *request) {
return hs_get_token_string(request, HSH_TOK_METHOD);
}
http_string_t http_request_target(http_request_t *request) {
return hs_get_token_string(request, HSH_TOK_TARGET);
}
http_string_t http_request_body(http_request_t *request) {
return hs_get_token_string(request, HSH_TOK_BODY);
}
int http_server_listen(http_server_t *serv) {
return hs_server_run_event_loop(serv, NULL);
}
int http_server_listen_addr(http_server_t *serv, const char *ipaddr) {
return hs_server_run_event_loop(serv, ipaddr);
}
int http_server_poll(http_server_t *serv) {
return hs_server_poll_events(serv);
}
int http_server_listen_poll(http_server_t *serv) {
hs_server_listen_on_addr(serv, NULL);
return 0;
}
int http_server_listen_addr_poll(http_server_t *serv, const char *ipaddr) {
hs_server_listen_on_addr(serv, ipaddr);
return 0;
}
void http_request_read_chunk(struct http_request_s *request,
void (*chunk_cb)(struct http_request_s *)) {
request->state = HTTP_SESSION_READ;
request->chunk_cb = chunk_cb;
hs_request_begin_read(request);
}

489
external/httpserver.h-master/src/api.h vendored Normal file
View File

@ -0,0 +1,489 @@
#ifndef HS_API_H
#define HS_API_H
/** * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
* @file api.h
*
* MIT License
*
* Copyright (c) 2019 Jeremy Williams
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
*
* httpserver.h (0.9.0)
*
* Description:
*
* A single header C library for building non-blocking event driven HTTP
* servers
*
* Usage:
*
* Do this:
* #define HTTPSERVER_IMPL
* before you include this file in *one* C or C++ file to create the
* implementation.
*
* // i.e. it should look like this:
* #include ...
* #include ...
* #include ...
* #define HTTPSERVER_IMPL
* #include "httpserver.h"
*
* There are some #defines that can be configured. This must be done in the
* same file that you define HTTPSERVER_IMPL These defines have default values
* and will need to be #undef'd and redefined to configure them.
*
* HTTP_REQUEST_BUF_SIZE - default 1024 - The initial size in bytes of the
* read buffer for the request. This buffer grows automatically if it's
* capacity is reached but it certain environments it may be optimal to
* change this value.
*
* HTTP_RESPONSE_BUF_SIZE - default 1024 - Same as above except for the
* response buffer.
*
* HTTP_REQUEST_TIMEOUT - default 20 - The amount of seconds the request
* will wait for activity on the socket before closing. This only applies mid
* request. For the amount of time to hold onto keep-alive connections see
* below.
*
* HTTP_KEEP_ALIVE_TIMEOUT - default 120 - The amount of seconds to keep a
* connection alive a keep-alive request has completed.
*
* HTTP_MAX_TOTAL_EST_MEM_USAGE - default 4294967296 (4GB) - This is the
* amount of read/write buffer space that is allowed to be allocated
* across all requests before new requests will get 503 responses.
*
* HTTP_MAX_TOKEN_LENGTH - default 8192 (8KB) - This is the max size of any
* non body http tokens. i.e: header names, header values, url length,
* etc.
*
* HTTP_MAX_REQUEST_BUF_SIZE - default 8388608 (8MB) - This is the maximum
* amount of bytes that the request buffer will grow to. If the body of
* the request + headers cannot fit in this size the request body will be
* streamed in.
*
* For more details see the documentation of the interface and the example
* below.
*
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
#ifdef __cplusplus
extern "C" {
#endif
// String type used to read the request details. The char pointer is NOT null
// terminated.
struct http_string_s;
struct http_server_s;
struct http_request_s;
struct http_response_s;
/**
* Get the event loop descriptor that the server is running on.
*
* This will be an epoll fd when running on Linux or a kqueue on BSD. This can
* be used to listen for activity on sockets, etc. The only caveat is that the
* user data must be set to a struct where the first member is the function
* pointer to a callback that will handle the event. i.e:
*
* For kevent:
*
* struct foo {
* void (*handler)(struct kevent*);
* ...
* }
*
* // Set ev.udata to a foo pointer when registering the event.
*
* For epoll:
*
* struct foo {
* void (*handler)(struct epoll_event*);
* ...
* }
*
* // Set ev.data.ptr to a foo pointer when registering the event.
*
* @param server The server.
*
* @return The descriptor of the event loop.
*/
int http_server_loop(struct http_server_s *server);
/**
* Allocates and initializes the http server.
*
* @param port The port to listen on.
* @param handler The callback that will fire to handle requests.
*
* @return Pointer to the allocated server.
*/
struct http_server_s *
http_server_init(int port, void (*handler)(struct http_request_s *));
/**
* Listens on the server socket and starts an event loop.
*
* During normal operation this function will not return.
*
* @param server The server.
* @param ipaddr The ip to bind to if NULL binds to all interfaces.
*
* @return Error code if the server fails.
*/
int http_server_listen_addr(struct http_server_s *server, const char *ipaddr);
/**
* See http_server_listen_addr
*/
int http_server_listen(struct http_server_s *server);
/**
* Poll the server socket on specific interface.
*
* Use this listen call in place of the one above when you want to integrate
* an http server into an existing application that has a loop already and you
* want to use the polling functionality instead. This works well for
* applications like games that have a constant update loop.
*
* @param server The server.
* @param ipaddr The ip to bind to if NULL bind to all.
*
* @return Error code if the poll fails.
*/
int http_server_listen_addr_poll(struct http_server_s *server,
const char *ipaddr);
/**
* Poll the server socket on all interfaces. See http_server_listen_addr_poll
*
* @param server The server.
*
* @return Error code if the poll fails.
*/
int http_server_listen_poll(struct http_server_s *server);
/**
* Poll of the request sockets.
*
* Call this function in your update loop. It will trigger the request handler
* once if there is a request ready. It should be called in a loop until it
* returns 0.
*
* @param server The server.
*
* @return Returns 1 if a request was handled and 0 if no requests were handled.
*/
int http_server_poll(struct http_server_s *server);
/**
* Check if a request flag is set.
*
* The flags that can be queried are listed below:
*
* HTTP_FLG_STREAMED
*
* This flag will be set when the request body is chunked or the body is too
* large to fit in memory are once. This means that the
* http_request_read_chunk function must be used to read the body piece by
* piece.
*
* @param request The request.
* @param flag One of the flags listed above.
*
* @return 1 or 0 if the flag is set or not respectively.
*/
int http_request_has_flag(struct http_request_s *request, int flag);
/**
* Returns the request method as it was read from the HTTP request line.
*
* @param request The request.
*
* @return The HTTP method.
*/
struct http_string_s http_request_method(struct http_request_s *request);
/**
* Returns the full request target (url) from the HTTP request line.
*
* @param request The request.
*
* @return The target.
*/
struct http_string_s http_request_target(struct http_request_s *request);
/**
* Retrieves the request body.
*
* @param request The request.
*
* @return The request body. If no request body was sent buf and len of the
* string will be set to 0.
*/
struct http_string_s http_request_body(struct http_request_s *request);
/**
* Returns the request header value for the given header key.
*
* @param request The request.
* @param key The case insensitive header key to search for.
*
* @return The value for the header matching the key. Will be length 0 if not
* found.
*/
struct http_string_s http_request_header(struct http_request_s *request,
char const *key);
/**
* Iterate over the request headers.
*
* Each call will set key and val to the key and value of the next header.
*
* @param request The request.
* @param[out] key The key of the header.
* @param[out] value The key of the header.
* @param[inout] iter Should be initialized to 0 before calling. Pass back in
* with each consecutive call.
*
* @return 0 when there are no more headers.
*/
int http_request_iterate_headers(struct http_request_s *request,
struct http_string_s *key,
struct http_string_s *val, int *iter);
/**
* Stores an arbitrary userdata pointer for this request.
*
* This is not used by the library in any way and is strictly for you, the
* application programmer to make use of.
*
* @param request The request.
* @param data Opaque pointer to user data.
*/
void http_request_set_userdata(struct http_request_s *request, void *data);
/**
* Retrieve the opaque data pointer that was set with http_request_set_userdata.
*
* @param request The request.
*/
void *http_request_userdata(struct http_request_s *request);
/**
* Stores a server wide opaque pointer for future retrieval.
*
* This is not used by the library in any way and is strictly for you, the
* application programmer to make use of.
*
* @param server The server.
* @param data Opaque data pointer.
*/
void http_server_set_userdata(struct http_server_s *server, void *data);
/**
* Retrieve the server wide userdata pointer.
*
* @param request The request.
*/
void *http_request_server_userdata(struct http_request_s *request);
/**
* Sets how the request will handle it's connection
*
* By default the server will inspect the Connection header and the HTTP
* version to determine whether the connection should be kept alive or not.
* Use this function to override that behaviour to force the connection to
* keep-alive or close by passing in the HTTP_KEEP_ALIVE or HTTP_CLOSE
* directives respectively. This may provide a minor performance improvement
* in cases where you control client and server and want to always close or
* keep the connection alive.
*
* @param request The request.
* @param directive One of HTTP_KEEP_ALIVE or HTTP_CLOSE
*/
void http_request_connection(struct http_request_s *request, int directive);
/**
* Frees the buffer of a request.
*
* When reading in the HTTP request the server allocates a buffer to store
* the request details such as the headers, method, body, etc. By default this
* memory will be freed when http_respond is called. This function lets you
* free that memory before the http_respond call. This can be useful if you
* have requests that take a long time to complete and you don't require the
* request data. Accessing any http_string_s's will be invalid after this call.
*
* @param request The request to free the buffer of.
*/
void http_request_free_buffer(struct http_request_s *request);
/**
* Allocates an http response.
*
* This memory will be freed when http_respond is called.
*
* @return Allocated response.
*/
struct http_response_s *http_response_init();
/**
* Set the response status.
*
* Accepts values between 100 and 599 inclusive. Any other value will map to
* 500.
*
* @param response The response struct to set status on.
* @param status The HTTP status code.
*/
void http_response_status(struct http_response_s *response, int status);
/**
* Sets an HTTP response header.
*
* @param response The response struct to set the header on.
* @param key The null-terminated key of the header eg: Content-Type
* @param value The null-terminated value of the header eg: application/json
*/
void http_response_header(struct http_response_s *response, char const *key,
char const *value);
/**
* Set the response body.
*
* The caller is responsible for freeing any memory that
* may have been allocated for the body. It is safe to free this memory AFTER
* http_respond has been called. If responding with chunked transfer encoding
* this will become a single chunk. This procedure can be used again to set
* subsequent chunks.
*
* @param response The response struct to set the body for.
* @param body The body of the response.
* @param length The length of the body
*/
void http_response_body(struct http_response_s *response, char const *body,
int length);
/**
* Starts writing the response to the client.
*
* Any memory allocated for the response body or response headers is safe to
* free after this call. Adds the default HTTP response headers, Date and
* Connection.
*
* @param request The request to respond to.
* @param response The response to respond with.
*/
void http_respond(struct http_request_s *request,
struct http_response_s *response);
/**
* Writes a chunk to the client.
*
* The notify_done callback will be called when the write is complete. This call
* consumes the response so a new response will need to be initialized for each
* chunk. The response status of the request will be the response status that is
* set when http_respond_chunk is called the first time. Any headers set for the
* first call will be sent as the response headers. Transfer-Encoding header
* will automatically be set to chunked. Headers set for subsequent calls will
* be ignored.
*
* @param request The request to respond to.
* @param response The response to respond with.
* @param notify_done The callback that's used to signal user code that another
* chunk is ready to be written out.
*/
void http_respond_chunk(struct http_request_s *request,
struct http_response_s *response,
void (*notify_done)(struct http_request_s *));
/**
* Ends the chunked response.
*
* Used to signal end of transmission on chunked requests. Any headers set
* before this call will be included as what the HTTP spec refers to as
* 'trailers' which are essentially more response headers.
*
* @param request The request to respond to.
* @param response The response to respond with.
*/
void http_respond_chunk_end(struct http_request_s *request,
struct http_response_s *response);
/**
* Read a chunk of the request body.
*
* If a request has Transfer-Encoding: chunked or the body is too big to fit in
* memory all at once you cannot read the body in the typical way. Instead you
* need to call this function to read one chunk at a time. To check if the
* request requires this type of reading you can call the http_request_has_flag
* function to check if the HTTP_FLG_STREAMED flag is set. To read a streamed
* body you pass a callback that will be called when the chunk is ready. When
* the callback is called you can use 'http_request_chunk' to get the current
* chunk. When done with that chunk call this function again to request the
* next chunk. If the chunk has size 0 then the request body has been completely
* read and you can now respond.
*
* @param request The request.
* @param chunk_cb Callback for when the chunk is ready.
*/
void http_request_read_chunk(struct http_request_s *request,
void (*chunk_cb)(struct http_request_s *));
/**
* Returns the current chunk of the request body.
*
* This chunk is only valid until the next call to 'http_request_read_chunk'.
*
* @param request The request.
*
* @return The chunk data.
*/
struct http_string_s http_request_chunk(struct http_request_s *request);
#define http_request_read_body http_request_read_chunk
#ifdef __cplusplus
}
#endif
// Minimal example usage.
#ifdef HTTPSERVER_EXAMPLE
#define RESPONSE "Hello, World!"
void handle_request(struct http_request_s *request) {
struct http_response_s *response = http_response_init();
http_response_status(response, 200);
http_response_header(response, "Content-Type", "text/plain");
http_response_body(response, RESPONSE, sizeof(RESPONSE) - 1);
http_respond(request, response);
}
int main() {
struct http_server_s *server = http_server_init(8080, handle_request);
http_server_listen(server);
}
#endif
#endif

View File

@ -0,0 +1,19 @@
#ifndef HS_BUFFER_UTIL_H
#define HS_BUFFER_UTIL_H
#include <stdlib.h>
#ifndef HTTPSERVER_IMPL
#include "common.h"
#endif
static inline void _hs_buffer_free(struct hsh_buffer_s *buffer,
int64_t *memused) {
if (buffer->buf) {
free(buffer->buf);
*memused -= buffer->capacity;
buffer->buf = NULL;
}
}
#endif

View File

@ -0,0 +1,127 @@
#ifndef HS_COMMON_H
#define HS_COMMON_H
// http session states
#define HTTP_SESSION_INIT 0
#define HTTP_SESSION_READ 1
#define HTTP_SESSION_WRITE 2
#define HTTP_SESSION_NOP 3
#define HTTP_REQUEST_TIMEOUT 20
#define HTTP_FLAG_SET(var, flag) var |= flag
#define HTTP_FLAG_CLEAR(var, flag) var &= ~flag
#define HTTP_FLAG_CHECK(var, flag) (var & flag)
#define HTTP_AUTOMATIC 0x8
#define HTTP_CHUNKED_RESPONSE 0x20
#define HTTP_KEEP_ALIVE 1
#define HTTP_CLOSE 0
#include <arpa/inet.h>
#include <sys/socket.h>
#ifdef KQUEUE
#include <sys/event.h>
#else
#include <sys/epoll.h>
#endif
#ifdef EPOLL
typedef void (*epoll_cb_t)(struct epoll_event *);
#endif
typedef struct http_ev_cb_s {
#ifdef KQUEUE
void (*handler)(struct kevent *ev);
#else
epoll_cb_t handler;
#endif
} ev_cb_t;
struct hsh_buffer_s {
char *buf;
int32_t capacity;
int32_t length;
int32_t index;
int32_t after_headers_index;
int8_t sequence_id;
};
enum hsh_token_e {
HSH_TOK_METHOD,
HSH_TOK_TARGET,
HSH_TOK_VERSION,
HSH_TOK_HEADER_KEY,
HSH_TOK_HEADER_VALUE,
HSH_TOK_HEADERS_DONE,
HSH_TOK_BODY,
HSH_TOK_NONE,
HSH_TOK_ERR
};
struct hsh_token_s {
enum hsh_token_e type;
uint8_t flags;
int len;
int index;
};
struct hsh_parser_s {
int64_t content_length;
int64_t content_remaining;
struct hsh_token_s token;
int16_t limit_count;
int16_t limit_max;
int8_t state;
int8_t flags;
int8_t sequence_id;
};
struct hs_token_array_s {
struct hsh_token_s *buf;
int capacity;
int size;
};
typedef struct http_request_s {
#ifdef KQUEUE
void (*handler)(struct kevent *ev);
#else
epoll_cb_t handler;
epoll_cb_t timer_handler;
int timerfd;
#endif
void (*chunk_cb)(struct http_request_s *);
void *data;
struct hsh_buffer_s buffer;
struct hsh_parser_s parser;
struct hs_token_array_s tokens;
int state;
int socket;
int timeout;
int64_t bytes_written;
struct http_server_s *server;
char flags;
} http_request_t;
typedef struct http_server_s {
#ifdef KQUEUE
void (*handler)(struct kevent *ev);
#else
epoll_cb_t handler;
epoll_cb_t timer_handler;
#endif
int64_t memused;
int socket;
int port;
int loop;
int timerfd;
socklen_t len;
void (*request_handler)(http_request_t *);
struct sockaddr_in addr;
void *data;
char date[32];
} http_server_t;
#endif

View File

@ -0,0 +1,119 @@
#include <assert.h>
#include <fcntl.h>
#include <stdlib.h>
#include <sys/socket.h>
#include <unistd.h>
#ifdef KQUEUE
#include <sys/event.h>
#else
#include <sys/epoll.h>
#include <sys/timerfd.h>
#include <time.h>
#endif
#ifndef HTTPSERVER_IMPL
#include "buffer_util.h"
#include "common.h"
#include "connection.h"
#endif
#ifdef KQUEUE
void _hs_delete_events(http_request_t *request) {
struct kevent ev_set;
EV_SET(&ev_set, request->socket, EVFILT_TIMER, EV_DELETE, 0, 0, request);
kevent(request->server->loop, &ev_set, 1, NULL, 0, NULL);
}
void _hs_add_timer_event(http_request_t *request, hs_io_cb_t unused) {
(void)unused;
struct kevent ev_set;
EV_SET(&ev_set, request->socket, EVFILT_TIMER, EV_ADD | EV_ENABLE, 0, 1000,
request);
kevent(request->server->loop, &ev_set, 1, NULL, 0, NULL);
}
#else
void _hs_delete_events(http_request_t *request) {
epoll_ctl(request->server->loop, EPOLL_CTL_DEL, request->socket, NULL);
epoll_ctl(request->server->loop, EPOLL_CTL_DEL, request->timerfd, NULL);
close(request->timerfd);
}
void _hs_add_timer_event(http_request_t *request, hs_io_cb_t timer_cb) {
request->timer_handler = timer_cb;
// Watch for read events
struct epoll_event ev;
ev.events = EPOLLIN | EPOLLET;
ev.data.ptr = request;
epoll_ctl(request->server->loop, EPOLL_CTL_ADD, request->socket, &ev);
// Add timer to timeout requests.
int tfd = timerfd_create(CLOCK_MONOTONIC, 0);
struct itimerspec ts = {};
ts.it_value.tv_sec = 1;
ts.it_interval.tv_sec = 1;
timerfd_settime(tfd, 0, &ts, NULL);
ev.events = EPOLLIN | EPOLLET;
ev.data.ptr = &request->timer_handler;
epoll_ctl(request->server->loop, EPOLL_CTL_ADD, tfd, &ev);
request->timerfd = tfd;
}
#endif
void hs_request_terminate_connection(http_request_t *request) {
_hs_delete_events(request);
close(request->socket);
_hs_buffer_free(&request->buffer, &request->server->memused);
free(request->tokens.buf);
request->tokens.buf = NULL;
free(request);
}
void _hs_token_array_init(struct hs_token_array_s *array, int capacity) {
array->buf =
(struct hsh_token_s *)malloc(sizeof(struct hsh_token_s) * capacity);
assert(array->buf != NULL);
array->size = 0;
array->capacity = capacity;
}
http_request_t *_hs_request_init(int sock, http_server_t *server,
hs_io_cb_t io_cb) {
http_request_t *request = (http_request_t *)calloc(1, sizeof(http_request_t));
assert(request != NULL);
request->socket = sock;
request->server = server;
request->handler = io_cb;
request->timeout = HTTP_REQUEST_TIMEOUT;
request->flags = HTTP_AUTOMATIC;
request->parser = (struct hsh_parser_s){};
request->buffer = (struct hsh_buffer_s){};
request->tokens.buf = NULL;
_hs_token_array_init(&request->tokens, 32);
return request;
}
http_request_t *hs_server_accept_connection(http_server_t *server,
hs_io_cb_t io_cb,
hs_io_cb_t epoll_timer_cb) {
http_request_t *request = NULL;
int sock = 0;
sock = accept(server->socket, (struct sockaddr *)&server->addr, &server->len);
if (sock > 0) {
int flags = fcntl(sock, F_GETFL, 0);
fcntl(sock, F_SETFL, flags | O_NONBLOCK);
request = _hs_request_init(sock, server, io_cb);
_hs_add_timer_event(request, epoll_timer_cb);
}
return request;
}

View File

@ -0,0 +1,47 @@
#ifndef HS_CONNECTION_H
#define HS_CONNECTION_H
// Forward declarations
struct http_request_s;
struct http_server_s;
#ifdef KQUEUE
struct kevent;
typedef void (*hs_io_cb_t)(struct kevent *ev);
#else
struct epoll_event;
typedef void (*hs_io_cb_t)(struct epoll_event *ev);
#endif
/* Closes the requests socket and frees its resources.
*
* Removes all event watchers from the request socket and frees any allocated
* buffers associated with the request struct.
*
* @param request The request to close
*/
void hs_request_terminate_connection(struct http_request_s *request);
/* Accepts connections on the server socket in a loop until it would block.
*
* When a connection is accepted a request struct is allocated and initialized
* and the request socket is set to non-blocking mode. Event watchers are set
* on the socket to call io_cb with a read/write ready event occurs. If the
* server has reached max_mem_usage the err_responder function is called to
* handle the issue.
*
* @param server The http server struct.
* @param io_cb The callback function to respond to events on the request socket
* @param epoll_timer_cb The callback function to respond to timer events for
* epoll. Can be NULL if not using epoll.
* @param err_responder The procedure to call when memory usage has reached the
* given limit. Typically this could respond with a 503 error and close the
* connection.
* @param max_mem_usage The limit at which err_responder should be called
* instead of regular operation.
*/
struct http_request_s *hs_server_accept_connection(struct http_server_s *server,
hs_io_cb_t io_cb,
hs_io_cb_t epoll_timer_cb);
#endif

View File

@ -0,0 +1,50 @@
// httpserver.h has been automatically generated from httpserver.m4 and the
// source files under /src
#ifndef HTTPSERVER_H
#define HTTPSERVER_H
#line 1 "api.h"
include(`api.h')
#line 1 "common.h"
include(`common.h')
#line 1 "buffer_util.h"
include(`buffer_util.h')
#line 1 "request_util.h"
include(`request_util.h')
#line 1 "parser.h"
include(`parser.h')
#line 1 "read_socket.h"
include(`read_socket.h')
#line 1 "respond.h"
include(`respond.h')
#line 1 "server.h"
include(`server.h')
#line 1 "write_socket.h"
include(`write_socket.h')
#line 1 "connection.h"
include(`connection.h')
#line 1 "io_events.h"
include(`io_events.h')
#ifdef HTTPSERVER_IMPL
#ifndef HTTPSERVER_IMPL_ONCE
#define HTTPSERVER_IMPL_ONCE
#line 1 "api.c"
include(`api.c')
#line 1 "request_util.c"
include(`request_util.c')
#line 1 "parser.c"
include(`parser.c')
#line 1 "read_socket.c"
include(`read_socket.c')
#line 1 "respond.c"
include(`respond.c')
#line 1 "server.c"
include(`server.c')
#line 1 "write_socket.c"
include(`write_socket.c')
#line 1 "connection.c"
include(`connection.c')
#line 1 "io_events.c"
include(`io_events.c')
#endif
#endif
#endif

View File

@ -0,0 +1,195 @@
#include <stdlib.h>
#ifdef KQUEUE
#include <sys/event.h>
#else
#include <stdint.h>
#include <sys/epoll.h>
#include <unistd.h>
#endif
#ifndef HTTPSERVER_IMPL
#include "buffer_util.h"
#include "common.h"
#include "connection.h"
#include "io_events.h"
#include "read_socket.h"
#include "respond.h"
#include "server.h"
#include "write_socket.h"
#endif
void _hs_read_socket_and_handle_return_code(http_request_t *request) {
struct hs_read_opts_s opts;
opts.initial_request_buf_capacity = HTTP_REQUEST_BUF_SIZE;
opts.max_request_buf_capacity = HTTP_MAX_REQUEST_BUF_SIZE;
opts.eof_rc = 0;
enum hs_read_rc_e rc = hs_read_request_and_exec_user_cb(request, opts);
switch (rc) {
case HS_READ_RC_PARSE_ERR:
hs_request_respond_error(request, 400, "Bad Request",
hs_request_begin_write);
break;
case HS_READ_RC_SOCKET_ERR:
hs_request_terminate_connection(request);
break;
case HS_READ_RC_SUCCESS:
break;
}
}
void hs_request_begin_read(http_request_t *request);
void _hs_write_socket_and_handle_return_code(http_request_t *request) {
enum hs_write_rc_e rc = hs_write_socket(request);
request->timeout = rc == HS_WRITE_RC_SUCCESS ? HTTP_KEEP_ALIVE_TIMEOUT
: HTTP_REQUEST_TIMEOUT;
if (rc != HS_WRITE_RC_CONTINUE)
_hs_buffer_free(&request->buffer, &request->server->memused);
switch (rc) {
case HS_WRITE_RC_SUCCESS_CLOSE:
case HS_WRITE_RC_SOCKET_ERR:
// Error or response complete, connection: close
hs_request_terminate_connection(request);
break;
case HS_WRITE_RC_SUCCESS:
// Response complete, keep-alive connection
hs_request_begin_read(request);
break;
case HS_WRITE_RC_SUCCESS_CHUNK:
// Finished writing chunk, request next
request->state = HTTP_SESSION_NOP;
request->chunk_cb(request);
break;
case HS_WRITE_RC_CONTINUE:
break;
}
}
void _hs_accept_and_begin_request_cycle(http_server_t *server,
hs_io_cb_t on_client_connection_cb,
hs_io_cb_t on_timer_event_cb) {
http_request_t *request = NULL;
while ((request = hs_server_accept_connection(server, on_client_connection_cb,
on_timer_event_cb))) {
if (server->memused > HTTP_MAX_TOTAL_EST_MEM_USAGE) {
hs_request_respond_error(request, 503, "Service Unavailable",
hs_request_begin_write);
} else {
hs_request_begin_read(request);
}
}
}
#ifdef KQUEUE
void _hs_on_kqueue_client_connection_event(struct kevent *ev) {
http_request_t *request = (http_request_t *)ev->udata;
if (ev->filter == EVFILT_TIMER) {
request->timeout -= 1;
if (request->timeout == 0)
hs_request_terminate_connection(request);
} else {
if (request->state == HTTP_SESSION_READ) {
_hs_read_socket_and_handle_return_code(request);
} else if (request->state == HTTP_SESSION_WRITE) {
_hs_write_socket_and_handle_return_code(request);
}
}
}
void hs_on_kqueue_server_event(struct kevent *ev) {
http_server_t *server = (http_server_t *)ev->udata;
if (ev->filter == EVFILT_TIMER) {
hs_generate_date_time(server->date);
} else {
_hs_accept_and_begin_request_cycle(
server, _hs_on_kqueue_client_connection_event, NULL);
}
}
#else
void _hs_on_epoll_client_connection_event(struct epoll_event *ev) {
http_request_t *request = (http_request_t *)ev->data.ptr;
if (request->state == HTTP_SESSION_READ) {
_hs_read_socket_and_handle_return_code(request);
} else if (request->state == HTTP_SESSION_WRITE) {
_hs_write_socket_and_handle_return_code(request);
}
}
void _hs_on_epoll_request_timer_event(struct epoll_event *ev) {
http_request_t *request =
(http_request_t *)((char *)ev->data.ptr - sizeof(epoll_cb_t));
uint64_t res;
int bytes = read(request->timerfd, &res, sizeof(res));
(void)bytes; // suppress warning
request->timeout -= 1;
if (request->timeout == 0)
hs_request_terminate_connection(request);
}
void hs_on_epoll_server_connection_event(struct epoll_event *ev) {
_hs_accept_and_begin_request_cycle((http_server_t *)ev->data.ptr,
_hs_on_epoll_client_connection_event,
_hs_on_epoll_request_timer_event);
}
void hs_on_epoll_server_timer_event(struct epoll_event *ev) {
http_server_t *server =
(http_server_t *)((char *)ev->data.ptr - sizeof(epoll_cb_t));
uint64_t res;
int bytes = read(server->timerfd, &res, sizeof(res));
(void)bytes; // suppress warning
hs_generate_date_time(server->date);
}
#endif
void _hs_add_write_event(http_request_t *request) {
#ifdef KQUEUE
struct kevent ev_set[2];
EV_SET(&ev_set[0], request->socket, EVFILT_WRITE, EV_ADD | EV_CLEAR, 0, 0,
request);
EV_SET(&ev_set[1], request->socket, EVFILT_READ, EV_DISABLE, 0, 0, request);
kevent(request->server->loop, ev_set, 2, NULL, 0, NULL);
#else
struct epoll_event ev;
ev.events = EPOLLOUT | EPOLLET;
ev.data.ptr = request;
epoll_ctl(request->server->loop, EPOLL_CTL_MOD, request->socket, &ev);
#endif
}
void hs_request_begin_write(http_request_t *request) {
request->state = HTTP_SESSION_WRITE;
_hs_add_write_event(request);
_hs_write_socket_and_handle_return_code(request);
}
void _hs_add_read_event(http_request_t *request) {
#ifdef KQUEUE
// No action needed for kqueue since it's read event stays active. Should
// it be disabled during write?
struct kevent ev_set;
EV_SET(&ev_set, request->socket, EVFILT_READ, EV_ADD | EV_ENABLE, 0, 0,
request);
kevent(request->server->loop, &ev_set, 1, NULL, 0, NULL);
#else
struct epoll_event ev;
ev.events = EPOLLIN | EPOLLET;
ev.data.ptr = request;
epoll_ctl(request->server->loop, EPOLL_CTL_MOD, request->socket, &ev);
#endif
}
void hs_request_begin_read(http_request_t *request) {
request->state = HTTP_SESSION_READ;
_hs_add_read_event(request);
_hs_read_socket_and_handle_return_code(request);
}

View File

@ -0,0 +1,28 @@
#ifndef HS_IO_EVENTS_H
#define HS_IO_EVENTS_H
#define HTTP_REQUEST_BUF_SIZE 1024
#define HTTP_MAX_REQUEST_BUF_SIZE 8388608 // 8mb
#define HTTP_MAX_TOTAL_EST_MEM_USAGE 4294967296 // 4gb
struct http_request_s;
void hs_request_begin_write(struct http_request_s *request);
void hs_request_begin_read(struct http_request_s *request);
#ifdef KQUEUE
struct kevent;
void hs_on_kqueue_server_event(struct kevent *ev);
#else
struct epoll_event;
void hs_on_epoll_server_connection_event(struct epoll_event *ev);
void hs_on_epoll_server_timer_event(struct epoll_event *ev);
#endif
#endif

View File

@ -0,0 +1,17 @@
#ifndef HTTP_PARSER_H
#define HTTP_PARSER_H
// HSH_TOK_HEADERS_DONE flags
#define HSH_TOK_FLAG_NO_BODY 0x1
#define HSH_TOK_FLAG_STREAMED_BODY 0x2
// HSH_TOK_BODY flags
#define HSH_TOK_FLAG_BODY_FINAL 0x1
#define HSH_TOK_FLAG_SMALL_BODY 0x2
struct hsh_token_s hsh_parser_exec(struct hsh_parser_s *parser,
struct hsh_buffer_s *buffer,
int max_buf_capacity);
void hsh_parser_init(struct hsh_parser_s *parser);
#endif

View File

@ -0,0 +1,261 @@
#include <string.h>
#include <stdlib.h>
#ifndef HTTPSERVER_IMPL
#include "common.h"
#include "parser.h"
#endif
#define HSH_P_FLAG_CHUNKED 0x1
#define HSH_P_FLAG_TOKEN_READY 0x2
#define HSH_P_FLAG_DONE 0x4
#define HSH_ENTER_TOKEN(tok_type, max_len) \
parser->token.type = tok_type; \
parser->token.index = p - buffer->buf; \
parser->token.flags = 0; \
parser->limit_count = 0; \
parser->limit_max = max_len;
%%{
machine hsh_http;
action method { HSH_ENTER_TOKEN(HSH_TOK_METHOD, 32) }
action target { HSH_ENTER_TOKEN(HSH_TOK_TARGET, 1024) }
action version { HSH_ENTER_TOKEN(HSH_TOK_VERSION, 16) }
action header_key { HSH_ENTER_TOKEN(HSH_TOK_HEADER_KEY, 256) }
action header_value { HSH_ENTER_TOKEN(HSH_TOK_HEADER_VALUE, 4096) }
action body {
parser->token.type = HSH_TOK_BODY;
parser->token.flags = 0;
parser->token.index = p - buffer->buf;
}
action emit_token {
parser->token.len = p - (buffer->buf + parser->token.index);
// hsh_token_array_push(&parser->tokens, parser->token);
HTTP_FLAG_SET(parser->flags, HSH_P_FLAG_TOKEN_READY);
fbreak;
}
action content_length_digit {
parser->content_length *= 10;
parser->content_length += fc - '0';
}
action transfer_encoding {
HTTP_FLAG_SET(parser->flags, HSH_P_FLAG_CHUNKED);
}
action reset_count {
parser->limit_count = 0;
parser->limit_max = 256;
}
action inc_count {
parser->limit_count++;
if (parser->limit_count > parser->limit_max) {
// parser->rc = (int8_t)HSH_PARSER_ERR;
fbreak;
}
}
action done_headers {
buffer->after_headers_index = p - buffer->buf + 1;
parser->content_remaining = parser->content_length;
parser->token = (struct hsh_token_s){ };
parser->token.type = HSH_TOK_HEADERS_DONE;
HTTP_FLAG_SET(parser->flags, HSH_P_FLAG_TOKEN_READY);
if (HTTP_FLAG_CHECK(parser->flags, HSH_P_FLAG_CHUNKED)) {
HTTP_FLAG_SET(parser->token.flags, HSH_TOK_FLAG_STREAMED_BODY);
fnext chunked_body;
fbreak;
} else if (parser->content_length == 0) {
HTTP_FLAG_SET(parser->token.flags, HSH_TOK_FLAG_NO_BODY);
fbreak;
// The body won't fit into the buffer at maximum capacity.
} else if (parser->content_length > max_buf_capacity - buffer->after_headers_index) {
HTTP_FLAG_SET(parser->token.flags, HSH_TOK_FLAG_STREAMED_BODY);
fnext large_body;
fbreak;
} else {
// Resize the buffer to hold the full body
if (parser->content_length + buffer->after_headers_index > buffer->capacity) {
buffer->buf = (char*)realloc(buffer->buf, parser->content_length + buffer->after_headers_index);
buffer->capacity = parser->content_length + buffer->after_headers_index;
}
fnext small_body;
fbreak;
}
}
action chunk_start {
parser->content_length = 0;
}
action chunk_size {
if (fc >= 'A' && fc <= 'F') {
parser->content_length *= 0x10;
parser->content_length += fc - 55;
} else if (fc >= 'a' && fc <= 'f') {
parser->content_length *= 0x10;
parser->content_length += fc - 87;
} else if (fc >= '0' && fc <= '9') {
parser->content_length *= 0x10;
parser->content_length += fc - '0';
}
}
action chunk_read {
char* last_body_byte = buffer->buf + parser->token.index + parser->content_length - 1;
if (pe >= last_body_byte) {
p = last_body_byte;
parser->token.len = parser->content_length;
HTTP_FLAG_SET(parser->flags, HSH_P_FLAG_TOKEN_READY);
fnext chunk_end;
fbreak;
// The current chunk is at the end of the buffer and the buffer cannot be expanded.
// Move the remaining contents of the buffer to just after the headers to free up
// capacity in the buffer.
} else if (p - buffer->buf + parser->content_length > max_buf_capacity) {
memcpy(buffer->buf + buffer->after_headers_index, p, pe - p);
buffer->length = buffer->after_headers_index + pe - p;
p = buffer->buf + buffer->after_headers_index;
parser->token.index = buffer->after_headers_index;
parser->sequence_id = buffer->sequence_id;
fhold;
fbreak;
}
}
action end_stream {
// write 0 byte body to tokens
parser->token.type = HSH_TOK_BODY;
parser->token.index = 0;
parser->token.len = 0;
parser->token.flags = HSH_TOK_FLAG_BODY_FINAL;
HTTP_FLAG_SET(parser->flags, HSH_P_FLAG_TOKEN_READY);
HTTP_FLAG_SET(parser->flags, HSH_P_FLAG_DONE);
fbreak;
}
action small_body_read {
parser->token.index = buffer->after_headers_index;
parser->token.len = parser->content_length;
HTTP_FLAG_SET(parser->token.flags, HSH_TOK_FLAG_SMALL_BODY);
char* last_body_byte = buffer->buf + parser->token.index + parser->content_length - 1;
if (pe >= last_body_byte) {
HTTP_FLAG_SET(parser->flags, HSH_P_FLAG_TOKEN_READY);
HTTP_FLAG_SET(parser->flags, HSH_P_FLAG_DONE);
}
p = pe;
fhold;
fbreak;
}
action large_body_read {
parser->token.index = buffer->after_headers_index;
char* last_body_byte = buffer->buf + buffer->after_headers_index + parser->content_remaining - 1;
if (pe >= last_body_byte) {
parser->token.flags = HSH_TOK_FLAG_BODY_FINAL;
parser->token.len = parser->content_remaining;
parser->content_remaining = 0;
HTTP_FLAG_SET(parser->flags, HSH_P_FLAG_TOKEN_READY);
HTTP_FLAG_SET(parser->flags, HSH_P_FLAG_DONE);
} else {
parser->token.len = pe - p;
parser->content_remaining -= parser->token.len;
HTTP_FLAG_SET(parser->flags, HSH_P_FLAG_TOKEN_READY);
p = buffer->buf + buffer->after_headers_index;
buffer->length = buffer->after_headers_index;
parser->sequence_id = buffer->sequence_id;
}
fhold;
fbreak;
}
action error {
// parser->rc = (int8_t)HSH_PARSER_ERR;
fbreak;
}
token = ^[()<>@,;:\\"/\[\]?={} \t]+ >reset_count @inc_count;
crlf = '\r\n';
lws = crlf [ \t]+;
ows = [ \t]+;
method = [a-zA-Z]+ @inc_count ' ';
target = [^ ]+ @inc_count ' ';
version = 'HTTP/1.' [01] '\r';
request_line = (
method >method @emit_token
target >target @emit_token
version >version @emit_token '\n'
);
generic_header = (
( token ':' ) >header_key @emit_token
ows ( ^[ \t]? @inc_count ^[\r\n]* @inc_count '\r' ) >header_value @emit_token '\n'
);
content_length = (
( 'Content-Length'i ':' ) >header_key @emit_token
ows digit+ $content_length_digit ows crlf
);
transfer_encoding = (
( 'Transfer-Encoding'i ':' ) >header_key @emit_token
ows 'chunked' @transfer_encoding ows crlf
);
header = content_length | transfer_encoding | generic_header;
headers = ( header+ >reset_count @inc_count crlf ) @done_headers;
chunk = (
( ^[0] xdigit* ) >chunk_start $chunk_size crlf any+ >body $chunk_read
);
zero_chunk = '0' crlf @end_stream;
chunk_end := ( crlf ( chunk | zero_chunk ) ) $!error;
chunked_body := ( chunk* zero_chunk ) $!error;
small_body := any+ >body $small_body_read;
large_body := any+ >body $large_body_read;
main :=
request_line $!error
headers $!error;
}%%
%% write data;
void hsh_parser_init(struct hsh_parser_s* parser) {
memset(parser, 0, sizeof(struct hsh_parser_s));
parser->state = hsh_http_start;
}
struct hsh_token_s hsh_parser_exec(struct hsh_parser_s* parser, struct hsh_buffer_s* buffer, int max_buf_capacity) {
struct hsh_token_s none = {};
none.type = HSH_TOK_NONE;
if (HTTP_FLAG_CHECK(parser->flags, HSH_P_FLAG_DONE) || parser->sequence_id == buffer->sequence_id) {
return none;
}
int cs = parser->state;
char* eof = NULL;
char *p = buffer->buf + buffer->index;
char *pe = buffer->buf + buffer->length;
%% write exec;
parser->state = cs;
buffer->index = p - buffer->buf;
if (HTTP_FLAG_CHECK(parser->flags, HSH_P_FLAG_TOKEN_READY)) {
HTTP_FLAG_CLEAR(parser->flags, HSH_P_FLAG_TOKEN_READY);
return parser->token;
} else {
parser->sequence_id = buffer->sequence_id;
return none;
}
}

View File

@ -0,0 +1,155 @@
#include <assert.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#ifndef HTTPSERVER_IMPL
#include "common.h"
#include "parser.h"
#include "read_socket.h"
#endif
void _hs_token_array_push(struct hs_token_array_s *array,
struct hsh_token_s a) {
if (array->size == array->capacity) {
array->capacity *= 2;
array->buf = (struct hsh_token_s *)realloc(
array->buf, array->capacity * sizeof(struct hsh_token_s));
assert(array->buf != NULL);
}
array->buf[array->size] = a;
array->size++;
}
void _hs_buffer_init(struct hsh_buffer_s *buffer, int initial_capacity,
int64_t *memused) {
*buffer = (struct hsh_buffer_s){0};
buffer->buf = (char *)calloc(1, initial_capacity);
*memused += initial_capacity;
assert(buffer->buf != NULL);
buffer->capacity = initial_capacity;
}
int _hs_read_into_buffer(struct hsh_buffer_s *buffer, int request_socket,
int64_t *server_memused,
int64_t max_request_buf_capacity) {
int bytes;
do {
bytes = read(request_socket, buffer->buf + buffer->length,
buffer->capacity - buffer->length);
if (bytes > 0)
buffer->length += bytes;
if (buffer->length == buffer->capacity &&
buffer->capacity != max_request_buf_capacity) {
*server_memused -= buffer->capacity;
buffer->capacity *= 2;
if (buffer->capacity > max_request_buf_capacity) {
buffer->capacity = max_request_buf_capacity;
}
*server_memused += buffer->capacity;
buffer->buf = (char *)realloc(buffer->buf, buffer->capacity);
assert(buffer->buf != NULL);
}
} while (bytes > 0 && buffer->capacity < max_request_buf_capacity);
buffer->sequence_id++;
return bytes;
}
int _hs_buffer_requires_read(struct hsh_buffer_s *buffer) {
return buffer->index >= buffer->length;
}
void _hs_exec_callback(http_request_t *request,
void (*cb)(struct http_request_s *)) {
request->state = HTTP_SESSION_NOP;
cb(request);
}
enum hs_read_rc_e
_hs_parse_buffer_and_exec_user_cb(http_request_t *request,
int max_request_buf_capacity) {
enum hs_read_rc_e rc = HS_READ_RC_SUCCESS;
do {
struct hsh_token_s token = hsh_parser_exec(
&request->parser, &request->buffer, max_request_buf_capacity);
switch (token.type) {
case HSH_TOK_HEADERS_DONE:
_hs_token_array_push(&request->tokens, token);
if (HTTP_FLAG_CHECK(token.flags, HSH_TOK_FLAG_STREAMED_BODY) ||
HTTP_FLAG_CHECK(token.flags, HSH_TOK_FLAG_NO_BODY)) {
HTTP_FLAG_SET(request->flags, HTTP_FLG_STREAMED);
_hs_exec_callback(request, request->server->request_handler);
return rc;
}
break;
case HSH_TOK_BODY:
_hs_token_array_push(&request->tokens, token);
if (HTTP_FLAG_CHECK(token.flags, HSH_TOK_FLAG_SMALL_BODY)) {
_hs_exec_callback(request, request->server->request_handler);
} else {
if (HTTP_FLAG_CHECK(token.flags, HSH_TOK_FLAG_BODY_FINAL) &&
token.len > 0) {
_hs_exec_callback(request, request->chunk_cb);
// A zero length body is used to indicate to the user code that the
// body has finished streaming. This is natural when dealing with
// chunked request bodies but requires us to inject a zero length
// body for non-chunked requests.
struct hsh_token_s token = {};
memset(&token, 0, sizeof(struct hsh_token_s));
token.type = HSH_TOK_BODY;
_hs_token_array_push(&request->tokens, token);
_hs_exec_callback(request, request->chunk_cb);
} else {
_hs_exec_callback(request, request->chunk_cb);
}
}
return rc;
case HSH_TOK_ERR:
return HS_READ_RC_PARSE_ERR;
case HSH_TOK_NONE:
return rc;
default:
_hs_token_array_push(&request->tokens, token);
break;
}
} while (1);
}
// Reads the request socket if required and parses HTTP in a non-blocking
// manner.
//
// It should be called when a new connection is established and when a read
// ready event occurs for the request socket. It parses the HTTP request and
// fills the tokens array of the request struct. It will also invoke the
// request_hander callback and the chunk_cb callback in the appropriate
// scenarios.
enum hs_read_rc_e hs_read_request_and_exec_user_cb(http_request_t *request,
struct hs_read_opts_s opts) {
request->state = HTTP_SESSION_READ;
request->timeout = HTTP_REQUEST_TIMEOUT;
if (request->buffer.buf == NULL) {
_hs_buffer_init(&request->buffer, opts.initial_request_buf_capacity,
&request->server->memused);
hsh_parser_init(&request->parser);
}
if (_hs_buffer_requires_read(&request->buffer)) {
int bytes = _hs_read_into_buffer(&request->buffer, request->socket,
&request->server->memused,
opts.max_request_buf_capacity);
if (bytes == opts.eof_rc) {
return HS_READ_RC_SOCKET_ERR;
}
}
return _hs_parse_buffer_and_exec_user_cb(request,
opts.max_request_buf_capacity);
}

View File

@ -0,0 +1,37 @@
#ifndef HS_READ_SOCKET_H
#define HS_READ_SOCKET_H
#define HTTP_FLG_STREAMED 0x1
#include <stdint.h>
struct http_request_s;
// Response code for hs_read_socket
enum hs_read_rc_e {
// Execution was successful
HS_READ_RC_SUCCESS,
// There was an error parsing the HTTP request
HS_READ_RC_PARSE_ERR,
// There was an error reading the socket
HS_READ_RC_SOCKET_ERR
};
// Holds configuration options for the hs_read_socket procedure.
struct hs_read_opts_s {
// Restricts the request buffer from ever growing larger than this size
int64_t max_request_buf_capacity;
// The value to be compared to the return of the read call to determine if
// the connection has been closed. Should generally be 0 in normal operation
// using sockets but can be useful to change if you want to use files instead
// of sockets for testing.
int eof_rc;
// The initial capacity that is allocated for the request buffer
int initial_request_buf_capacity;
};
enum hs_read_rc_e
hs_read_request_and_exec_user_cb(struct http_request_s *request,
struct hs_read_opts_s opts);
#endif

View File

@ -0,0 +1,112 @@
#include <stdlib.h>
#include <string.h>
#ifndef HTTPSERVER_IMPL
#include "common.h"
#include "request_util.h"
#endif
int _hs_case_insensitive_cmp(char const *a, char const *b, int len) {
for (int i = 0; i < len; i++) {
char c1 = a[i] >= 'A' && a[i] <= 'Z' ? a[i] + 32 : a[i];
char c2 = b[i] >= 'A' && b[i] <= 'Z' ? b[i] + 32 : b[i];
if (c1 != c2)
return 0;
}
return 1;
}
http_string_t hs_get_token_string(http_request_t *request,
enum hsh_token_e token_type) {
http_string_t str = {0, 0};
if (request->tokens.buf == NULL)
return str;
for (int i = 0; i < request->tokens.size; i++) {
struct hsh_token_s token = request->tokens.buf[i];
if (token.type == token_type) {
str.buf = &request->buffer.buf[token.index];
str.len = token.len;
return str;
}
}
return str;
}
http_string_t hs_request_header(http_request_t *request, char const *key) {
int len = strlen(key);
for (int i = 0; i < request->tokens.size; i++) {
struct hsh_token_s token = request->tokens.buf[i];
if (token.type == HSH_TOK_HEADER_KEY && token.len == len) {
if (_hs_case_insensitive_cmp(&request->buffer.buf[token.index], key,
len)) {
token = request->tokens.buf[i + 1];
return (http_string_t){.buf = &request->buffer.buf[token.index],
.len = token.len};
}
}
}
return (http_string_t){};
}
void hs_request_detect_keep_alive_flag(http_request_t *request) {
http_string_t str = hs_get_token_string(request, HSH_TOK_VERSION);
if (str.buf == NULL)
return;
int version = str.buf[str.len - 1] == '1';
str = hs_request_header(request, "Connection");
if ((str.len == 5 && _hs_case_insensitive_cmp(str.buf, "close", 5)) ||
(str.len == 0 && version == HTTP_1_0)) {
HTTP_FLAG_CLEAR(request->flags, HTTP_KEEP_ALIVE);
} else {
HTTP_FLAG_SET(request->flags, HTTP_KEEP_ALIVE);
}
}
int _hs_get_header_key_val(http_request_t *request, http_string_t *key,
http_string_t *val, int iter) {
struct hsh_token_s token = request->tokens.buf[iter];
if (request->tokens.buf[iter].type == HSH_TOK_HEADERS_DONE)
return 0;
*key = (http_string_t){.buf = &request->buffer.buf[token.index],
.len = token.len};
token = request->tokens.buf[iter + 1];
*val = (http_string_t){.buf = &request->buffer.buf[token.index],
.len = token.len};
return 1;
}
int hs_request_iterate_headers(http_request_t *request, http_string_t *key,
http_string_t *val, int *iter) {
if (*iter == 0) {
for (; *iter < request->tokens.size; (*iter)++) {
struct hsh_token_s token = request->tokens.buf[*iter];
if (token.type == HSH_TOK_HEADER_KEY) {
int more = _hs_get_header_key_val(request, key, val, *iter);
(*iter)++;
return more;
}
}
return 0;
} else {
(*iter)++;
int more = _hs_get_header_key_val(request, key, val, *iter);
(*iter)++;
return more;
}
}
void hs_request_set_keep_alive_flag(http_request_t *request, int directive) {
if (directive == HTTP_KEEP_ALIVE) {
HTTP_FLAG_CLEAR(request->flags, HTTP_AUTOMATIC);
HTTP_FLAG_SET(request->flags, HTTP_KEEP_ALIVE);
} else if (directive == HTTP_CLOSE) {
HTTP_FLAG_CLEAR(request->flags, HTTP_AUTOMATIC);
HTTP_FLAG_CLEAR(request->flags, HTTP_KEEP_ALIVE);
}
}
http_string_t hs_request_chunk(struct http_request_s *request) {
struct hsh_token_s token = request->tokens.buf[request->tokens.size - 1];
return (http_string_t){.buf = &request->buffer.buf[token.index],
.len = token.len};
}

View File

@ -0,0 +1,28 @@
#ifndef HS_REQUEST_UTIL_H
#define HS_REQUEST_UTIL_H
#ifndef HTTPSERVER_IMPL
#include "common.h"
#endif
// http version indicators
#define HTTP_1_0 0
#define HTTP_1_1 1
struct http_string_s {
char const *buf;
int len;
};
typedef struct http_string_s http_string_t;
http_string_t hs_get_token_string(http_request_t *request,
enum hsh_token_e token_type);
http_string_t hs_request_header(http_request_t *request, char const *key);
void hs_request_detect_keep_alive_flag(http_request_t *request);
int hs_request_iterate_headers(http_request_t *request, http_string_t *key,
http_string_t *val, int *iter);
void hs_request_set_keep_alive_flag(http_request_t *request, int directive);
http_string_t hs_request_chunk(struct http_request_s *request);
#endif

View File

@ -0,0 +1,256 @@
#include <assert.h>
#include <stdarg.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#ifndef HTTPSERVER_IMPL
#include "buffer_util.h"
#include "common.h"
#include "request_util.h"
#include "respond.h"
#endif
char const *hs_status_text[] = {
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
"", "", "", "", "",
// 100s
"Continue", "Switching Protocols", "", "", "", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "", "", "", "", "",
// 200s
"OK", "Created", "Accepted", "Non-Authoritative Information", "No Content",
"Reset Content", "Partial Content", "", "", "",
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "", "", "", "", "", "", "",
// 300s
"Multiple Choices", "Moved Permanently", "Found", "See Other",
"Not Modified", "Use Proxy", "", "Temporary Redirect", "", "",
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "", "", "", "", "", "", "",
// 400s
"Bad Request", "Unauthorized", "Payment Required", "Forbidden", "Not Found",
"Method Not Allowed", "Not Acceptable", "Proxy Authentication Required",
"Request Timeout", "Conflict",
"Gone", "Length Required", "", "Payload Too Large", "", "", "", "", "", "",
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
"", "", "", "",
// 500s
"Internal Server Error", "Not Implemented", "Bad Gateway",
"Service Unavailable", "Gateway Timeout", "", "", "", "", "",
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "", "", "", "", "", "", ""};
typedef struct {
char *buf;
int capacity;
int size;
int64_t *memused;
} grwprintf_t;
void _grwprintf_init(grwprintf_t *ctx, int capacity, int64_t *memused) {
ctx->memused = memused;
ctx->size = 0;
ctx->buf = (char *)malloc(capacity);
*ctx->memused += capacity;
assert(ctx->buf != NULL);
ctx->capacity = capacity;
}
void _grwmemcpy(grwprintf_t *ctx, char const *src, int size) {
if (ctx->size + size > ctx->capacity) {
*ctx->memused -= ctx->capacity;
ctx->capacity = ctx->size + size;
*ctx->memused += ctx->capacity;
ctx->buf = (char *)realloc(ctx->buf, ctx->capacity);
assert(ctx->buf != NULL);
}
memcpy(ctx->buf + ctx->size, src, size);
ctx->size += size;
}
void _grwprintf(grwprintf_t *ctx, char const *fmt, ...) {
va_list args;
va_start(args, fmt);
int bytes =
vsnprintf(ctx->buf + ctx->size, ctx->capacity - ctx->size, fmt, args);
if (bytes + ctx->size > ctx->capacity) {
*ctx->memused -= ctx->capacity;
while (bytes + ctx->size > ctx->capacity)
ctx->capacity *= 2;
*ctx->memused += ctx->capacity;
ctx->buf = (char *)realloc(ctx->buf, ctx->capacity);
assert(ctx->buf != NULL);
bytes +=
vsnprintf(ctx->buf + ctx->size, ctx->capacity - ctx->size, fmt, args);
}
ctx->size += bytes;
va_end(args);
}
void _http_serialize_headers_list(http_response_t *response,
grwprintf_t *printctx) {
http_header_t *header = response->headers;
while (header) {
_grwprintf(printctx, "%s: %s\r\n", header->key, header->value);
header = header->next;
}
_grwprintf(printctx, "\r\n");
}
void _http_serialize_headers(http_request_t *request, http_response_t *response,
grwprintf_t *printctx) {
if (HTTP_FLAG_CHECK(request->flags, HTTP_AUTOMATIC)) {
hs_request_detect_keep_alive_flag(request);
}
if (HTTP_FLAG_CHECK(request->flags, HTTP_KEEP_ALIVE)) {
hs_response_set_header(response, "Connection", "keep-alive");
} else {
hs_response_set_header(response, "Connection", "close");
}
_grwprintf(printctx, "HTTP/1.1 %d %s\r\nDate: %s\r\n", response->status,
hs_status_text[response->status], request->server->date);
if (!HTTP_FLAG_CHECK(request->flags, HTTP_CHUNKED_RESPONSE)) {
_grwprintf(printctx, "Content-Length: %d\r\n", response->content_length);
}
_http_serialize_headers_list(response, printctx);
}
void _http_perform_response(http_request_t *request, http_response_t *response,
grwprintf_t *printctx, hs_req_fn_t http_write) {
http_header_t *header = response->headers;
while (header) {
http_header_t *tmp = header;
header = tmp->next;
free(tmp);
}
_hs_buffer_free(&request->buffer, &request->server->memused);
free(response);
request->buffer.buf = printctx->buf;
request->buffer.length = printctx->size;
request->buffer.capacity = printctx->capacity;
request->bytes_written = 0;
request->state = HTTP_SESSION_WRITE;
http_write(request);
}
// See api.h http_response_header
void hs_response_set_header(http_response_t *response, char const *key,
char const *value) {
http_header_t *header = (http_header_t *)malloc(sizeof(http_header_t));
assert(header != NULL);
header->key = key;
header->value = value;
http_header_t *prev = response->headers;
header->next = prev;
response->headers = header;
}
// Serializes the response into the request buffer and calls http_write.
// See api.h http_respond for more details
void hs_request_respond(http_request_t *request, http_response_t *response,
hs_req_fn_t http_write) {
grwprintf_t printctx;
_grwprintf_init(&printctx, HTTP_RESPONSE_BUF_SIZE, &request->server->memused);
_http_serialize_headers(request, response, &printctx);
if (response->body) {
_grwmemcpy(&printctx, response->body, response->content_length);
}
_http_perform_response(request, response, &printctx, http_write);
}
// Serializes a chunk into the request buffer and calls http_write.
// See api.h http_respond_chunk for more details.
void hs_request_respond_chunk(http_request_t *request,
http_response_t *response, hs_req_fn_t cb,
hs_req_fn_t http_write) {
grwprintf_t printctx;
_grwprintf_init(&printctx, HTTP_RESPONSE_BUF_SIZE, &request->server->memused);
if (!HTTP_FLAG_CHECK(request->flags, HTTP_CHUNKED_RESPONSE)) {
HTTP_FLAG_SET(request->flags, HTTP_CHUNKED_RESPONSE);
hs_response_set_header(response, "Transfer-Encoding", "chunked");
_http_serialize_headers(request, response, &printctx);
}
request->chunk_cb = cb;
_grwprintf(&printctx, "%X\r\n", response->content_length);
_grwmemcpy(&printctx, response->body, response->content_length);
_grwprintf(&printctx, "\r\n");
_http_perform_response(request, response, &printctx, http_write);
}
// Serializes the zero sized final chunk into the request buffer and calls
// http_write. See api.h http_respond_chunk_end for more details.
void hs_request_respond_chunk_end(http_request_t *request,
http_response_t *response,
hs_req_fn_t http_write) {
grwprintf_t printctx;
_grwprintf_init(&printctx, HTTP_RESPONSE_BUF_SIZE, &request->server->memused);
_grwprintf(&printctx, "0\r\n");
_http_serialize_headers_list(response, &printctx);
_grwprintf(&printctx, "\r\n");
HTTP_FLAG_CLEAR(request->flags, HTTP_CHUNKED_RESPONSE);
_http_perform_response(request, response, &printctx, http_write);
}
// See api.h http_response_status
void hs_response_set_status(http_response_t *response, int status) {
response->status = status > 599 || status < 100 ? 500 : status;
}
// See api.h http_response_body
void hs_response_set_body(http_response_t *response, char const *body,
int length) {
response->body = body;
response->content_length = length;
}
// See api.h http_response_init
http_response_t *hs_response_init() {
http_response_t *response =
(http_response_t *)calloc(1, sizeof(http_response_t));
assert(response != NULL);
response->status = 200;
return response;
}
// Simple less flexible interface for responses, used for errors.
void hs_request_respond_error(http_request_t *request, int code,
char const *message, hs_req_fn_t http_write) {
struct http_response_s *response = hs_response_init();
hs_response_set_status(response, code);
hs_response_set_header(response, "Content-Type", "text/plain");
hs_response_set_body(response, message, strlen(message));
hs_request_respond(request, response, http_write);
http_write(request);
}

View File

@ -0,0 +1,51 @@
#ifndef HS_RESPOND_H
#define HS_RESPOND_H
#define HTTP_RESPONSE_BUF_SIZE 1024
struct http_request_s;
typedef void (*hs_req_fn_t)(struct http_request_s *);
// Represents a single header of an HTTP response.
typedef struct http_header_s {
// The key of the header eg: Content-Type
char const *key;
// The value of the header eg: application/json
char const *value;
// Pointer to the next header in the linked list.
struct http_header_s *next;
} http_header_t;
// Represents the response of an HTTP request before it is serialized on the
// wire.
typedef struct http_response_s {
// Head of the linked list of response headers
http_header_t *headers;
// The complete body of the response or the chunk if generating a chunked
// response.
char const *body;
// The length of the body or chunk.
int content_length;
// The HTTP status code for the response.
int status;
} http_response_t;
http_response_t *hs_response_init();
void hs_response_set_header(http_response_t *response, char const *key,
char const *value);
void hs_response_set_status(http_response_t *response, int status);
void hs_response_set_body(http_response_t *response, char const *body,
int length);
void hs_request_respond(struct http_request_s *request,
http_response_t *response, hs_req_fn_t http_write);
void hs_request_respond_chunk(struct http_request_s *request,
http_response_t *response, hs_req_fn_t cb,
hs_req_fn_t http_write);
void hs_request_respond_chunk_end(struct http_request_s *request,
http_response_t *response,
hs_req_fn_t http_write);
void hs_request_respond_error(struct http_request_s *request, int code,
char const *message, hs_req_fn_t http_write);
#endif

View File

@ -0,0 +1,165 @@
#include <arpa/inet.h>
#include <assert.h>
#include <fcntl.h>
#include <netinet/in.h>
#include <signal.h>
#include <stdlib.h>
#include <sys/socket.h>
#include <time.h>
#ifdef EPOLL
#include <sys/epoll.h>
#include <sys/timerfd.h>
#else
#include <sys/event.h>
#endif
#ifndef HTTPSERVER_IMPL
#include "common.h"
#include "server.h"
#endif
void _hs_bind_localhost(int s, struct sockaddr_in *addr, const char *ipaddr,
int port) {
addr->sin_family = AF_INET;
if (ipaddr == NULL) {
addr->sin_addr.s_addr = INADDR_ANY;
} else {
addr->sin_addr.s_addr = inet_addr(ipaddr);
}
addr->sin_port = htons(port);
int rc = bind(s, (struct sockaddr *)addr, sizeof(struct sockaddr_in));
if (rc < 0) {
exit(1);
}
}
#ifdef KQUEUE
void _hs_add_server_sock_events(http_server_t *serv) {
struct kevent ev_set;
EV_SET(&ev_set, serv->socket, EVFILT_READ, EV_ADD | EV_CLEAR, 0, 0, serv);
kevent(serv->loop, &ev_set, 1, NULL, 0, NULL);
}
void _hs_server_init_events(http_server_t *serv, hs_evt_cb_t unused) {
(void)unused;
serv->loop = kqueue();
struct kevent ev_set;
EV_SET(&ev_set, 1, EVFILT_TIMER, EV_ADD | EV_ENABLE, NOTE_SECONDS, 1, serv);
kevent(serv->loop, &ev_set, 1, NULL, 0, NULL);
}
int hs_server_run_event_loop(http_server_t *serv, const char *ipaddr) {
hs_server_listen_on_addr(serv, ipaddr);
struct kevent ev_list[1];
while (1) {
int nev = kevent(serv->loop, NULL, 0, ev_list, 1, NULL);
for (int i = 0; i < nev; i++) {
ev_cb_t *ev_cb = (ev_cb_t *)ev_list[i].udata;
ev_cb->handler(&ev_list[i]);
}
}
return 0;
}
int hs_server_poll_events(http_server_t *serv) {
struct kevent ev;
struct timespec ts = {0, 0};
int nev = kevent(serv->loop, NULL, 0, &ev, 1, &ts);
if (nev <= 0)
return nev;
ev_cb_t *ev_cb = (ev_cb_t *)ev.udata;
ev_cb->handler(&ev);
return nev;
}
#else
void _hs_server_init_events(http_server_t *serv, hs_evt_cb_t timer_cb) {
serv->loop = epoll_create1(0);
serv->timer_handler = timer_cb;
int tfd = timerfd_create(CLOCK_MONOTONIC, 0);
struct itimerspec ts = {};
ts.it_value.tv_sec = 1;
ts.it_interval.tv_sec = 1;
timerfd_settime(tfd, 0, &ts, NULL);
struct epoll_event ev;
ev.events = EPOLLIN | EPOLLET;
ev.data.ptr = &serv->timer_handler;
epoll_ctl(serv->loop, EPOLL_CTL_ADD, tfd, &ev);
serv->timerfd = tfd;
}
void _hs_add_server_sock_events(http_server_t *serv) {
struct epoll_event ev;
ev.events = EPOLLIN | EPOLLET;
ev.data.ptr = serv;
epoll_ctl(serv->loop, EPOLL_CTL_ADD, serv->socket, &ev);
}
int hs_server_run_event_loop(http_server_t *serv, const char *ipaddr) {
hs_server_listen_on_addr(serv, ipaddr);
struct epoll_event ev_list[1];
while (1) {
int nev = epoll_wait(serv->loop, ev_list, 1, -1);
for (int i = 0; i < nev; i++) {
ev_cb_t *ev_cb = (ev_cb_t *)ev_list[i].data.ptr;
ev_cb->handler(&ev_list[i]);
}
}
return 0;
}
int hs_server_poll_events(http_server_t *serv) {
struct epoll_event ev;
int nev = epoll_wait(serv->loop, &ev, 1, 0);
if (nev <= 0)
return nev;
ev_cb_t *ev_cb = (ev_cb_t *)ev.data.ptr;
ev_cb->handler(&ev);
return nev;
}
#endif
void hs_server_listen_on_addr(http_server_t *serv, const char *ipaddr) {
// Ignore SIGPIPE. We handle these errors at the call site.
signal(SIGPIPE, SIG_IGN);
serv->socket = socket(AF_INET, SOCK_STREAM, 0);
int flag = 1;
setsockopt(serv->socket, SOL_SOCKET, SO_REUSEPORT, &flag, sizeof(flag));
_hs_bind_localhost(serv->socket, &serv->addr, ipaddr, serv->port);
serv->len = sizeof(serv->addr);
int flags = fcntl(serv->socket, F_GETFL, 0);
fcntl(serv->socket, F_SETFL, flags | O_NONBLOCK);
listen(serv->socket, 128);
_hs_add_server_sock_events(serv);
}
void hs_generate_date_time(char *datetime) {
time_t rawtime;
struct tm *timeinfo;
time(&rawtime);
timeinfo = gmtime(&rawtime);
strftime(datetime, 32, "%a, %d %b %Y %T GMT", timeinfo);
}
http_server_t *hs_server_init(int port, void (*handler)(http_request_t *),
hs_evt_cb_t accept_cb,
hs_evt_cb_t epoll_timer_cb) {
http_server_t *serv = (http_server_t *)malloc(sizeof(http_server_t));
assert(serv != NULL);
serv->port = port;
serv->memused = 0;
serv->handler = accept_cb;
_hs_server_init_events(serv, epoll_timer_cb);
hs_generate_date_time(serv->date);
serv->request_handler = handler;
return serv;
}

View File

@ -0,0 +1,30 @@
#ifndef HS_SERVER_H
#define HS_SERVER_H
#ifdef KQUEUE
struct kevent;
typedef void (*hs_evt_cb_t)(struct kevent *ev);
#else
struct epoll_event;
typedef void (*hs_evt_cb_t)(struct epoll_event *ev);
#endif
struct http_request_s;
struct http_server_s;
void hs_server_listen_on_addr(struct http_server_s *serv, const char *ipaddr);
int hs_server_run_event_loop(struct http_server_s *serv, const char *ipaddr);
void hs_generate_date_time(char *datetime);
struct http_server_s *hs_server_init(int port,
void (*handler)(struct http_request_s *),
hs_evt_cb_t accept_cb,
hs_evt_cb_t timer_cb);
int hs_server_poll_events(struct http_server_s *serv);
#endif

View File

@ -0,0 +1,50 @@
#include <errno.h>
#include <unistd.h>
#ifndef HTTPSERVER_IMPL
#include "common.h"
#include "write_socket.h"
#endif
#ifdef DEBUG
#define write hs_test_write
ssize_t hs_test_write(int fd, char const *data, size_t size);
#endif
// Writes response bytes from the buffer out to the socket.
//
// Runs when we get a socket ready to write event or when initiating an HTTP
// response and writing to the socket for the first time. If the response is
// chunked the chunk_cb callback will be invoked signalling to the user code
// that another chunk is ready to be written.
enum hs_write_rc_e hs_write_socket(http_request_t *request) {
int bytes =
write(request->socket, request->buffer.buf + request->bytes_written,
request->buffer.length - request->bytes_written);
if (bytes > 0)
request->bytes_written += bytes;
enum hs_write_rc_e rc = HS_WRITE_RC_SUCCESS;
if (errno == EPIPE) {
rc = HS_WRITE_RC_SOCKET_ERR;
} else {
if (request->bytes_written != request->buffer.length) {
// All bytes of the body were not written and we need to wait until the
// socket is writable again to complete the write
rc = HS_WRITE_RC_CONTINUE;
} else if (HTTP_FLAG_CHECK(request->flags, HTTP_CHUNKED_RESPONSE)) {
// All bytes of the chunk were written and we need to get the next chunk
// from the application.
rc = HS_WRITE_RC_SUCCESS_CHUNK;
} else {
if (HTTP_FLAG_CHECK(request->flags, HTTP_KEEP_ALIVE)) {
rc = HS_WRITE_RC_SUCCESS;
} else {
rc = HS_WRITE_RC_SUCCESS_CLOSE;
}
}
}
return rc;
}

View File

@ -0,0 +1,24 @@
#ifndef HS_WRITE_SOCKET_H
#define HS_WRITE_SOCKET_H
#define HTTP_KEEP_ALIVE_TIMEOUT 120
struct http_request_s;
// Response code for hs_write_socket
enum hs_write_rc_e {
// Successful and has written the full response
HS_WRITE_RC_SUCCESS,
// Successful and has written the full chunk
HS_WRITE_RC_SUCCESS_CHUNK,
// Successful, has written the full response and the socket should be closed
HS_WRITE_RC_SUCCESS_CLOSE,
// Successful but has not written the full response, wait for write ready
HS_WRITE_RC_CONTINUE,
// Error writing to the socket
HS_WRITE_RC_SOCKET_ERR
};
enum hs_write_rc_e hs_write_socket(struct http_request_s *request);
#endif

View File

@ -0,0 +1,34 @@
@startuml
[*] --> Read : Connection accepted, hs_read
Read : Read the socket into the buffer
Read : until EWOULDBLOCK
Read --> ParseHttp : _hs_parse
WaitRead --> Read : hs_connection_io_cb
WaitRead : Wait for epoll/kqueue read ready
ParseHttp : Parse the new buffered input
ParseHttp --> RequestCallback : Request ready
ParseHttp --> ChunkCallback : Chunk ready
ParseHttp --> WaitRead : Chunk/Request not ready
RequestCallback : Execute server wide request handler
RequestCallback --> Write : http_respond(_chunk)
RequestCallback --> Read : http_request_read_chunk
ChunkCallback : Execute request chunk handler
ChunkCallback --> Read : http_request_read_chunk
ChunkCallback --> Write : http_respond(_chunk)
Write : Write out the buffer to the socket
Write : until done or EWOULDBLOCK
Write --> [*] : Connection: Close
Write --> WaitWrite : EWOULDBLOCK
Write --> Read : Connection Keep-Alive
Write --> ChunkCallback : Transfer-Encoding: chunked
WaitWrite : Wait for epoll/kqueue write ready
WaitWrite --> Write : hs_connection_io_cb
@enduml

View File

@ -0,0 +1,174 @@
/* Copyright (c) 2011-2021, Scott Tsai
*
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
#ifndef DEBUG_BREAK_H
#define DEBUG_BREAK_H
#ifdef _MSC_VER
#define debug_break __debugbreak
#else
#ifdef __cplusplus
extern "C" {
#endif
#define DEBUG_BREAK_USE_TRAP_INSTRUCTION 1
#define DEBUG_BREAK_USE_BULTIN_TRAP 2
#define DEBUG_BREAK_USE_SIGTRAP 3
#if defined(__i386__) || defined(__x86_64__)
#define DEBUG_BREAK_IMPL DEBUG_BREAK_USE_TRAP_INSTRUCTION
__inline__ static void trap_instruction(void)
{
__asm__ volatile("int $0x03");
}
#elif defined(__thumb__)
#define DEBUG_BREAK_IMPL DEBUG_BREAK_USE_TRAP_INSTRUCTION
/* FIXME: handle __THUMB_INTERWORK__ */
__attribute__((always_inline))
__inline__ static void trap_instruction(void)
{
/* See 'arm-linux-tdep.c' in GDB source.
* Both instruction sequences below work. */
#if 1
/* 'eabi_linux_thumb_le_breakpoint' */
__asm__ volatile(".inst 0xde01");
#else
/* 'eabi_linux_thumb2_le_breakpoint' */
__asm__ volatile(".inst.w 0xf7f0a000");
#endif
/* Known problem:
* After a breakpoint hit, can't 'stepi', 'step', or 'continue' in GDB.
* 'step' would keep getting stuck on the same instruction.
*
* Workaround: use the new GDB commands 'debugbreak-step' and
* 'debugbreak-continue' that become available
* after you source the script from GDB:
*
* $ gdb -x debugbreak-gdb.py <... USUAL ARGUMENTS ...>
*
* 'debugbreak-step' would jump over the breakpoint instruction with
* roughly equivalent of:
* (gdb) set $instruction_len = 2
* (gdb) tbreak *($pc + $instruction_len)
* (gdb) jump *($pc + $instruction_len)
*/
}
#elif defined(__arm__) && !defined(__thumb__)
#define DEBUG_BREAK_IMPL DEBUG_BREAK_USE_TRAP_INSTRUCTION
__attribute__((always_inline))
__inline__ static void trap_instruction(void)
{
/* See 'arm-linux-tdep.c' in GDB source,
* 'eabi_linux_arm_le_breakpoint' */
__asm__ volatile(".inst 0xe7f001f0");
/* Known problem:
* Same problem and workaround as Thumb mode */
}
#elif defined(__aarch64__) && defined(__APPLE__)
#define DEBUG_BREAK_IMPL DEBUG_BREAK_USE_BULTIN_DEBUGTRAP
#elif defined(__aarch64__)
#define DEBUG_BREAK_IMPL DEBUG_BREAK_USE_TRAP_INSTRUCTION
__attribute__((always_inline))
__inline__ static void trap_instruction(void)
{
/* See 'aarch64-tdep.c' in GDB source,
* 'aarch64_default_breakpoint' */
__asm__ volatile(".inst 0xd4200000");
}
#elif defined(__powerpc__)
/* PPC 32 or 64-bit, big or little endian */
#define DEBUG_BREAK_IMPL DEBUG_BREAK_USE_TRAP_INSTRUCTION
__attribute__((always_inline))
__inline__ static void trap_instruction(void)
{
/* See 'rs6000-tdep.c' in GDB source,
* 'rs6000_breakpoint' */
__asm__ volatile(".4byte 0x7d821008");
/* Known problem:
* After a breakpoint hit, can't 'stepi', 'step', or 'continue' in GDB.
* 'step' stuck on the same instruction ("twge r2,r2").
*
* The workaround is the same as ARM Thumb mode: use debugbreak-gdb.py
* or manually jump over the instruction. */
}
#elif defined(__riscv)
/* RISC-V 32 or 64-bit, whether the "C" extension
* for compressed, 16-bit instructions are supported or not */
#define DEBUG_BREAK_IMPL DEBUG_BREAK_USE_TRAP_INSTRUCTION
__attribute__((always_inline))
__inline__ static void trap_instruction(void)
{
/* See 'riscv-tdep.c' in GDB source,
* 'riscv_sw_breakpoint_from_kind' */
__asm__ volatile(".4byte 0x00100073");
}
#else
#define DEBUG_BREAK_IMPL DEBUG_BREAK_USE_SIGTRAP
#endif
#ifndef DEBUG_BREAK_IMPL
#error "debugbreak.h is not supported on this target"
#elif DEBUG_BREAK_IMPL == DEBUG_BREAK_USE_TRAP_INSTRUCTION
__attribute__((always_inline))
__inline__ static void debug_break(void)
{
trap_instruction();
}
#elif DEBUG_BREAK_IMPL == DEBUG_BREAK_USE_BULTIN_DEBUGTRAP
__attribute__((always_inline))
__inline__ static void debug_break(void)
{
__builtin_debugtrap();
}
#elif DEBUG_BREAK_IMPL == DEBUG_BREAK_USE_BULTIN_TRAP
__attribute__((always_inline))
__inline__ static void debug_break(void)
{
__builtin_trap();
}
#elif DEBUG_BREAK_IMPL == DEBUG_BREAK_USE_SIGTRAP
#include <signal.h>
__attribute__((always_inline))
__inline__ static void debug_break(void)
{
raise(SIGTRAP);
}
#else
#error "invalid DEBUG_BREAK_IMPL value"
#endif
#ifdef __cplusplus
}
#endif
#endif /* ifdef _MSC_VER */
#endif /* ifndef DEBUG_BREAK_H */

View File

@ -0,0 +1,16 @@
add_executable(functional-test-server main.c)
add_executable(functional-test-server-cpp main.cpp)
include(CheckFunctionExists)
check_function_exists(epoll_wait EPOLL)
check_function_exists(kqueue KQUEUE)
if(KQUEUE)
target_compile_definitions(functional-test-server PRIVATE "KQUEUE")
target_compile_definitions(functional-test-server-cpp PRIVATE "KQUEUE")
endif()
if(EPOLL)
target_compile_definitions(functional-test-server PRIVATE "EPOLL")
target_compile_definitions(functional-test-server-cpp PRIVATE "EPOLL")
endif()

View File

@ -0,0 +1,99 @@
#!/usr/bin/env sh
run_functional_tests() {
echo "Small Response Body:"
curl http://localhost:8080/
echo "\n\nEmpty Response:"
curl http://localhost:8080/empty
echo "\n\nEcho Body:"
curl -XPOST http://localhost:8080/echo -d'Echo test'
echo "\n\nGet Header:"
curl http://localhost:8080/host
echo "\n\nRequest Body larger than max in mem size:"
dd if=/dev/urandom of=test.dat bs=25165824 count=1 2> /dev/null
curl -H'Expect:' --data-binary @test.dat -o r1.dat http://localhost:8080/large
diff r1.dat test.dat
echo "\n\nChunked Response:"
curl http://localhost:8080/chunked
echo "\n\nChunked Response keep-alive:"
curl http://localhost:8080/chunked http://localhost:8080/chunked
echo "\n\nChunked Response close:"
curl -H'Connection: close' http://localhost:8080/chunked http://localhost:8080/chunked
echo "\n\nChunked Request keep-alive: (expect empty)"
dd if=/dev/urandom of=test.dat bs=262144 count=1 2> /dev/null
curl -H'Expect:' -H'Transfer-Encoding: chunked' -XPOST --data-binary @test.dat -o r1.dat http://localhost:8080/chunked-req -o r2.dat http://localhost:8080/chunked-req
diff r1.dat test.dat
diff r2.dat test.dat
echo "\n\nChunked Request close: (expect empty)"
curl -H'Expect:' -H'Transfer-Encoding: chunked' -XPOST --data-binary @test.dat -o r3.dat http://localhost:8080/chunked-req -o r4.dat http://localhost:8080/chunked-req
diff r3.dat test.dat
diff r4.dat test.dat
echo "\n\nPoll Server:"
curl http://localhost:8081/ &
sleep 1
curl http://localhost:8080/poll
sleep 1
curl http://localhost:8080/poll
echo "\n\nIterate Headers:"
curl -H'User-Agent: test-ua' -H'Foo-Header: foo-bar' http://localhost:8080/headers
echo "\nStress Test keep-alive:"
ab -k -c 200 -n 100000 http://127.0.0.1:8080/ > keep-alive.bench
cat keep-alive.bench | grep Complete
cat keep-alive.bench | grep Non-2xx
cat keep-alive.bench 1>&2
echo "\nStress Test close:"
ab -c 20 -n 1000 http://127.0.0.1:8080/ > connection-close.bench
cat connection-close.bench | grep Complete
cat connection-close.bench | grep Non-2xx
cat connection-close.bench 1>&2
echo "\nStress Test large body:"
dd if=/dev/urandom of=test.dat bs=7388608 count=1 2> /dev/null
ab -c 2 -n 10 -p test.dat http://127.0.0.1:8080/echo > large-file.bench
cat large-file.bench | grep Complete
cat large-file.bench | grep Non-2xx
cat large-file.bench 1>&2
rm *.dat *.bench
}
run_valgrind_functional_tests() {
valgrind --leak-check=full \
--show-leak-kinds=all \
--track-origins=yes \
--verbose \
--log-file=valgrind-log.txt \
./http-server &
serverpid=$!
sleep 3
run_functional_tests
kill $serverpid
sleep 2
cat valgrind-log.txt | cut -d = -f 5 | grep ERROR > valgrind-results.txt
cat valgrind-log.txt | cut -d = -f 5 | grep 'All heap blocks were freed' >> valgrind-results.txt
cat valgrind-log.txt 1>&2
diff valgrind-results.txt test/valgrind.txt
}
run_base_functional_tests() {
./build/test/functional/functional-test-server$1 &
serverpid=$!
sleep 1
run_functional_tests > test-results$1.txt
kill $serverpid
diff test-results$1.txt test/functional/results.txt
}
run_base_functional_tests $1

View File

@ -0,0 +1,128 @@
#define HTTPSERVER_IMPL
#include "../../build/src/httpserver.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <signal.h>
#include <unistd.h>
#define RESPONSE "Hello, World!"
ssize_t hs_test_write(int fd, char const *data, size_t size) {
return write(fd, data, size);
}
int request_target_is(struct http_request_s* request, char const * target) {
http_string_t url = http_request_target(request);
int len = strlen(target);
return len == url.len && memcmp(url.buf, target, url.len) == 0;
}
int chunk_count = 0;
void chunk_cb(struct http_request_s* request) {
chunk_count++;
struct http_response_s* response = http_response_init();
http_response_body(response, RESPONSE, sizeof(RESPONSE) - 1);
if (chunk_count < 3) {
http_respond_chunk(request, response, chunk_cb);
} else {
http_response_header(response, "Foo-Header", "bar");
http_respond_chunk_end(request, response);
}
}
typedef struct {
char* buf;
struct http_response_s* response;
int index;
} chunk_buf_t;
void chunk_req_cb(struct http_request_s* request) {
http_string_t str = http_request_chunk(request);
chunk_buf_t* chunk_buffer = (chunk_buf_t*)http_request_userdata(request);
if (str.len > 0) {
memcpy(chunk_buffer->buf + chunk_buffer->index, str.buf, str.len);
chunk_buffer->index += str.len;
http_request_read_chunk(request, chunk_req_cb);
} else {
http_response_body(chunk_buffer->response, chunk_buffer->buf, chunk_buffer->index);
http_respond(request, chunk_buffer->response);
free(chunk_buffer->buf);
free(chunk_buffer);
}
}
struct http_server_s* poll_server;
void handle_request(struct http_request_s* request) {
chunk_count = 0;
http_request_connection(request, HTTP_AUTOMATIC);
struct http_response_s* response = http_response_init();
http_response_status(response, 200);
if (request_target_is(request, "/echo")) {
http_string_t body = http_request_body(request);
http_response_header(response, "Content-Type", "text/plain");
http_response_body(response, body.buf, body.len);
} else if (request_target_is(request, "/host")) {
http_string_t ua = http_request_header(request, "Host");
http_response_header(response, "Content-Type", "text/plain");
http_response_body(response, ua.buf, ua.len);
} else if (request_target_is(request, "/poll")) {
while (http_server_poll(poll_server) > 0);
http_response_header(response, "Content-Type", "text/plain");
http_response_body(response, RESPONSE, sizeof(RESPONSE) - 1);
} else if (request_target_is(request, "/empty")) {
// No Body
} else if (request_target_is(request, "/chunked")) {
http_response_header(response, "Content-Type", "text/plain");
http_response_body(response, RESPONSE, sizeof(RESPONSE) - 1);
http_respond_chunk(request, response, chunk_cb);
return;
} else if (request_target_is(request, "/chunked-req")) {
chunk_buf_t* chunk_buffer = (chunk_buf_t*)calloc(1, sizeof(chunk_buf_t));
chunk_buffer->buf = (char*)malloc(512 * 1024);
chunk_buffer->response = response;
http_request_set_userdata(request, chunk_buffer);
http_request_read_chunk(request, chunk_req_cb);
return;
} else if (request_target_is(request, "/large")) {
chunk_buf_t* chunk_buffer = (chunk_buf_t*)calloc(1, sizeof(chunk_buf_t));
chunk_buffer->buf = (char*)malloc(25165824);
chunk_buffer->response = response;
http_request_set_userdata(request, chunk_buffer);
http_request_read_chunk(request, chunk_req_cb);
return;
} else if (request_target_is(request, "/headers")) {
int iter = 0, i = 0;
http_string_t key, val;
char buf[512];
while (http_request_iterate_headers(request, &key, &val, &iter)) {
i += snprintf(buf + i, 512 - i, "%.*s: %.*s\n", key.len, key.buf, val.len, val.buf);
}
http_response_header(response, "Content-Type", "text/plain");
http_response_body(response, buf, i);
return http_respond(request, response);
} else {
http_response_header(response, "Content-Type", "text/plain");
http_response_body(response, RESPONSE, sizeof(RESPONSE) - 1);
}
http_respond(request, response);
}
struct http_server_s* server;
void handle_sigterm(int signum) {
(void)signum;
free(server);
free(poll_server);
exit(0);
}
int main() {
signal(SIGTERM, handle_sigterm);
server = http_server_init(8080, handle_request);
poll_server = http_server_init(8081, handle_request);
http_server_listen_poll(poll_server);
http_server_listen(server);
}

View File

@ -0,0 +1 @@
main.c

View File

@ -0,0 +1,47 @@
Small Response Body:
Hello, World!
Empty Response:
Echo Body:
Echo test
Get Header:
localhost:8080
Request Body larger than max in mem size:
Chunked Response:
Hello, World!Hello, World!Hello, World!
Chunked Response keep-alive:
Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!
Chunked Response close:
Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!
Chunked Request keep-alive: (expect empty)
Chunked Request close: (expect empty)
Poll Server:
Hello, World!Hello, World!Hello, World!
Iterate Headers:
Host: localhost:8080
Accept: */*
User-Agent: test-ua
Foo-Header: foo-bar
Stress Test keep-alive:
Complete requests: 100000
Stress Test close:
Complete requests: 1000
Stress Test large body:
Complete requests: 10

View File

@ -0,0 +1,3 @@
ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)
ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)
All heap blocks were freed -- no leaks are possible

View File

@ -0,0 +1,18 @@
add_executable(
unit-test-runner
munit.c test_parser.c test_read_socket.c test_write_socket.c main.c
)
include(CheckFunctionExists)
check_function_exists(epoll_wait EPOLL)
check_function_exists(kqueue KQUEUE)
if(KQUEUE)
target_compile_definitions(unit-test-runner PRIVATE "KQUEUE")
endif()
if(EPOLL)
target_compile_definitions(unit-test-runner PRIVATE "EPOLL")
endif()
target_link_libraries(unit-test-runner PUBLIC httpsrv)

View File

@ -0,0 +1,33 @@
#include <stddef.h>
#include "munit.h"
#include "test_parser.h"
#include "test_read_socket.h"
#include "test_write_socket.h"
static MunitTest tests[] = {
// definition order: test-name, test-func, setup-func, teardown-func, options, params
{ (char*) "/parser/small_body/complete", test_parser_small_body_complete, NULL, NULL, MUNIT_TEST_OPTION_NONE, NULL },
{ (char*) "/parser/small_body/partial", test_parser_small_body_partial, NULL, NULL, MUNIT_TEST_OPTION_NONE, NULL },
{ (char*) "/parser/large_body", test_parser_large_body, NULL, NULL, MUNIT_TEST_OPTION_NONE, NULL },
{ (char*) "/parser/chunked_body", test_parser_chunked_body, NULL, NULL, MUNIT_TEST_OPTION_NONE, NULL },
{ (char*) "/parser/chunked_body/partial", test_parser_chunked_body_partial, NULL, NULL, MUNIT_TEST_OPTION_NONE, NULL },
{ (char*) "/read_socket/small_body", test_read_socket_small_body, NULL, NULL, MUNIT_TEST_OPTION_NONE, NULL },
{ (char*) "/read_socket/small_body/expand_buffer", test_read_socket_small_body_expand_buffer, NULL, NULL, MUNIT_TEST_OPTION_NONE, NULL },
{ (char*) "/read_socket/large_body", test_read_socket_large_body, NULL, NULL, MUNIT_TEST_OPTION_NONE, NULL },
{ (char*) "/write_socket/partial", test_write_socket_partial, NULL, NULL, MUNIT_TEST_OPTION_NONE, NULL },
// end
{ NULL, NULL, NULL, NULL, MUNIT_TEST_OPTION_NONE, NULL }
};
static const MunitSuite test_suite = {
(char*) "/hs", // Test suite name prefix
tests, // Tests
NULL, // Sub test suites
1, // Execution mode (normal)
MUNIT_SUITE_OPTION_NONE // Options
};
int main(int argc, char** argv) {
return munit_suite_main(&test_suite, (void*) "httpserver.h", argc, argv);
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,535 @@
/* µnit Testing Framework
* Copyright (c) 2013-2017 Evan Nemerson <evan@nemerson.com>
*
* Permission is hereby granted, free of charge, to any person
* obtaining a copy of this software and associated documentation
* files (the "Software"), to deal in the Software without
* restriction, including without limitation the rights to use, copy,
* modify, merge, publish, distribute, sublicense, and/or sell copies
* of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
* BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
#if !defined(MUNIT_H)
#define MUNIT_H
#include <stdarg.h>
#include <stdlib.h>
#define MUNIT_VERSION(major, minor, revision) \
(((major) << 16) | ((minor) << 8) | (revision))
#define MUNIT_CURRENT_VERSION MUNIT_VERSION(0, 4, 1)
#if defined(_MSC_VER) && (_MSC_VER < 1600)
# define munit_int8_t __int8
# define munit_uint8_t unsigned __int8
# define munit_int16_t __int16
# define munit_uint16_t unsigned __int16
# define munit_int32_t __int32
# define munit_uint32_t unsigned __int32
# define munit_int64_t __int64
# define munit_uint64_t unsigned __int64
#else
# include <stdint.h>
# define munit_int8_t int8_t
# define munit_uint8_t uint8_t
# define munit_int16_t int16_t
# define munit_uint16_t uint16_t
# define munit_int32_t int32_t
# define munit_uint32_t uint32_t
# define munit_int64_t int64_t
# define munit_uint64_t uint64_t
#endif
#if defined(_MSC_VER) && (_MSC_VER < 1800)
# if !defined(PRIi8)
# define PRIi8 "i"
# endif
# if !defined(PRIi16)
# define PRIi16 "i"
# endif
# if !defined(PRIi32)
# define PRIi32 "i"
# endif
# if !defined(PRIi64)
# define PRIi64 "I64i"
# endif
# if !defined(PRId8)
# define PRId8 "d"
# endif
# if !defined(PRId16)
# define PRId16 "d"
# endif
# if !defined(PRId32)
# define PRId32 "d"
# endif
# if !defined(PRId64)
# define PRId64 "I64d"
# endif
# if !defined(PRIx8)
# define PRIx8 "x"
# endif
# if !defined(PRIx16)
# define PRIx16 "x"
# endif
# if !defined(PRIx32)
# define PRIx32 "x"
# endif
# if !defined(PRIx64)
# define PRIx64 "I64x"
# endif
# if !defined(PRIu8)
# define PRIu8 "u"
# endif
# if !defined(PRIu16)
# define PRIu16 "u"
# endif
# if !defined(PRIu32)
# define PRIu32 "u"
# endif
# if !defined(PRIu64)
# define PRIu64 "I64u"
# endif
#else
# include <inttypes.h>
#endif
#if !defined(munit_bool)
# if defined(bool)
# define munit_bool bool
# elif defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 199901L)
# define munit_bool _Bool
# else
# define munit_bool int
# endif
#endif
#if defined(__cplusplus)
extern "C" {
#endif
#if defined(__GNUC__)
# define MUNIT_LIKELY(expr) (__builtin_expect ((expr), 1))
# define MUNIT_UNLIKELY(expr) (__builtin_expect ((expr), 0))
# define MUNIT_UNUSED __attribute__((__unused__))
#else
# define MUNIT_LIKELY(expr) (expr)
# define MUNIT_UNLIKELY(expr) (expr)
# define MUNIT_UNUSED
#endif
#if defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 199901L) && !defined(__PGI)
# define MUNIT_ARRAY_PARAM(name) name
#else
# define MUNIT_ARRAY_PARAM(name)
#endif
#if !defined(_WIN32)
# define MUNIT_SIZE_MODIFIER "z"
# define MUNIT_CHAR_MODIFIER "hh"
# define MUNIT_SHORT_MODIFIER "h"
#else
# if defined(_M_X64) || defined(__amd64__)
# define MUNIT_SIZE_MODIFIER "I64"
# else
# define MUNIT_SIZE_MODIFIER ""
# endif
# define MUNIT_CHAR_MODIFIER ""
# define MUNIT_SHORT_MODIFIER ""
#endif
#if defined(__STDC_VERSION__) && __STDC_VERSION__ >= 201112L
# define MUNIT_NO_RETURN _Noreturn
#elif defined(__GNUC__)
# define MUNIT_NO_RETURN __attribute__((__noreturn__))
#elif defined(_MSC_VER)
# define MUNIT_NO_RETURN __declspec(noreturn)
#else
# define MUNIT_NO_RETURN
#endif
#if defined(_MSC_VER) && (_MSC_VER >= 1500)
# define MUNIT_PUSH_DISABLE_MSVC_C4127_ __pragma(warning(push)) __pragma(warning(disable:4127))
# define MUNIT_POP_DISABLE_MSVC_C4127_ __pragma(warning(pop))
#else
# define MUNIT_PUSH_DISABLE_MSVC_C4127_
# define MUNIT_POP_DISABLE_MSVC_C4127_
#endif
typedef enum {
MUNIT_LOG_DEBUG,
MUNIT_LOG_INFO,
MUNIT_LOG_WARNING,
MUNIT_LOG_ERROR
} MunitLogLevel;
#if defined(__GNUC__) && !defined(__MINGW32__)
# define MUNIT_PRINTF(string_index, first_to_check) __attribute__((format (printf, string_index, first_to_check)))
#else
# define MUNIT_PRINTF(string_index, first_to_check)
#endif
MUNIT_PRINTF(4, 5)
void munit_logf_ex(MunitLogLevel level, const char* filename, int line, const char* format, ...);
#define munit_logf(level, format, ...) \
munit_logf_ex(level, __FILE__, __LINE__, format, __VA_ARGS__)
#define munit_log(level, msg) \
munit_logf(level, "%s", msg)
MUNIT_NO_RETURN
MUNIT_PRINTF(3, 4)
void munit_errorf_ex(const char* filename, int line, const char* format, ...);
#define munit_errorf(format, ...) \
munit_errorf_ex(__FILE__, __LINE__, format, __VA_ARGS__)
#define munit_error(msg) \
munit_errorf("%s", msg)
#define munit_assert(expr) \
do { \
if (!MUNIT_LIKELY(expr)) { \
munit_error("assertion failed: " #expr); \
} \
MUNIT_PUSH_DISABLE_MSVC_C4127_ \
} while (0) \
MUNIT_POP_DISABLE_MSVC_C4127_
#define munit_assert_true(expr) \
do { \
if (!MUNIT_LIKELY(expr)) { \
munit_error("assertion failed: " #expr " is not true"); \
} \
MUNIT_PUSH_DISABLE_MSVC_C4127_ \
} while (0) \
MUNIT_POP_DISABLE_MSVC_C4127_
#define munit_assert_false(expr) \
do { \
if (!MUNIT_LIKELY(!(expr))) { \
munit_error("assertion failed: " #expr " is not false"); \
} \
MUNIT_PUSH_DISABLE_MSVC_C4127_ \
} while (0) \
MUNIT_POP_DISABLE_MSVC_C4127_
#define munit_assert_type_full(prefix, suffix, T, fmt, a, op, b) \
do { \
T munit_tmp_a_ = (a); \
T munit_tmp_b_ = (b); \
if (!(munit_tmp_a_ op munit_tmp_b_)) { \
munit_errorf("assertion failed: %s %s %s (" prefix "%" fmt suffix " %s " prefix "%" fmt suffix ")", \
#a, #op, #b, munit_tmp_a_, #op, munit_tmp_b_); \
} \
MUNIT_PUSH_DISABLE_MSVC_C4127_ \
} while (0) \
MUNIT_POP_DISABLE_MSVC_C4127_
#define munit_assert_type(T, fmt, a, op, b) \
munit_assert_type_full("", "", T, fmt, a, op, b)
#define munit_assert_char(a, op, b) \
munit_assert_type_full("'\\x", "'", char, "02" MUNIT_CHAR_MODIFIER "x", a, op, b)
#define munit_assert_uchar(a, op, b) \
munit_assert_type_full("'\\x", "'", unsigned char, "02" MUNIT_CHAR_MODIFIER "x", a, op, b)
#define munit_assert_short(a, op, b) \
munit_assert_type(short, MUNIT_SHORT_MODIFIER "d", a, op, b)
#define munit_assert_ushort(a, op, b) \
munit_assert_type(unsigned short, MUNIT_SHORT_MODIFIER "u", a, op, b)
#define munit_assert_int(a, op, b) \
munit_assert_type(int, "d", a, op, b)
#define munit_assert_uint(a, op, b) \
munit_assert_type(unsigned int, "u", a, op, b)
#define munit_assert_long(a, op, b) \
munit_assert_type(long int, "ld", a, op, b)
#define munit_assert_ulong(a, op, b) \
munit_assert_type(unsigned long int, "lu", a, op, b)
#define munit_assert_llong(a, op, b) \
munit_assert_type(long long int, "lld", a, op, b)
#define munit_assert_ullong(a, op, b) \
munit_assert_type(unsigned long long int, "llu", a, op, b)
#define munit_assert_size(a, op, b) \
munit_assert_type(size_t, MUNIT_SIZE_MODIFIER "u", a, op, b)
#define munit_assert_float(a, op, b) \
munit_assert_type(float, "f", a, op, b)
#define munit_assert_double(a, op, b) \
munit_assert_type(double, "g", a, op, b)
#define munit_assert_ptr(a, op, b) \
munit_assert_type(const void*, "p", a, op, b)
#define munit_assert_int8(a, op, b) \
munit_assert_type(munit_int8_t, PRIi8, a, op, b)
#define munit_assert_uint8(a, op, b) \
munit_assert_type(munit_uint8_t, PRIu8, a, op, b)
#define munit_assert_int16(a, op, b) \
munit_assert_type(munit_int16_t, PRIi16, a, op, b)
#define munit_assert_uint16(a, op, b) \
munit_assert_type(munit_uint16_t, PRIu16, a, op, b)
#define munit_assert_int32(a, op, b) \
munit_assert_type(munit_int32_t, PRIi32, a, op, b)
#define munit_assert_uint32(a, op, b) \
munit_assert_type(munit_uint32_t, PRIu32, a, op, b)
#define munit_assert_int64(a, op, b) \
munit_assert_type(munit_int64_t, PRIi64, a, op, b)
#define munit_assert_uint64(a, op, b) \
munit_assert_type(munit_uint64_t, PRIu64, a, op, b)
#define munit_assert_double_equal(a, b, precision) \
do { \
const double munit_tmp_a_ = (a); \
const double munit_tmp_b_ = (b); \
const double munit_tmp_diff_ = ((munit_tmp_a_ - munit_tmp_b_) < 0) ? \
-(munit_tmp_a_ - munit_tmp_b_) : \
(munit_tmp_a_ - munit_tmp_b_); \
if (MUNIT_UNLIKELY(munit_tmp_diff_ > 1e-##precision)) { \
munit_errorf("assertion failed: %s == %s (%0." #precision "g == %0." #precision "g)", \
#a, #b, munit_tmp_a_, munit_tmp_b_); \
} \
MUNIT_PUSH_DISABLE_MSVC_C4127_ \
} while (0) \
MUNIT_POP_DISABLE_MSVC_C4127_
#include <string.h>
#define munit_assert_string_equal(a, b) \
do { \
const char* munit_tmp_a_ = a; \
const char* munit_tmp_b_ = b; \
if (MUNIT_UNLIKELY(strcmp(munit_tmp_a_, munit_tmp_b_) != 0)) { \
munit_errorf("assertion failed: string %s == %s (\"%s\" == \"%s\")", \
#a, #b, munit_tmp_a_, munit_tmp_b_); \
} \
MUNIT_PUSH_DISABLE_MSVC_C4127_ \
} while (0) \
MUNIT_POP_DISABLE_MSVC_C4127_
#define munit_assert_string_not_equal(a, b) \
do { \
const char* munit_tmp_a_ = a; \
const char* munit_tmp_b_ = b; \
if (MUNIT_UNLIKELY(strcmp(munit_tmp_a_, munit_tmp_b_) == 0)) { \
munit_errorf("assertion failed: string %s != %s (\"%s\" == \"%s\")", \
#a, #b, munit_tmp_a_, munit_tmp_b_); \
} \
MUNIT_PUSH_DISABLE_MSVC_C4127_ \
} while (0) \
MUNIT_POP_DISABLE_MSVC_C4127_
#define munit_assert_memory_equal(size, a, b) \
do { \
const unsigned char* munit_tmp_a_ = (const unsigned char*) (a); \
const unsigned char* munit_tmp_b_ = (const unsigned char*) (b); \
const size_t munit_tmp_size_ = (size); \
if (MUNIT_UNLIKELY(memcmp(munit_tmp_a_, munit_tmp_b_, munit_tmp_size_)) != 0) { \
size_t munit_tmp_pos_; \
for (munit_tmp_pos_ = 0 ; munit_tmp_pos_ < munit_tmp_size_ ; munit_tmp_pos_++) { \
if (munit_tmp_a_[munit_tmp_pos_] != munit_tmp_b_[munit_tmp_pos_]) { \
munit_errorf("assertion failed: memory %s == %s, at offset %" MUNIT_SIZE_MODIFIER "u", \
#a, #b, munit_tmp_pos_); \
break; \
} \
} \
} \
MUNIT_PUSH_DISABLE_MSVC_C4127_ \
} while (0) \
MUNIT_POP_DISABLE_MSVC_C4127_
#define munit_assert_memory_not_equal(size, a, b) \
do { \
const unsigned char* munit_tmp_a_ = (const unsigned char*) (a); \
const unsigned char* munit_tmp_b_ = (const unsigned char*) (b); \
const size_t munit_tmp_size_ = (size); \
if (MUNIT_UNLIKELY(memcmp(munit_tmp_a_, munit_tmp_b_, munit_tmp_size_)) == 0) { \
munit_errorf("assertion failed: memory %s != %s (%zu bytes)", \
#a, #b, munit_tmp_size_); \
} \
MUNIT_PUSH_DISABLE_MSVC_C4127_ \
} while (0) \
MUNIT_POP_DISABLE_MSVC_C4127_
#define munit_assert_ptr_equal(a, b) \
munit_assert_ptr(a, ==, b)
#define munit_assert_ptr_not_equal(a, b) \
munit_assert_ptr(a, !=, b)
#define munit_assert_null(ptr) \
munit_assert_ptr(ptr, ==, NULL)
#define munit_assert_not_null(ptr) \
munit_assert_ptr(ptr, !=, NULL)
#define munit_assert_ptr_null(ptr) \
munit_assert_ptr(ptr, ==, NULL)
#define munit_assert_ptr_not_null(ptr) \
munit_assert_ptr(ptr, !=, NULL)
/*** Memory allocation ***/
void* munit_malloc_ex(const char* filename, int line, size_t size);
#define munit_malloc(size) \
munit_malloc_ex(__FILE__, __LINE__, (size))
#define munit_new(type) \
((type*) munit_malloc(sizeof(type)))
#define munit_calloc(nmemb, size) \
munit_malloc((nmemb) * (size))
#define munit_newa(type, nmemb) \
((type*) munit_calloc((nmemb), sizeof(type)))
/*** Random number generation ***/
void munit_rand_seed(munit_uint32_t seed);
munit_uint32_t munit_rand_uint32(void);
int munit_rand_int_range(int min, int max);
double munit_rand_double(void);
void munit_rand_memory(size_t size, munit_uint8_t buffer[MUNIT_ARRAY_PARAM(size)]);
/*** Tests and Suites ***/
typedef enum {
/* Test successful */
MUNIT_OK,
/* Test failed */
MUNIT_FAIL,
/* Test was skipped */
MUNIT_SKIP,
/* Test failed due to circumstances not intended to be tested
* (things like network errors, invalid parameter value, failure to
* allocate memory in the test harness, etc.). */
MUNIT_ERROR
} MunitResult;
typedef struct {
char* name;
char** values;
} MunitParameterEnum;
typedef struct {
char* name;
char* value;
} MunitParameter;
const char* munit_parameters_get(const MunitParameter params[], const char* key);
typedef enum {
MUNIT_TEST_OPTION_NONE = 0,
MUNIT_TEST_OPTION_SINGLE_ITERATION = 1 << 0,
MUNIT_TEST_OPTION_TODO = 1 << 1
} MunitTestOptions;
typedef MunitResult (* MunitTestFunc)(const MunitParameter params[], void* user_data_or_fixture);
typedef void* (* MunitTestSetup)(const MunitParameter params[], void* user_data);
typedef void (* MunitTestTearDown)(void* fixture);
typedef struct {
char* name;
MunitTestFunc test;
MunitTestSetup setup;
MunitTestTearDown tear_down;
MunitTestOptions options;
MunitParameterEnum* parameters;
} MunitTest;
typedef enum {
MUNIT_SUITE_OPTION_NONE = 0
} MunitSuiteOptions;
typedef struct MunitSuite_ MunitSuite;
struct MunitSuite_ {
char* prefix;
MunitTest* tests;
MunitSuite* suites;
unsigned int iterations;
MunitSuiteOptions options;
};
int munit_suite_main(const MunitSuite* suite, void* user_data, int argc, char* const argv[MUNIT_ARRAY_PARAM(argc + 1)]);
/* Note: I'm not very happy with this API; it's likely to change if I
* figure out something better. Suggestions welcome. */
typedef struct MunitArgument_ MunitArgument;
struct MunitArgument_ {
char* name;
munit_bool (* parse_argument)(const MunitSuite* suite, void* user_data, int* arg, int argc, char* const argv[MUNIT_ARRAY_PARAM(argc + 1)]);
void (* write_help)(const MunitArgument* argument, void* user_data);
};
int munit_suite_main_custom(const MunitSuite* suite,
void* user_data,
int argc, char* const argv[MUNIT_ARRAY_PARAM(argc + 1)],
const MunitArgument arguments[]);
#if defined(MUNIT_ENABLE_ASSERT_ALIASES)
#define assert_true(expr) munit_assert_true(expr)
#define assert_false(expr) munit_assert_false(expr)
#define assert_char(a, op, b) munit_assert_char(a, op, b)
#define assert_uchar(a, op, b) munit_assert_uchar(a, op, b)
#define assert_short(a, op, b) munit_assert_short(a, op, b)
#define assert_ushort(a, op, b) munit_assert_ushort(a, op, b)
#define assert_int(a, op, b) munit_assert_int(a, op, b)
#define assert_uint(a, op, b) munit_assert_uint(a, op, b)
#define assert_long(a, op, b) munit_assert_long(a, op, b)
#define assert_ulong(a, op, b) munit_assert_ulong(a, op, b)
#define assert_llong(a, op, b) munit_assert_llong(a, op, b)
#define assert_ullong(a, op, b) munit_assert_ullong(a, op, b)
#define assert_size(a, op, b) munit_assert_size(a, op, b)
#define assert_float(a, op, b) munit_assert_float(a, op, b)
#define assert_double(a, op, b) munit_assert_double(a, op, b)
#define assert_ptr(a, op, b) munit_assert_ptr(a, op, b)
#define assert_int8(a, op, b) munit_assert_int8(a, op, b)
#define assert_uint8(a, op, b) munit_assert_uint8(a, op, b)
#define assert_int16(a, op, b) munit_assert_int16(a, op, b)
#define assert_uint16(a, op, b) munit_assert_uint16(a, op, b)
#define assert_int32(a, op, b) munit_assert_int32(a, op, b)
#define assert_uint32(a, op, b) munit_assert_uint32(a, op, b)
#define assert_int64(a, op, b) munit_assert_int64(a, op, b)
#define assert_uint64(a, op, b) munit_assert_uint64(a, op, b)
#define assert_double_equal(a, b, precision) munit_assert_double_equal(a, b, precision)
#define assert_string_equal(a, b) munit_assert_string_equal(a, b)
#define assert_string_not_equal(a, b) munit_assert_string_not_equal(a, b)
#define assert_memory_equal(size, a, b) munit_assert_memory_equal(size, a, b)
#define assert_memory_not_equal(size, a, b) munit_assert_memory_not_equal(size, a, b)
#define assert_ptr_equal(a, b) munit_assert_ptr_equal(a, b)
#define assert_ptr_not_equal(a, b) munit_assert_ptr_not_equal(a, b)
#define assert_ptr_null(ptr) munit_assert_null_equal(ptr)
#define assert_ptr_not_null(ptr) munit_assert_not_null(ptr)
#define assert_null(ptr) munit_assert_null(ptr)
#define assert_not_null(ptr) munit_assert_not_null(ptr)
#endif /* defined(MUNIT_ENABLE_ASSERT_ALIASES) */
#if defined(__cplusplus)
}
#endif
#endif /* !defined(MUNIT_H) */
#if defined(MUNIT_ENABLE_ASSERT_ALIASES)
# if defined(assert)
# undef assert
# endif
# define assert(expr) munit_assert(expr)
#endif

View File

@ -0,0 +1,5 @@
GET /foo HTTP/1.1
Host: www.jeremycw.com
Content-Length: 13
Hello, World!

View File

@ -0,0 +1,264 @@
#include "munit.h"
#include <stdlib.h>
#include <string.h>
#include "common.h"
#include "parser.h"
void setup_buffer_and_parser(struct hsh_parser_s *parser,
struct hsh_buffer_s *buffer, char const *req_str) {
// setup parser
hsh_parser_init(parser);
// setup input buffer
int len = strlen(req_str);
buffer->buf = malloc(1024);
buffer->capacity = 1024;
memcpy(buffer->buf, req_str, len);
buffer->length = len;
buffer->sequence_id = 1;
}
MunitResult test_parser_small_body_complete(const MunitParameter params[],
void *data) {
(void)params;
(void)data;
char const *request_string =
"GET /foo HTTP/1.1\r\nHost: www.jeremycw.com\r\nContent-Length: "
"16\r\n\r\naaaaaaaaaaaaaaaa";
struct hsh_parser_s parser = {0};
struct hsh_buffer_s buffer = {0};
setup_buffer_and_parser(&parser, &buffer, request_string);
enum hsh_token_e expected_types[] = {
HSH_TOK_METHOD, HSH_TOK_TARGET, HSH_TOK_VERSION,
HSH_TOK_HEADER_KEY, HSH_TOK_HEADER_VALUE, HSH_TOK_HEADER_KEY,
HSH_TOK_HEADER_VALUE, HSH_TOK_HEADERS_DONE, HSH_TOK_BODY};
char const *expected_values[] = {"GET",
"/foo",
"HTTP/1.1",
"Host",
"www.jeremycw.com",
"Content-Length",
"16",
"",
"aaaaaaaaaaaaaaaa"};
int i = 0;
struct hsh_token_s out;
while ((out = hsh_parser_exec(&parser, &buffer, 1024)).type != HSH_TOK_NONE) {
munit_assert_memory_equal(out.len, expected_values[i],
&buffer.buf[out.index]);
munit_assert(expected_types[i] == out.type);
if (out.type == HSH_TOK_HEADERS_DONE) {
munit_assert(out.flags == 0);
}
i++;
}
munit_assert(i == 9);
free(buffer.buf);
return MUNIT_OK;
}
MunitResult test_parser_small_body_partial(const MunitParameter params[],
void *data) {
(void)params;
(void)data;
char const *request_string =
"GET /foo HTTP/1.1\r\nHost: www.jeremycw.com\r\nContent-Length: "
"16\r\n\r\naaaaaaaa";
struct hsh_parser_s parser = {0};
struct hsh_buffer_s buffer = {0};
setup_buffer_and_parser(&parser, &buffer, request_string);
enum hsh_token_e expected_types[] = {
HSH_TOK_METHOD, HSH_TOK_TARGET, HSH_TOK_VERSION,
HSH_TOK_HEADER_KEY, HSH_TOK_HEADER_VALUE, HSH_TOK_HEADER_KEY,
HSH_TOK_HEADER_VALUE, HSH_TOK_HEADERS_DONE};
char const *expected_values[] = {
"GET", "/foo", "HTTP/1.1", "Host", "www.jeremycw.com", "Content-Length",
"16", ""};
int i = 0;
struct hsh_token_s out;
while ((out = hsh_parser_exec(&parser, &buffer, 1024)).type != HSH_TOK_NONE) {
munit_assert_memory_equal(out.len, expected_values[i],
&buffer.buf[out.index]);
munit_assert(expected_types[i] == out.type);
i++;
}
munit_assert(i == 8);
memcpy(buffer.buf + buffer.length, "aaaaaaaa", 8);
buffer.length += 8;
buffer.sequence_id++;
out = hsh_parser_exec(&parser, &buffer, 1024);
munit_assert(out.type == HSH_TOK_BODY);
munit_assert_memory_equal(out.len, "aaaaaaaaaaaaaaaa",
&buffer.buf[out.index]);
free(buffer.buf);
return MUNIT_OK;
}
MunitResult test_parser_large_body(const MunitParameter params[], void *data) {
(void)params;
(void)data;
char const *request_string =
"GET /foo HTTP/1.1\r\nHost: www.jeremycw.com\r\nContent-Length: "
"16\r\n\r\naaaaaaaa";
struct hsh_parser_s parser = {0};
struct hsh_buffer_s buffer = {0};
setup_buffer_and_parser(&parser, &buffer, request_string);
enum hsh_token_e expected_types[] = {
HSH_TOK_METHOD, HSH_TOK_TARGET, HSH_TOK_VERSION,
HSH_TOK_HEADER_KEY, HSH_TOK_HEADER_VALUE, HSH_TOK_HEADER_KEY,
HSH_TOK_HEADER_VALUE, HSH_TOK_HEADERS_DONE, HSH_TOK_BODY};
char const *expected_values[] = {
"GET", "/foo", "HTTP/1.1", "Host", "www.jeremycw.com", "Content-Length",
"16", "", "aaaaaaaa"};
int i = 0;
struct hsh_token_s out;
int max_buf_capacity = strlen(request_string);
while ((out = hsh_parser_exec(&parser, &buffer, max_buf_capacity)).type !=
HSH_TOK_NONE) {
munit_assert_memory_equal(out.len, expected_values[i],
&buffer.buf[out.index]);
munit_assert(expected_types[i] == out.type);
i++;
}
munit_assert(i == 9);
memcpy(buffer.buf + buffer.length, "bbbbbbbb", 8);
buffer.length += 8;
buffer.sequence_id++;
out = hsh_parser_exec(&parser, &buffer, max_buf_capacity);
munit_assert(out.type == HSH_TOK_BODY);
munit_assert_memory_equal(out.len, "bbbbbbbb", &buffer.buf[out.index]);
free(buffer.buf);
return MUNIT_OK;
}
MunitResult test_parser_chunked_body(const MunitParameter params[],
void *data) {
(void)params;
(void)data;
char const *request_string =
"POST /chunked/test HTTP/1.0\r\nHost: "
"www.jeremycw.com\r\nTransfer-Encoding: "
"chunked\r\n\r\n5\r\nabcde\r\na\r\n1234567890\r\n0\r\n\r\n";
struct hsh_parser_s parser = {0};
struct hsh_buffer_s buffer = {0};
setup_buffer_and_parser(&parser, &buffer, request_string);
enum hsh_token_e expected_types[] = {
HSH_TOK_METHOD, HSH_TOK_TARGET, HSH_TOK_VERSION,
HSH_TOK_HEADER_KEY, HSH_TOK_HEADER_VALUE, HSH_TOK_HEADER_KEY,
HSH_TOK_HEADER_VALUE, HSH_TOK_HEADERS_DONE, HSH_TOK_BODY,
HSH_TOK_BODY, HSH_TOK_BODY};
char const *expected_values[] = {"POST",
"/chunked/test",
"HTTP/1.0",
"Host",
"www.jeremycw.com",
"Transfer-Encoding",
"chunked",
"",
"abcde",
"1234567890",
""};
int i = 0;
struct hsh_token_s out;
int max_buf_capacity = strlen(request_string);
while ((out = hsh_parser_exec(&parser, &buffer, max_buf_capacity)).type !=
HSH_TOK_NONE) {
munit_assert_memory_equal(out.len, expected_values[i],
&buffer.buf[out.index]);
munit_assert(expected_types[i] == out.type);
i++;
}
munit_assert(i == 11);
free(buffer.buf);
return MUNIT_OK;
}
MunitResult test_parser_chunked_body_partial(const MunitParameter params[],
void *data) {
(void)params;
(void)data;
char const *request_string = "POST /chunked/test HTTP/1.0\r\nHost: "
"www.jeremycw.com\r\nTransfer-Encoding: "
"chunked\r\n\r\n5\r\nabcde\r\na\r\n12345678";
struct hsh_parser_s parser = {0};
struct hsh_buffer_s buffer = {0};
setup_buffer_and_parser(&parser, &buffer, request_string);
enum hsh_token_e expected_types[] = {
HSH_TOK_METHOD, HSH_TOK_TARGET, HSH_TOK_VERSION,
HSH_TOK_HEADER_KEY, HSH_TOK_HEADER_VALUE, HSH_TOK_HEADER_KEY,
HSH_TOK_HEADER_VALUE, HSH_TOK_HEADERS_DONE, HSH_TOK_BODY};
char const *expected_values[] = {"POST",
"/chunked/test",
"HTTP/1.0",
"Host",
"www.jeremycw.com",
"Transfer-Encoding",
"chunked",
"",
"abcde"};
int i = 0;
struct hsh_token_s out;
int max_buf_capacity = strlen(request_string);
while ((out = hsh_parser_exec(&parser, &buffer, max_buf_capacity)).type !=
HSH_TOK_NONE) {
munit_assert_memory_equal(out.len, expected_values[i],
&buffer.buf[out.index]);
munit_assert(expected_types[i] == out.type);
i++;
}
munit_assert(i == 9);
memcpy(buffer.buf + buffer.length, "90\r\n0\r\n\r\n", 9);
buffer.length += 9;
buffer.sequence_id++;
out = hsh_parser_exec(&parser, &buffer, max_buf_capacity);
munit_assert(out.type == HSH_TOK_BODY);
munit_assert_memory_equal(out.len, "1234567890", &buffer.buf[out.index]);
free(buffer.buf);
return MUNIT_OK;
}

View File

@ -0,0 +1,10 @@
#ifndef TEST_PARSER_H
#define TEST_PARSER_H
MunitResult test_parser_small_body_complete(const MunitParameter params[], void* data);
MunitResult test_parser_small_body_partial(const MunitParameter params[], void* data);
MunitResult test_parser_large_body(const MunitParameter params[], void* data);
MunitResult test_parser_chunked_body(const MunitParameter params[], void* data);
MunitResult test_parser_chunked_body_partial(const MunitParameter params[], void* data);
#endif

View File

@ -0,0 +1,153 @@
#include <fcntl.h>
#include <unistd.h>
#include <stdlib.h>
#include "munit.h"
#include "read_socket.h"
#include "common.h"
void token_array_init(struct hs_token_array_s *array, int capacity) {
array->buf =
(struct hsh_token_s *)malloc(sizeof(struct hsh_token_s) * capacity);
array->capacity = capacity;
}
void test_callback(struct http_request_s* request) {
*((int*)request->data) = 1;
}
struct http_request_s* setup_test_request() {
struct http_request_s* request = calloc(1, sizeof(struct http_request_s));
request->server = calloc(1, sizeof(struct http_server_s));
token_array_init(&request->tokens, 32);
request->server->request_handler = &test_callback;
return request;
}
void destroy_test_request(struct http_request_s* request) {
free(request->server);
free(request->buffer.buf);
free(request->tokens.buf);
free(request);
}
MunitResult test_read_socket_small_body(const MunitParameter params[], void* data) {
(void)params;
(void)data;
struct http_request_s* request = setup_test_request();
int callback_ran = 0;
request->data = (void*)&callback_ran;
struct hs_read_opts_s opts = {
.max_request_buf_capacity = 4096,
.initial_request_buf_capacity = 1024,
.eof_rc = -1
};
int fd = openat(AT_FDCWD, "test/unit/read_socket.txt", O_RDONLY);
request->socket = fd;
hs_read_request_and_exec_user_cb(request, opts);
munit_assert_int(request->timeout, ==, HTTP_REQUEST_TIMEOUT);
munit_assert_int64(request->server->memused, ==, 1024);
munit_assert(callback_ran);
struct hsh_token_s tok = request->tokens.buf[request->tokens.size-1];
munit_assert_memory_equal(tok.len, "Hello, World!",
&request->buffer.buf[tok.index]);
close(fd);
destroy_test_request(request);
return MUNIT_OK;
}
MunitResult test_read_socket_small_body_expand_buffer(const MunitParameter params[], void* data) {
(void)params;
(void)data;
struct http_request_s* request = setup_test_request();
int callback_ran = 0;
request->data = (void*)&callback_ran;
struct hs_read_opts_s opts = {
.max_request_buf_capacity = 4096,
.initial_request_buf_capacity = 8,
.eof_rc = -1
};
int fd = openat(AT_FDCWD, "test/unit/read_socket.txt", O_RDONLY);
request->socket = fd;
hs_read_request_and_exec_user_cb(request, opts);
munit_assert_int64(request->server->memused, ==, 128);
struct hsh_token_s tok = request->tokens.buf[request->tokens.size-1];
munit_assert_memory_equal(tok.len, "Hello, World!",
&request->buffer.buf[tok.index]);
munit_assert(callback_ran);
close(fd);
destroy_test_request(request);
return MUNIT_OK;
}
void test_chunk_callback(struct http_request_s* request) {
*((int*)request->data) = 2;
}
MunitResult test_read_socket_large_body(const MunitParameter params[], void* data) {
(void)params;
(void)data;
struct http_request_s* request = setup_test_request();
int callback_ran = 0;
request->data = (void*)&callback_ran;
request->chunk_cb = &test_chunk_callback;
struct hs_read_opts_s opts = {
.max_request_buf_capacity = 72,
.initial_request_buf_capacity = 72,
.eof_rc = -1
};
int fd = openat(AT_FDCWD, "test/unit/read_socket.txt", O_RDONLY);
request->socket = fd;
hs_read_request_and_exec_user_cb(request, opts);
munit_assert(HTTP_FLAG_CHECK(request->flags, HTTP_FLG_STREAMED));
munit_assert_int(callback_ran, ==, 1);
hs_read_request_and_exec_user_cb(request, opts);
munit_assert_int(callback_ran, ==, 2);
struct hsh_token_s tok = request->tokens.buf[request->tokens.size-1];
munit_assert_memory_equal(tok.len, "Hello, ",
&request->buffer.buf[tok.index]);
hs_read_request_and_exec_user_cb(request, opts);
tok = request->tokens.buf[request->tokens.size-1];
munit_assert_memory_equal(tok.len, "World!",
&request->buffer.buf[tok.index]);
close(fd);
destroy_test_request(request);
return MUNIT_OK;
}

View File

@ -0,0 +1,3 @@
MunitResult test_read_socket_small_body(const MunitParameter params[], void* data);
MunitResult test_read_socket_small_body_expand_buffer(const MunitParameter params[], void* data);
MunitResult test_read_socket_large_body(const MunitParameter params[], void* data);

View File

@ -0,0 +1,68 @@
#include <unistd.h>
#include <stdlib.h>
#include "munit.h"
#include "write_socket.h"
#include "common.h"
enum hs_test_write_mode_e {
HS_TEST_WRITE_SUCCESS,
HS_TEST_WRITE_PARTIAL
};
static enum hs_test_write_mode_e hs_test_write_mode;
static int write_stub_enabled = 0;
void hs_test_enable_write_stub(int enabled) {
write_stub_enabled = enabled;
}
ssize_t hs_test_write(int fd, char const *data, size_t size) {
if (write_stub_enabled) {
switch (hs_test_write_mode) {
case HS_TEST_WRITE_SUCCESS:
return size;
case HS_TEST_WRITE_PARTIAL:
return size / 2;
}
return 0;
} else {
return write(fd, data, size);
}
}
static struct http_request_s* setup_test_request() {
struct http_request_s* request = calloc(1, sizeof(struct http_request_s));
request->server = calloc(1, sizeof(struct http_server_s));
request->buffer.buf = (char *)calloc(1, 1024);
request->buffer.capacity = 1024;
return request;
}
static void destroy_test_request(struct http_request_s* request) {
free(request->server);
free(request->buffer.buf);
free(request->tokens.buf);
free(request);
}
MunitResult test_write_socket_partial(const MunitParameter params[], void* data) {
(void)params;
(void)data;
struct http_request_s* request = setup_test_request();
request->buffer.length = 512;
hs_test_write_mode = HS_TEST_WRITE_PARTIAL;
hs_test_enable_write_stub(1);
enum hs_write_rc_e rc = hs_write_socket(request);
munit_assert_int(rc, ==, HS_WRITE_RC_CONTINUE);
destroy_test_request(request);
return MUNIT_OK;
}

View File

@ -0,0 +1 @@
MunitResult test_write_socket_partial(const MunitParameter params[], void* data);

View File

@ -6,5 +6,6 @@ add_subdirectory(WebServer)
add_subdirectory(UartDevice) add_subdirectory(UartDevice)
add_subdirectory(LinuxApiMock) add_subdirectory(LinuxApiMock)
add_subdirectory(McuProtocol) add_subdirectory(McuProtocol)
add_subdirectory(FxHttpServer)

View File

@ -0,0 +1,58 @@
# include(${CMAKE_SOURCE_DIR}/build/independent_source.cmake)
include(${CMAKE_SOURCE_DIR_IPCSDK}/build/global_config.cmake)
set(EXECUTABLE_OUTPUT_PATH ${TEST_OUTPUT_PATH}/bin)
include_directories(
${UTILS_SOURCE_PATH}/StatusCode/include
${UTILS_SOURCE_PATH}/Log/include
${UTILS_SOURCE_PATH}/FxHttpServer/include
${EXTERNAL_SOURCE_PATH}/gtest/googletest-release-1.11.0/googletest/include
${EXTERNAL_SOURCE_PATH}/gtest/googletest-release-1.11.0/googlemock/include
)
link_directories(
${LIBS_OUTPUT_PATH}
${EXTERNAL_LIBS_OUTPUT_PATH}
)
aux_source_directory(. SRC_FILES)
aux_source_directory(./src SRC_FILES)
set(TARGET_NAME FxHttpServerTest)
add_executable(${TARGET_NAME} ${SRC_FILES})
target_link_libraries(${TARGET_NAME} FxHttpServer gtest gmock pthread Log)
if(${TEST_COVERAGE} MATCHES "true")
target_link_libraries(${TARGET_NAME} gcov)
endif()
if ("${CLANG_TIDY_SUPPORT}" MATCHES "true")
add_custom_target(
FxHttpServerTest_code_check
COMMAND ${CLANG_TIDY_EXE}
-checks='${CLANG_TIDY_CHECKS}'
--header-filter=.*
--system-headers=false
${SRC_FILES}
${CLANG_TIDY_CONFIG}
# --line-filter='[{\"name\":\"${EXTERNAL_SOURCE_PATH}/gtest/googletest-release-1.11.0/googletest/include/getest/gtest.h\"}]'
--line-filter='[{\"name\":\"${EXTERNAL_SOURCE_PATH}/gtest/googletest-release-1.11.0/googletest/include/getest/*.h\"}]'
-p ${PLATFORM_PATH}/cmake-shell
WORKING_DIRECTORY ${TEST_SOURCE_PATH}/utils/FxHttpServer
)
file(GLOB_RECURSE HEADER_FILES *.h)
add_custom_target(
FxHttpServerTest_code_format
COMMAND ${CLANG_FORMAT_EXE}
-style=file
-i ${SRC_FILES} ${HEADER_FILES}
WORKING_DIRECTORY ${TEST_SOURCE_PATH}/utils/FxHttpServer
)
add_custom_command(
TARGET ${TARGET_NAME}
PRE_BUILD
COMMAND make FxHttpServerTest_code_check
COMMAND make FxHttpServerTest_code_format
WORKING_DIRECTORY ${PLATFORM_PATH}/cmake-shell/
)
endif()
define_file_name(${TARGET_NAME})

View File

@ -0,0 +1,23 @@
/*
* Copyright (c) 2023 Fancy Code.
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include <gmock/gmock.h>
#include <gtest/gtest.h>
#include <thread>
#include <unistd.h>
int main(int argc, char *argv[])
{
testing::InitGoogleTest(&argc, argv);
return RUN_ALL_TESTS();
}

View File

@ -0,0 +1,32 @@
/*
* Copyright (c) 2023 Fancy Code.
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "FxHttpServer.h"
#include "ILog.h"
#include <gmock/gmock.h>
#include <gtest/gtest.h>
#include <thread>
namespace FxHttpServerTest
{
// ../output_files/test/bin/FxHttpServerTest --gtest_filter=FxHttpServerTest.Demo
TEST(FxHttpServerTest, Demo)
{
CreateLogModule();
ILogInit(LOG_INSTANCE_TYPE_END);
FxHttpServerInit();
std::this_thread::sleep_for(std::chrono::milliseconds(1000 * 10));
FxHttpServerUnInit();
ILogUnInit();
}
} // namespace FxHttpServerTest

View File

@ -15,10 +15,6 @@ link_directories(
${EXTERNAL_LIBS_OUTPUT_PATH} ${EXTERNAL_LIBS_OUTPUT_PATH}
) )
aux_source_directory(. SRC_FILES) aux_source_directory(. SRC_FILES)
aux_source_directory(./src SRC_FILES) aux_source_directory(./src SRC_FILES)

View File

@ -12,4 +12,5 @@ add_subdirectory(McuProtocol)
add_subdirectory(ModBusCRC16) add_subdirectory(ModBusCRC16)
add_subdirectory(LedControl) add_subdirectory(LedControl)
add_subdirectory(KeyControl) add_subdirectory(KeyControl)
add_subdirectory(MediaAdapter) add_subdirectory(MediaAdapter)
add_subdirectory(FxHttpServer)

View File

@ -0,0 +1,65 @@
include(${CMAKE_SOURCE_DIR_IPCSDK}/build/global_config.cmake)
include(${EXTERNAL_SOURCE_PATH}/goahead-5.2.0/goahead.cmake)
set(EXECUTABLE_OUTPUT_PATH ${EXEC_OUTPUT_PATH})
set(LIBRARY_OUTPUT_PATH ${LIBS_OUTPUT_PATH})
include_directories(
./src
./include
${UTILS_SOURCE_PATH}/StatusCode/include
${UTILS_SOURCE_PATH}/Log/include
${EXTERNAL_SOURCE_PATH}/httpserver.h-master
# ${UTILS_SOURCE_PATH}/LinuxApi/include
)
# link_directories(
# ${EXTERNAL_SOURCE_PATH}/libconfig/libconfig-1.7.3/lib/.libs
# )
include(CheckFunctionExists)
check_function_exists(epoll_wait EPOLL)
check_function_exists(kqueue KQUEUE)
aux_source_directory(./src SRC_FILES)
set(TARGET_NAME FxHttpServer)
add_library(${TARGET_NAME} STATIC ${SRC_FILES})
target_link_libraries(${TARGET_NAME} StatusCode Log -Wl,--start-group httpsrv -Wl,--end-group)
if ("${CLANG_TIDY_SUPPORT}" MATCHES "true")
add_custom_target(
FxHttpServer_code_check
COMMAND ${CLANG_TIDY_EXE}
-checks='${CLANG_TIDY_CHECKS}'
--header-filter=.*
--system-headers=false
${SRC_FILES}
${CLANG_TIDY_CONFIG}
-p ${PLATFORM_PATH}/cmake-shell
WORKING_DIRECTORY ${UTILS_SOURCE_PATH}/FxHttpServer
)
file(GLOB_RECURSE HEADER_FILES *.h)
add_custom_target(
FxHttpServer_code_format
COMMAND ${CLANG_FORMAT_EXE}
-style=file
-i ${SRC_FILES} ${HEADER_FILES}
WORKING_DIRECTORY ${UTILS_SOURCE_PATH}/FxHttpServer
)
add_custom_command(
TARGET ${TARGET_NAME}
PRE_BUILD
COMMAND make FxHttpServer_code_check
COMMAND make FxHttpServer_code_format
WORKING_DIRECTORY ${PLATFORM_PATH}/cmake-shell/
)
endif()
if(KQUEUE)
target_compile_definitions(${TARGET_NAME} PRIVATE KQUEUE)
endif()
if(EPOLL)
target_compile_definitions(${TARGET_NAME} PRIVATE EPOLL)
endif()
define_file_name(${TARGET_NAME})

View File

@ -0,0 +1,24 @@
# 1. httpserver组件库
&emsp;&emsp; 使用<sdk>/external/httpserver.h-master/src/CMakeLists.txt文件编译的开源库二次封装接口给到应用层使用。
## 1.1. 注意事项
1. 编译libhttpsrv时可能会提示 ragel 工具未安装,需要安装 ragel 工具:
```
$ sudo apt install ragel
```
2. 对开源库的CMakeLists.txt文件增加拷贝命令
```
message("${PLATFORM_PATH}/cmake-shell/external${SUBMODULE_PATH_OF_IPC_SDK}/httpserver.h-master/src/libhttpsrv.a")
add_custom_command(
TARGET httpsrv
POST_BUILD
COMMAND cp ${PLATFORM_PATH}/cmake-shell${SUBMODULE_PATH_OF_IPC_SDK}/external/httpserver.h-master/src/libhttpsrv.a ${EXTERNAL_LIBS_OUTPUT_PATH}
WORKING_DIRECTORY ${PLATFORM_PATH}/cmake-shell/
)
```
3. 取消开源库的CMakeLists.txt文件debug配置未知会产生什么不良后果
```
PUBLIC $<$<CONFIG:DEBUG>:-fsanitize=address -fsanitize=undefined -fno-sanitize-recover=all>
```
4.

View File

@ -0,0 +1,26 @@
/*
* Copyright (c) 2023 Fancy Code.
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef FX_HTTP_SERVER_H
#define FX_HTTP_SERVER_H
#include "StatusCode.h"
#ifdef __cplusplus
extern "C" {
#endif
StatusCode FxHttpServerInit(void);
StatusCode FxHttpServerUnInit(void);
#ifdef __cplusplus
}
#endif
#endif

View File

@ -0,0 +1,54 @@
/*
* Copyright (c) 2023 Fancy Code.
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "FxHttpServer.h"
#include "ILog.h"
#include "httpserver.h"
#include <string.h>
static struct http_server_s *server = NULL;
static struct http_server_s *poll_server = NULL;
static int request_target_is(struct http_request_s *request, char const *target)
{
http_string_t url = http_request_target(request);
LogInfo("sssssssssssssssssssssss url.buf = %s\n", url.buf);
int len = strlen(target);
return len == url.len && memcmp(url.buf, target, url.len) == 0;
}
static void handle_request(struct http_request_s *request)
{
http_request_connection(request, HTTP_AUTOMATIC);
struct http_response_s *response = http_response_init();
http_response_status(response, 200);
if (request_target_is(request, "/set")) {
LogInfo("======================================== set\n");
}
http_response_header(response, "Content-Type", "text/plain");
http_response_body(response, "RESPONSE", sizeof("RESPONSE") - 1);
http_respond(request, response);
}
StatusCode FxHttpServerInit(void)
{
server = http_server_init(8080, handle_request);
poll_server = http_server_init(8081, handle_request);
http_server_listen_poll(poll_server);
http_server_listen(server);
return CreateStatusCode(STATUS_CODE_OK);
}
StatusCode FxHttpServerUnInit(void)
{
free(server);
free(poll_server);
return CreateStatusCode(STATUS_CODE_OK);
}

View File

@ -18,7 +18,13 @@
#ifdef __cplusplus #ifdef __cplusplus
extern "C" { extern "C" {
#endif #endif
void *CreateMediaAdapter(void); enum SENSOR_NUM
{
SENSOR_NUM_1 = 0,
SENSOR_NUM_2,
SENSOR_NUM_END
};
void *CreateMediaAdapter(const SENSOR_NUM num);
void TestApi(void *object); void TestApi(void *object);
void IMediaAdapterFree(void *object); void IMediaAdapterFree(void *object);
#ifdef __cplusplus #ifdef __cplusplus

View File

@ -17,7 +17,7 @@
#include <string.h> #include <string.h>
static const char *MEDIA_ADAPTER_NAME = "media_adapter"; static const char *MEDIA_ADAPTER_NAME = "media_adapter";
const char inline *GetMediaAdapterModuleName(void) { return MEDIA_ADAPTER_NAME; } const char inline *GetMediaAdapterModuleName(void) { return MEDIA_ADAPTER_NAME; }
std::shared_ptr<IMediaAdapter> *NewIMediaAdapter(void) std::shared_ptr<IMediaAdapter> *NewIMediaAdapter(const SENSOR_NUM &num)
{ {
LogInfo("Create the uart device object.\n"); LogInfo("Create the uart device object.\n");
MeidaAdapter *impl = (MeidaAdapter *)malloc(sizeof(MeidaAdapter)); MeidaAdapter *impl = (MeidaAdapter *)malloc(sizeof(MeidaAdapter));

View File

@ -14,6 +14,7 @@
*/ */
#ifndef I_MEDIA_ADAPTER_H #ifndef I_MEDIA_ADAPTER_H
#define I_MEDIA_ADAPTER_H #define I_MEDIA_ADAPTER_H
#include "MediaAdapter.h"
#include <memory> #include <memory>
class IMediaAdapter class IMediaAdapter
{ {
@ -32,5 +33,5 @@ typedef struct media_adapter
std::shared_ptr<IMediaAdapter> mIMediaAdapter; std::shared_ptr<IMediaAdapter> mIMediaAdapter;
} MeidaAdapter; } MeidaAdapter;
const char *GetMediaAdapterModuleName(void); const char *GetMediaAdapterModuleName(void);
std::shared_ptr<IMediaAdapter> *NewIMediaAdapter(void); std::shared_ptr<IMediaAdapter> *NewIMediaAdapter(const SENSOR_NUM &num);
#endif #endif

View File

@ -15,7 +15,7 @@
#include "MediaAdapter.h" #include "MediaAdapter.h"
#include "ILog.h" #include "ILog.h"
#include "IMediaAdapter.h" #include "IMediaAdapter.h"
void *CreateMediaAdapter(void) { return NewIMediaAdapter(); } void *CreateMediaAdapter(const SENSOR_NUM num) { return NewIMediaAdapter(num); }
static bool ObjectCheck(void *object) static bool ObjectCheck(void *object)
{ {
if (nullptr == object) { if (nullptr == object) {