Consolexin's blog Consolexin's blog
首页
  • 算法基础

    • 图论
    • 字符串
    • 动态规划
    • 二分
    • 滑动窗口
    • 排序
  • Project

    • CppServer
  • 相关书籍

    • 现代C++编程
  • 书籍

    • SQL必知必会
    • MySQL必知必会
分类
标签
归档
GitHub (opens new window)

Consolexinhun

小学生
首页
  • 算法基础

    • 图论
    • 字符串
    • 动态规划
    • 二分
    • 滑动窗口
    • 排序
  • Project

    • CppServer
  • 相关书籍

    • 现代C++编程
  • 书籍

    • SQL必知必会
    • MySQL必知必会
分类
标签
归档
GitHub (opens new window)
  • README
  • day01-从一个最简单的socket开始
  • day02-不要放过任何一个错误
  • day03-高并发还得用epoll
  • day04-来看看我们的第一个类
  • day05-epoll高级用法-Channel登场
  • day06-服务器与事件驱动核心类登场
  • day07-为我们的服务器添加一个Acceptor
  • day08-一切皆是类,连TCP连接也不例外
  • day09-缓冲区-大作用
  • day10-加入线程池到服务器
  • day11-完善线程池,加入一个简单的测试程序
  • day12-将服务器改写为主从Reactor多线程模式
  • day13-支持业务逻辑自定义、完善Connection类
  • day14-重构核心库、使用智能指针
  • day15-重构Connection、修改生命周期
  • day16-使用CMake工程化
  • day17-使用EventLoopThreadPool、移交EventLoop
  • day18-HTTP有限状态转换机
  • day19-创建HTTP响应,实现HTTP服务器
  • day20-定时器的创建使用
  • day21-服务器主动关闭连接
  • day22-初步涉及日志库,定义自己的输出流LogStream
  • day23-定义前端日志库,实现同步输出
  • day24-异步日志库
  • day25-更有效的缓冲区
  • day26-监听写事件
  • day27-处理静态文件,实现POST请求
  • day28-文件服务器的简单实现,文件的展示和下载
  • day29-文件的上传
  • day30-WebBench的测试
  • CppServer
consolexinhun
2025-04-20

day19-创建HTTP响应,实现HTTP服务器

# day19-创建HTTP响应,实现HTTP服务器.md

与HTTP请求相似,HTTP响应也存在四个部分分别是状态行,响应头,空行和响应正文

具体的

HTTP/版本 状态码 状态描述符
响应头
空行
响应体
1
2
3
4

举例说明

HTTP\1.1 200 OK\r\n
Content-Encoding: gzip\r\n
Content-Type: text/html\r\n
Content-Length: 5\r\n
\r\n
hello
1
2
3
4
5
6

因此我们只需要根据这个格式创建我们的响应即可,因此创建HttpResponse,具体的其保存有响应的信息

class HttpResponse{
    public:
        HttpResponse(bool close_connection);
        ~HttpResponse();
        void SetStatusCode(HttpStatusCode status_code); // 设置回应码
        void SetStatusMessage(const std::string &status_message);
        void SetCloseConnection(bool close_connection);
        void SetContentType(const std::string &content_type); 
        void AddHeader(const std::string &key, const std::string &value); // 设置回应头
        void SetBody(const std::string &body);
        std::string message(); 
        bool IsCloseConnection();
    private:
        std::map<std::string, std::string> headers_;

