HTTP协议作为本世纪最通用的应用层协议,本文就不加以介绍了,不熟悉的自行阅读awesome-http
简单的HTTP服务端示例
示例代码参考examples/http_server_test.cpp
#include "hv/HttpServer.h"
int main() {
HttpService router;
// curl -v http://ip:port/
router.Static("/", "./html");
// curl -v http://ip:port/proxy/get
router.Proxy("/proxy/", "http://httpbin.org/");
// curl -v http://ip:port/ping
router.GET("/ping", [](HttpRequest* req, HttpResponse* resp) {
return resp->String("pong");
});
// curl -v http://ip:port/data
router.GET("/data", [](HttpRequest* req, HttpResponse* resp) {
static char data[] = "0123456789";
return resp->Data(data, 10 /*, false */);
});
// curl -v http://ip:port/paths
router.GET("/paths", [&router](HttpRequest* req, HttpResponse* resp) {
return resp->Json(router.Paths());
});
// curl -v http://ip:port/get?env=1
router.GET("/get", [](HttpRequest* req, HttpResponse* resp) {
resp->json["origin"] = req->client_addr.ip;
resp->json["url"] = req->url;
resp->json["args"] = req->query_params;
resp->json["headers"] = req->headers;
return 200;
});
// curl -v http://ip:port/echo -d "hello,world!"
router.POST("/echo", [](const HttpContextPtr& ctx) {
return ctx->send(ctx->body(), ctx->type());
});
// curl -v http://ip:port/user/123
router.GET("/user/{id}", [](const HttpContextPtr& ctx) {
hv::Json resp;
resp["id"] = ctx->param("id");
return ctx->send(resp.dump(2));
});
http_server_t server;
server.port = 8080;
server.service = &router;
http_server_run(&server);
return 0;
}
友情提示:
上面示例直接运行在main主线程,http_server_run(&server, 1);
会阻塞当前线程运行,所以router
和server
对象不会被析构,如使用http_server_run(&server, 0);
内部会另起线程运行,不会阻塞当前线程,此时需要注意router
和server
的生命周期,不要定义为局部变量
被析构了,需定义为类成员变量或者全局变量
。
编译运行:
c++ -std=c++11 examples/http_server_test.cpp -o bin/http_server_test -lhv
bin/http_server_test
测试使用curl
或浏览器输入以下url
:
curl -v http://127.0.0.1:8080/ping
curl -v http://127.0.0.1:8080/data
curl -v http://127.0.0.1:8080/paths
curl -v http://127.0.0.1:8080/echo -d "hello,world"
curl -v http://127.0.0.1:8080/user/123
完整的HTTP服务端示例
完整的http服务端示例代码参考examples/httpd
测试步骤:
git clone https://github.com/ithewei/libhv.git
cd libhv
make httpd curl
bin/httpd -h
bin/httpd -d
#bin/httpd -c etc/httpd.conf -s restart -d
ps aux | grep httpd
# http web service
bin/curl -v localhost:8080
# http indexof service
bin/curl -v localhost:8080/downloads/
# http api service
bin/curl -v localhost:8080/ping
bin/curl -v localhost:8080/echo -d "hello,world!"
bin/curl -v localhost:8080/query?page_no=1\&page_size=10
bin/curl -v localhost:8080/kv -H "Content-Type:application/x-www-form-urlencoded" -d 'user=admin&pswd=123456'
bin/curl -v localhost:8080/json -H "Content-Type:application/json" -d '{"user":"admin","pswd":"123456"}'
bin/curl -v localhost:8080/form -F "user=admin pswd=123456"
bin/curl -v localhost:8080/upload -F "file=@LICENSE"
bin/curl -v localhost:8080/test -H "Content-Type:application/x-www-form-urlencoded" -d 'bool=1&int=123&float=3.14&string=hello'
bin/curl -v localhost:8080/test -H "Content-Type:application/json" -d '{"bool":true,"int":123,"float":3.14,"string":"hello"}'
bin/curl -v localhost:8080/test -F 'bool=1 int=123 float=3.14 string=hello'
# RESTful API: /group/:group_name/user/:user_id
bin/curl -v -X DELETE localhost:8080/group/test/user/123
压力测试
使用apache
的ab
、或者wrk
都可以用来做压力测试,一般服务器单线程QPS
可轻松达到3w
# sudo apt install apache2-utils
ab -c 100 -n 100000 http://127.0.0.1:8080/
# sudo apt install wrk
wrk -c 100 -t 4 -d 10s http://127.0.0.1:8080/
更多HTTP压力测试工具参考awesome-http-benchmark
更多说明
server.worker_threads
:设置线程数server.worker_processes
:设置进程数(仅在linux系统下有用,windows下使用多线程)HttpService::Static
:设置静态资源目录HttpService::Proxy
:设置代理转发
// 同步handler: 适用于非阻塞型的快速响应
typedef std::function<int(HttpRequest* req, HttpResponse* resp)> http_sync_handler;
// 异步handler: 适用于耗时处理和响应
typedef std::function<void(const HttpRequestPtr& req, const HttpResponseWriterPtr& writer)> http_async_handler;
// 类似nodejs koa的ctx handler: 兼容以上两种handler的最新写法,可在回调里自己决定同步响应还是异步响应
typedef std::function<int(const HttpContextPtr& ctx)> http_ctx_handler;
因为历史兼容原因,同时保留支持以上三种格式的handler
,用户可根据自己的业务和接口耗时选择合适的handler
,如果使用的较新版libhv
,推荐使用带HttpContext
参数的http_ctx_handler
。
三种handler
的等同写法见:
// 同步handler: 回调函数运行在IO线程
router.POST("/echo", [](HttpRequest* req, HttpResponse* resp) {
resp->content_type = req->content_type;
resp->body = req->body;
return 200;
});
// 异步handler:回调函数运行在hv::async全局线程池
router.POST("/echo", [](const HttpRequestPtr& req, const HttpResponseWriterPtr& writer) {
writer->Begin();
writer->WriteStatus(HTTP_STATUS_OK);
writer->WriteHeader("Content-Type", req->GetHeader("Content-Type"));
writer->WriteBody(req->body);
writer->End();
});
// 带HttpContext参数的handler是兼容同步/异步handler的最新写法,推荐使用
// 回调函数运行在IO线程,可通过hv::async丢到全局线程池处理,或者自己的消费者线程/线程池
// HttpContext里包含了HttpRequest和HttpResponseWriter成员变量,参照nodejs koa提供了一系列操作HttpRequest和HttpResponse的成员函数,写法更加简洁
router.POST("/echo", [](const HttpContextPtr& ctx) {
return ctx->send(ctx->body(), ctx->type());
});
router.POST("/echo", [](const HttpContextPtr& ctx) {
// demo演示丢到hv::async全局线程池处理,实际使用推荐丢到自己的消费者线程/线程池
hv::async([ctx]() {
ctx->send(ctx->body(), ctx->type());
});
return 0;
});
附 HttpContext.h 接口中文注释
class HttpContext {
// 获取请求信息
// 获取客户端IP
std::string ip();
// 获取客户端端口
int port();
// 获取请求method
http_method method();
// 获取请求url
std::string url();
// 获取请求path
std::string path();
// 获取请求host
std::string host();
// 获取请求头部
const http_headers& headers();
std::string header(const char* key, const std::string& defvalue = hv::empty_string);
// 获取请求参数
const hv::QueryParams& params();
std::string param(const char* key, const std::string& defvalue = hv::empty_string);
// 获取请求cookie
const HttpCookie& cookie(const char* name);
// 获取请求 `Content-Length`
int length();
// 获取请求 `Content-Type`
http_content_type type();
// 判断请求 `Content-Type`
bool is(http_content_type content_type);
// 获取请求body
std::string& body();
// 获取 `application/json` 格式数据
const hv::Json& json();
// 获取 `multipart/form-data` 格式数据
const hv::MultiPart& form();
std::string form(const char* name, const std::string& defvalue = hv::empty_string);
// 获取 `application/x-www-urlencoded` 格式数据
const hv::KeyValue& urlencoded();
std::string urlencoded(const char* key, const std::string& defvalue = hv::empty_string);
// 根据 `Content-Type` 获取对应格式数据
template<typename T>
T get(const char* key, T defvalue = 0);
std::string get(const char* key, const std::string& defvalue = hv::empty_string);
// 设置响应信息
// 设置响应状态码
void setStatus(http_status status);
// 设置响应 `Content-Type`
void setContentType(http_content_type type);
// 设置响应头部
void setHeader(const char* key, const std::string& value);
// 设置响应cookie
void setCookie(const HttpCookie& cookie);
// 设置响应body
void setBody(const std::string& body);
template<typename T>
// 根据 `Content-Type` 设置对应格式数据
void set(const char* key, const T& value);
// 发送
int send();
int send(const std::string& str, http_content_type type = APPLICATION_JSON);
// 发送文本数据
int sendString(const std::string& str);
// 发送二进制数据
int sendData(void* data, int len, bool nocopy = true);
// 发送文件
int sendFile(const char* filepath);
// 发送json数据
template<typename T>
int sendJson(const T& t);
// 重定向
int redirect(const std::string& location, http_status status = HTTP_STATUS_FOUND);
// 主动关闭连接
int close();
};
Tips:
std::async
在不同c++运行库下有着不同的实现,有的是线程池,有的就是当场另起一个线程,而且返回值析构时也会阻塞等待,不推荐使用,可以使用hv::async
代替(需要#include “hasync.h”
),可以通过hv::async::startup
配置全局线程池的最小线程数、最大线程数、最大空闲时间,hv::async::cleanup
用于销毁全局线程池;- 关于是否需要丢到消费者线程处理请求的考量:在并发不高的场景,通过设置
worker_threads
起多线程就可以满足了,不能满足的(并发很高不能容忍阻塞后面请求、handler回调里耗时秒级以上)才考虑将HttpContextPtr
丢到消费者线程池处理; - 关于大文件的发送可以参考 examples/httpd 里的
largeFileHandler
,单独起线程循环读文件->发送
,但是要注意做好流量控制,因为磁盘IO总是快于网络IO的,或者对方接受过慢,都会导致发送数据积压在发送缓存里,耗费大量内存,示例里是通过判断WriteBody
返回值调整sleep
睡眠时间从而控制发送速度的,当然你也可以通过ctx->writer->fd()
获取到套接字,设置成阻塞来发;或者设置ctx->writer->onwrite
监听写完成事件统计写数据来决定是否继续发送;或者通过ctx->writer->writeBufsize()
获取当前写缓存积压字节数来决定是否继续发送; - 关于发送事先不知道长度的实时流数据,可以通过
chunked
方式,回调里基本流程是Begin -> EndHeaders("Transfer-Encoding", "chunked") -> WriteChunked -> WriteChunked -> ... -> End