【免费下载链接】IncludeOS
A minimal, resource efficient unikernel for cloud services
项目地址:
https://gitcode.com/gh_mirrors/in/IncludeOS
点击查看 免费下载
导读
本文围绕 test/integration/net/http/README.md 所描述的 HTTP 集成测试展开,深入讲解 IncludeOS 如何在 unikernel 内部同时启动 http::Server 与 http::Basic_client,并借助宿主机上的 Python HTTP 服务器与 vmrunner 完成双向验证。读完本文,你将掌握:IncludeOS HTTP 服务的构建与网络配置方式、服务端请求回调与响应写入的 API 用法、客户端请求构造与错误处理机制,以及整个集成测试从启动 VM 到断言输出的完整运行链路。
原文档核心信息仅有两句话:在本地服务器上测试 HTTP Client,以及 出站(outgoing)测试需要正确的网络配置。本文以这两点为骨架,结合 test/integration/net/http/ 目录下的全部测试文件与 api/net/http/、src/net/http/ 的源码实现,展开为一份可运行的实战指南。
一、测试全景:一个 VM 内同时扮演客户端与服务器
test/integration/net/http/ 目录下共有 6 个文件,构成了完整的测试体系:
| service.cpp | unikernel 服务主体:配置网络、启动 HTTP 服务器、构造 HTTP 客户端并发送请求 |
| test.py | 宿主端 Python 驱动:在 9011 端口起一个回显服务器,并用 vmrunner 启动/断言 VM |
| run.sh | 启动脚本:创建桥接网络后运行 VM 镜像 |
| CMakeLists.txt | 构建配置:生成名为 net_http 的可执行镜像,并挂载 virtionet 网卡驱动 |
| vm.json | VM 配置:指定 virtio 网卡、MAC 地址与内存大小 |
| README.md | 本文所对应的原始测试说明 |
测试的核心思路是:IncludeOS 服务在 VM 内监听 8080 端口扮演 HTTP 服务器;宿主机通过 Python HTTP 服务器在 9011 端口扮演外部服务器;VM 内的 HTTP 客户端向宿主机发起出站请求。这恰好覆盖了服务端与客户端两个方向,也是原文档强调"出站测试需要正确网络配置"的原因——VM 内的客户端要访问宿主机的 9011 端口,必须先打通桥接网络。
从 test/README.md 可以看到,本测试固定占用 10.0.0.46 这一 IP 地址,与其他网络测试(如 tcp 用 10.0.0.44、dns 用 10.0.0.48)错开,避免并行运行时发生地址冲突。
二、构建配置:如何把一个 HTTP 服务编译成镜像
CMakeLists.txt 展示了 IncludeOS 服务标准的三段式构建写法:
cmake_minimum_required(VERSION 3.31.6)
set(CMAKE_TRY_COMPILE_TARGET_TYPE STATIC_LIBRARY)
project (service)
include(os)
set(SOURCES
service.cpp
)
os_add_executable(net_http "HTTP test" ${SOURCES})
os_add_drivers(net_http virtionet)
os_add_stdout(net_http default_stdout)
configure_file(test.py ${CMAKE_CURRENT_BINARY_DIR})
要点解读:
- os_add_executable(net_http "HTTP test" ${SOURCES}):把 service.cpp 编译链接为名为 net_http 的 unikernel 镜像,输出二进制为 net_http.elf.bin(这正是 test.py 末尾 vm.boot(20, image_name='net_http.elf.bin') 所引用的镜像名)。
- os_add_drivers(net_http virtionet):挂载 virtio 网络驱动,使 VM 能够使用 vm.json 中声明的 virtio 网卡。
- os_add_stdout(net_http default_stdout):将标准输出绑定到默认 stdout 驱动,确保 INFO、printf 打印的内容能够被 vmrunner 捕获,用于后续的输出匹配触发。
- configure_file(test.py …):把测试驱动脚本复制到构建目录,方便与镜像放在一起执行。
对应的 VM 资源配置见 vm.json:
{
"net" : [{"device" : "virtio", "mac" : "c0:01:0a:00:00:2a"}],
"mem" : 256
}
这里声明了一块 virtio 网卡(MAC 为 c0:01:0a:00:00:2a,属于桥接网段 10.0.0.x 对应的地址规划)与 256 MB 内存,为后续网络交互提供硬件基础。
三、服务端代码剖析:10 行回调搭起一个 HTTP 服务器
service.cpp 在 Service::ready() 中完成了全部测试逻辑。首先为接口 0 配置静态网络:
auto& inet = net::Interfaces::get(0);
inet.network_config(
{ 10, 0, 0, 46 }, // IP
{ 255,255,255, 0 }, // Netmask
{ 10, 0, 0, 1 }, // Gateway
{ 8, 8, 8, 8 } // DNS
);
- IP 10.0.0.46 与 test/README.md 中的地址分配表一致;
- 网关 10.0.0.1 是桥接网络中宿主机的地址——客户端后续就向这个地址的 9011 端口发请求;
- DNS 8.8.8.8 用于域名解析(测试中虽未真正出站访问外网,但地址仍被配置)。
3.1 服务器:on_request 回调 + Response_writer
随后创建 http::Server 并注册请求处理回调:
server_ = std::make_unique<Server>(inet.tcp());
server_->on_request([] (Request_ptr req, Response_writer_ptr writer)
{
printf("Received request:\\n%s\\n", req->to_string().c_str());
// set content type
writer->header().set_field(header::Content_Type, "text/plain");
// write body
writer->write("Hello");
});
server_->listen(8080);
这段代码对应 server.hpp 中定义的 API:
- on_request(Request_handler handler):注册请求回调。回调签名是 delegate<void(Request_ptr, Response_writer_ptr)>,当解析到完整请求时被触发(见 server.cpp 的 Server::receive)。
- listen(const uint16_t port):绑定端口开始监听。从 server.cpp 的实现可见,listen 会先断言 on_request_ 已设置("You must set 'on_request' on the server to receive requests!"),再调用 bind(port) 通过 tcp_.listen(port, {this, &Server::on_connect}) 注册连接事件,并打印 "Listening on port %u on CPU %d"——这正是 test.py 中 vm.on_output("Listening on port 8080", …) 用来触发宿主端断言的关键日志。
- Response_writer 是响应写入的辅助对象(response_writer.hpp):header().set_field() 设置响应头字段,write("Hello") 写入响应体;析构或 end() 时自动完成报文发送。
3.2 服务器底层:连接复用、空闲超时与统计
服务器并非一个简单的回调壳。从 server.cpp 的实现可以读到更多细节:
- 连接管理:connect() 优先复用 free_idx_ 中空闲的槽位,否则新建 Server_connection,并用 connected_clients() 统计在线连接数。
- 空闲超时:构造时可通过第三个参数设置 idle_duration(默认 DEFAULT_IDLE_TIMEOUT 为 60 秒,见 server.cpp);listen() 中若超时不为 0,会注册 Timers::periodic(30s, 1min, …) 周期性地通过 timeout_clients() 关闭超时连接。
- 请求解析:Server_connection::recv_request(见 server_connection.cpp)把 TCP 流数据累积成 Request,若请求头带有 Content-Length 则按长度判定请求完成,否则立即结束请求;解析出错时以 Bad_Request 状态码响应。
- 统计指标:构造函数通过 Statman 注册了 http_server.connections、http_server.requests_rx、http_server.requests_bad、http_server.timeouts 四个统计项,可在运行期通过 statman 查询服务器健康状态。
四、客户端代码剖析:Basic_client 的请求构造与 HTTPS 限制
4.1 发送前回调:观察将要发出的请求
client_ = std::make_unique<Basic_client>(inet.tcp());
client_->on_send([] (Request& req, Basic_client::Options& opt, const Basic_client::Host host)
{
printf("Sending request:\\n%s\\n", req.to_string().c_str());
});
on_send 回调在请求真正写入 TCP 流之前被调用(见 basic_client.cpp 中 send() 内 if(on_send_) on_send_(*req, options, host);),适合调试或修改自动生成的请求。
4.2 HTTPS 不支持:期望抛出异常
INFO("Basic_client", "HTTPS");
try
{
client_->get("https://www.google.com", {}, [](Error err, Response_ptr res, Connection&) {});
assert(false && "Basic Client should throw exception");
}
catch(const http::Client_error& err)
{
CHECKSERT(true, "Basic Client should throw exception on https URL");
}
这是一个很有意思的负向测试:Basic_client 构造函数传入的 https_supported 标志为 false(见 basic_client.cpp),因此当 URL 的 scheme 为 https 时,validate_secure() 会抛出 Client_error{"Secured connections not supported (use the HTTPS Client)."}(见 basic_client.hpp)。CHECKSERT 宏(定义于 api/info,即 CHECK(…) + assert(…))在打印 [x] 通过标记的同时执行断言,确认异常确实被抛出。
若需要 HTTPS 客户端,仓库提供了继承自 Basic_client 的 http::Client(见 client.cpp),它在 get_secure_connection() 中通过 openssl::TLS_stream 建立 TLS 连接。测试这里刻意验证的是"普通客户端必须拒绝 https",属于 API 契约测试。
4.3 正向往本地服务器发起请求
auto req = client_->create_request();
req->set_uri(uri::URI{"/testing"});
client_->send(std::move(req), {inet.gateway(), 9011},
[] (Error err, Response_ptr res, Connection&)
{
if (err)
printf("Error: %s \\n", err.to_string().c_str());
CHECKSERT(!err, "No error");
printf("Received body: %.*s\\n", static_cast<int>(res->body().length()), res->body().data());
CHECKSERT(res->body() == "/testing", "Received body: \\"/testing\\"");
printf("SUCCESS\\n");
});
这一段的完整调用链体现了 Basic_client 的设计:
4.4 客户端底层:连接复用、超时与重定向
从 client_connection.cpp 可以看到客户端连接的核心机制:
- 连接复用:Basic_client 内部以 Connection_mapset = std::map<Host, Connection_set> 按目标主机缓存连接;get_connection() 优先复用未占用的空闲连接,否则新建 TCP 连接(见 basic_client.cpp)。连接的可用性由 available()(on_response_ == nullptr && keep_alive_)决定,而 keep_alive_ 取决于请求头 Connection 是否为 close。
- 响应接收:recv_response 把 TCP 分片逐步拼装成 Response,按 Content-Length 判断报文是否完整;对于 1xx/204/304 以及 HEAD 请求等"无实体"响应,收到空行即结束。
- 超时控制:Options.timeout 默认 DEFAULT_TIMEOUT 为 5 秒(见 basic_client.cpp),超时后回调收到 Error::TIMEOUT。
- 重定向:Options.follow_redirect 默认 0(不跟随);若大于 0,收到 3xx 且带 Location 的响应时,Client_connection::redirect 会更新 URI 并重新发送请求(见 client_connection.cpp)。
- 关闭语义:连接被对端关闭时,若已收到完整响应头则回调 Error::NONE,否则回调 Error::CLOSING。
五、宿主端测试驱动:Python 回显服务器与 vmrunner
test.py 是运行在宿主 Linux 上的 Python 3 脚本,它同时承担两个角色。
5.1 角色一:9011 端口的回显 HTTP 服务器
HOST = ''
PORT = 9011
class RequestHandler(http.server.BaseHTTPRequestHandler):
def do_GET(s):
s.send_response(200)
s.send_header("Content-type", "text/plain; charset=utf-8",)
s.end_headers()
s.wfile.write(s.path.encode("utf-8"))
def Client_test():
server_class = http.server.HTTPServer
httpd = server_class((HOST, PORT), RequestHandler)
global DO_SERVE
while(DO_SERVE):
httpd.handle_request()
DO_SERVE = False
httpd.server_close()
# Start web server in a separate thread
_thread.start_new_thread(Client_test, ())
这段逻辑设计得非常精妙:宿主机在 10.0.0.1:9011 起一个只处理一次请求的 HTTP 服务器,do_GET 直接把请求路径作为响应体返回(请求 /testing 就回 testing)。VM 内客户端恰好发送 URI 为 /testing 的请求到 {inet.gateway(), 9011}——网关就是宿主机——因此客户端回调断言 res->body() == "/testing" 时,实际上同时验证了桥接网络连通性 + TCP 连接 + HTTP 请求构造 + 响应解析四层链路。这里就是原文档"Outgoing test requires correct network configuration"所指的环节:若宿主机与 VM 之间没有正确桥接(run.sh 中 create_bridge.sh 的职责),请求将永远到达不了 9011 端口。
5.2 角色二:vmrunner 驱动的断言脚本
def Server_test(triggerline):
res = urllib.request.urlopen("http://10.0.0.46:8080").read()
assert(res.decode('utf-8') == "Hello")
# Get an auto-created VM from the vmrunner
vm = vmrunner.vms[0]
# Add custom event for testing server
vm.on_output("Listening on port 8080", Server_test)
if len(sys.argv) > 1:
vm.boot(image_name=str(sys.argv[1]))
else:
# Boot the VM, taking a timeout as parameter
vm.boot(20,image_name='net_http.elf.bin')
Server_test 在 VM 输出 Listening on port 8080 的那一刻触发,用标准库 urllib 向 VM 的 8080 端口发起 GET,断言响应体为 Hello——与 service.cpp 中 writer->write("Hello") 一一对应。这验证了服务端方向:外部客户端能访问到 unikernel 内的 HTTP 服务器,并收到正确的响应。
vm.boot(20, …) 表示以 20 秒超时启动 VM(若命令行传入镜像参数则以参数为准),整个测试脚本最终通过 exit 状态码向 CI 汇报成败。
六、运行方式与出站网络配置
run.sh 提供了标准启动流程:
#! /bin/bash
set –
INCLUDEOS_PREFIX=${INCLUDEOS_PREFIX-/usr/local}
sudo $INCLUDEOS_PREFIX/includeos/scripts/create_bridge.sh
FILE=$1
shift
source $INCLUDEOS_PREFIX/includeos/scripts/run.sh $FILE ${*}
执行步骤:
6.1 两种测试方向的网络要求
| 服务端测试(宿主机访问 VM:8080) | VM 与宿主机桥接互通即可,无需外网 | urllib 连接超时,assert 失败 |
| 客户端出站测试(VM 访问宿主机:9011) | 桥接互通 + 宿主机 9011 端口可被 VM 访问 | 客户端回调收到错误,CHECKSERT(!err) 失败 |
原文档的"出站测试需要正确的网络配置"正是针对第二行:VM 内的 TCP 连接要到达宿主机监听端口,除桥接外还要求宿主机侧防火墙/路由允许该流量;若要访问外网(如某些测试访问真实公网服务),还需配置 NAT 或端口转发规则。本测试用"宿主机回显服务器"替代外网目标,把外部依赖降到最低,这是它适合作为 CI 回归测试的重要原因。
七、测试预期输出与结果判定
整个测试成功时,日志链路大致如下(按时间顺序):
[ HTTP Server ] Listening on port 8080 on CPU 0 ← VM 服务端就绪,触发 Server_test
[ Basic_client ] Testing against local server ← 客户端开始出站请求
Sending request: ← on_send 回调打印
GET /testing HTTP/1.1
Host: 10.0.0.1:9011
…
Received body: /testing ← 回调收到回显
SUCCESS ← 客户端测试通过
若某个环节失败:
- CHECKSERT(!err, "No error") 失败说明出站请求出错,可结合 Error::to_string() 定位(Unable to resolve host / No reply / Request timed out 等);
- CHECKSERT(res->body() == "/testing", …) 失败说明响应内容异常,通常是宿主机回显服务器行为与预期不符;
- 服务端方向的 assert(res.decode('utf-8') == "Hello") 失败说明 8080 端口响应体错误。
由于 CHECKSERT 宏在打印 [ ] 失败标记后会触发 assert(见 api/info),VM 会中止运行,vmrunner 随之判定测试失败——这正是集成测试"断言即哨兵"的设计思路。
八、小结:从测试文档到源码契约
尽管原 README 只有寥寥数语,但 test/integration/net/http/ 目录实际上浓缩了 IncludeOS HTTP 栈最重要的两条使用路径:
- 服务端:Server + on_request 回调 + Response_writer,配合连接复用、空闲超时与 Statman 统计,见 server.hpp 与 server.cpp;
- 客户端:Basic_client 的请求构造、连接缓存、超时/重定向与错误模型,以及"不支持 HTTPS 必须抛异常"的 API 契约,见 basic_client.hpp 与 basic_client.cpp。
对想要在 IncludeOS 上开发 HTTP 服务的开发者而言,这套测试就是现成的"最小可运行范例":照抄 service.cpp 的服务器三段式(创建 Server → 注册回调 → listen),再配合 CMakeLists.txt 的构建脚手架,即可快速产出自己的 HTTP 服务镜像;而客户端的 create_request / get / post / send / on_send 组合,则覆盖了在 unikernel 内主动发起 HTTP 请求的全部常见场景。运行测试前,务必像原文档提醒的那样确认桥接与出站网络配置正确——这是整套链路能够跑通的前提。
赞
【免费下载链接】IncludeOS
A minimal, resource efficient unikernel for cloud services
项目地址:
https://gitcode.com/gh_mirrors/in/IncludeOS
点击查看 免费下载
相关推荐
3分钟掌握DeepSeek-V4-Flash-DSpark编码技巧:从消息格式到工具调用全攻略
Sequin开发指南:如何为这个ANSI解析工具贡献代码
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
网硕互联帮助中心




评论前必须登录!
注册