        HttpStatusCode status_code_;
        std::string status_message_;
        std::string body_;
        bool close_connection_;
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

为了将这些信息整合,以发送至客户端,简单的创建一个string来保存这些信息,以备后续使用

std::string HttpResponse::message(){
    std::string message;
    message += ("HTTP/1.1 " +
                std::to_string(status_code_) + " " +
                status_message_ + "\r\n"
    );
    if(close_connection_){
        message += ("Connection: close\r\n");
    }else{
        message += ("Content-Length: " + std::to_string(body_.size()) + "\r\n");
        message += ("Connection: Keep-Alive\r\n");
    }

    for (const auto&header : headers_){
        message += (header.first + ": " + header.second + "\r\n");
    }

    message += "\r\n";
    message += body_;

    return message;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

自此,我们的HTTP请求和HTTP响应都已经创建完成。为了在建立连接时,能够对HTTP请求进行解析,我们在TcpConnection创建一个HttpContext,这样,当TcpConnection的读缓冲区有信息时,可以将内部消息放到HttpContext中进行解析。为了便于实现,在此处实现了每个TcpConnection都有一个独有的HttpContext,并使用智能指针进行管理。

// TcpConnection.h
class TcpConnection : public std::enable_shared_from_this<TcpConnection>
{
public:
    HttpContext *context() const;
private:
    std::unique_ptr<HttpContext> context_;
}


//TcpConnection.cpp

TcpConnection::TcpConnection(EventLoop *loop, int connfd){
    //...
    context_ = std::make_unique<HttpContext>();
}
HttpContext * TcpConnection::context() const{
    return context_.get();
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

随后,我们使用类似于EchoServer的方法来创建HttpServer,具体的通过设置回调函数的方法,当我们的TcpConnection接收到消息时,将读缓冲区中的数据送入到HttpContext中解析并构建响应的HTTP响应报文,返回给客户端。

具体的,我们创建HttpServer类,该类包含一个TcpServer,在创建HttpServer时创建TcpServer,并传入响应的回调函数,从而间接为为TcpConnection创建回调

HttpServer::HttpServer(const char *ip, const int port)  {
    server_ = std::make_unique<TcpServer>(ip, port);
    server_->set_connection_callback(
        std::bind(&HttpServer::onConnection, this, std::placeholders::_1));

    server_->set_message_callback(
        std::bind(&HttpServer::onMessage, this, std::placeholders::_1)
    );
};
1
2
3
4
5
6
7
8
9

我们的TcpConnection处理可读信息时,会首先获得自身的Context解析器,并尝试解析read_buf中的信息,如果解析失败,服务器主动断开连接,否则则对请求报文进行处理并创建响应报文返回。

void HttpServer::onMessage(const TcpConnectionPtr &conn){
    HttpContext *context = conn->context();
    if (!context->ParaseRequest(conn->read_buf()->c_str(), conn->read_buf()->Size()))
    {
        conn->Send("HTTP/1.1 400 Bad Request\r\n\r\n");
        conn->OnClose();
    }

    if (context->GetCompleteRequest())
    {
        onRequest(conn, *context->request());
        context->ResetContextStatus();
    }
}

void HttpServer::onRequest(const TcpConnectionPtr &conn, const HttpRequest &request){
    std::string connection_state = request.GetHeader("Connection");
    bool close = (connection_state == "Close" ||
                  request.version() == HttpRequest::Version::kHttp10 &&
                      connection_state != "keep-alive");
    HttpResponse response(close);
    response_callback_(request, &response);

    conn->Send(response.message().c_str());
    if(response.IsCloseConnection()){
        conn->OnClose();
    }
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29

这样,我们只需要在业务层定义response_callback即可。我们以get请求做做了相应的测试

const std::string html = " <font color=\"red\">This is html!</font> ";
void HttpResponseCallback(const HttpRequest &request, HttpResponse *response)
{
    if(request.method() != HttpRequest::Method::kGet){
        response->SetStatusCode(HttpResponse::HttpStatusCode::k400BadRequest);
        response->SetStatusMessage("Bad Request");
        response->SetCloseConnection(true);
    }

    {
        std::string url = request.url();
        std::cout << url << std::endl;
        if(url == "/"){
            response->SetStatusCode(HttpResponse::HttpStatusCode::k200K);
            response->SetBody(html);
            response->SetContentType("text/html");
        }else if(url == "/hello"){
            response->SetStatusCode(HttpResponse::HttpStatusCode::k200K);
            response->SetBody("hello world\n");
            response->SetContentType("text/plain");
        }else{
            response->SetStatusCode(HttpResponse::HttpStatusCode::k404NotFound);
            response->SetStatusMessage("Not Found");
            response->SetBody("Sorry Not Found\n");
            response->SetCloseConnection(true);
        }
    }
    return;
}

int main(){
    HttpServer *server = new HttpServer("127.0.0.1", 1234);
    server->SetHttpCallback(HttpResponseCallback);
    server->start();
    return 0;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36

通过运行make http_server获得响应的可执行文件,然后运行可执行文件,并在任意浏览器中键入http://127.0.0.1:1234/hello或http://127.0.0.1:1234或其他路径查看服务器的返回情况。

编辑 (opens new window)
上次更新: 2025/05/21, 06:42:57
day18-HTTP有限状态转换机
day20-定时器的创建使用

← day18-HTTP有限状态转换机 day20-定时器的创建使用→

最近更新
01
6-其他操作
05-20
02
4-联结
05-20
03
7-管理
05-20
更多文章>
Theme by Vdoing | Copyright © 2019-2025 Consolexinhun | MIT License
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式
×