libhv教程10--创建一个简单的HTTP服务端

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);会阻塞当前线程运行,所以routerserver对象不会被析构,如使用http_server_run(&server, 0);内部会另起线程运行,不会阻塞当前线程,此时需要注意routerserver的生命周期,不要定义为局部变量被析构了,需定义为类成员变量或者全局变量

编译运行:

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

压力测试

使用apacheab、或者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
评论 31
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

ithewei

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值