1 设计
HttpResponse类用来,存放需要发送给客户端的数据.
这些数据是一些原始的数据.并没有合成完整的报文.
但是提供一个接口,可以填充传入的 buffer 对象,合成完整的响应报文.
2 源码
#ifndef MUDUO_NET_HTTP_HTTPRESPONSE_H #define MUDUO_NET_HTTP_HTTPRESPONSE_H #include <muduo/base/copyable.h> #include <muduo/base/Types.h> #include <map> namespace muduo { namespace net { class Buffer; // 这个类用来保存构造出来的响应报文 class HttpResponse : public muduo::copyable { public: // 状态吗 enum HttpStatusCode { kUnknown, k200Ok = 200, k301MovedPermanently = 301, k400BadRequest = 400, k404NotFound = 404, }; // close 参数表示是否需要在发送完毕以后关闭连接 explicit HttpResponse(bool close) : statusCode_(kUnknown), closeConnection_(close) { } // 设置 void setStatusCode(HttpStatusCode code) { statusCode_ = code; } void setStatusMessage(const string& message) { statusMessage_ = message; } // 发送完毕是否需要关闭链接,在后期可以更改 void setCloseConnection(bool on) { closeConnection_ = on; } bool closeConnection() const { return closeConnection_; } // 各种设置和添加了 void setContentType(const string& contentType) { addHeader("Content-Type", contentType); } void addHeader(const string& key, const string& value) { headers_[key] = value; } void setBody(const string& body) { body_ = body; } // 传入一个buffer ,将响应报文完整的构造出来,用来发送 void appendToBuffer(Buffer* output) const; private: std::map<string, string> headers_; HttpStatusCode statusCode_; // FIXME: add http version string statusMessage_; bool closeConnection_; string body_; }; } } #endif // MUDUO_NET_HTTP_HTTPRESPONSE_H
实现
#include <muduo/net/http/HttpResponse.h> #include <muduo/net/Buffer.h> #include <stdio.h> using namespace muduo; using namespace muduo::net; // 填充的 void HttpResponse::appendToBuffer(Buffer* output) const { char buf[32]; // 这是http版本 snprintf(buf, sizeof buf, "HTTP/1.1 %d ", statusCode_); output->append(buf); output->append(statusMessage_); output->append("\r\n"); // 开始构造响应头 if (closeConnection_) { output->append("Connection: close\r\n"); } else { // 当是长连接的时候,需要分包,因此由这个 Content-Length 用来分包 snprintf(buf, sizeof buf, "Content-Length: %zd\r\n", body_.size()); output->append(buf); output->append("Connection: Keep-Alive\r\n"); } // 添加其他的字段 for (std::map<string, string>::const_iterator it = headers_.begin(); it != headers_.end(); ++it) { output->append(it->first); output->append(": "); output->append(it->second); output->append("\r\n"); } output->append("\r\n"); output->append(body_); }
3 使用代码
HttpResponse 只用在了这里
void HttpServer::onRequest(const TcpConnectionPtr& conn, const HttpRequest& req) { const string& connection = req.getHeader("Connection"); // 当 请求报文中设置了关闭,或是http版本是1.0 ,再或者是 1.1 版本但是没有设置 keep-alive // 那么都需要在发送以后关闭 bool close = connection == "close" || (req.getVersion() == HttpRequest::kHttp10 && connection != "Keep-Alive"); // 这里生成一个 HttpResponse 对象.其中的参数 是由上面计算来的 HttpResponse response(close); // httpCallback_ 是由 用户编写的函数,更具请求头,来生成对应的响应报文 httpCallback_(req, &response); // 生成一个 buffer 对象,用来保存生成的响应报文 Buffer buf; // 从响应体中构造出 可发送的响应报文. response.appendToBuffer(&buf); // 发送给客户端 conn->send(&buf); // 注意这里,当上面判断应该关闭的会后,那么这里就会关闭链接. // 还有上面的 httpCallback_ 也是可以根据请求报文,来设置是否关闭的. if (response.closeConnection()) { conn->shutdown(); } }
再看 ,httpserver 中的 httpCallback_
void setHttpCallback(const HttpCallback& cb) { httpCallback_ = cb; }
然后继续看,什么时候会发送:
首先:
// 处理读事件 void TcpConnection::handleRead(Timestamp receiveTime) { loop_->assertInLoopThread(); int savedErrno = 0; // 使用的是分散读,然后聚合到一起 ssize_t n = inputBuffer_.readFd(channel_->fd(), &savedErrno); if (n > 0) { // 如果读到了数据,那么开始执行回调函数 messageCallback_(shared_from_this(), &inputBuffer_, receiveTime); } else if (n == 0) { // 读到的数据为0,那么直接关闭连接,说明是fin handleClose(); } else { // 当-1,那么发生了错误,应该去处理错误 errno = savedErrno; LOG_SYSERR << "TcpConnection::handleRead"; handleError(); } }
而在TcpConnection构造的时候:
// acceptor 在获取了新的链接以后执行的回调函数 // sockfd 为acceptor的返回值,peerAddr为acceptor内获得的对端链接 void TcpServer::newConnection(int sockfd, const InetAddress &peerAddr) { loop_->assertInLoopThread(); // 然后 因为tcpserver 有一个 eventloop 线程池,用来实际的工作. // 所以这句话其实是一个负载均衡,去一个eventloop,以后传入 tcpconnection EventLoop *ioLoop = threadPool_->getNextLoop(); // 这里为tcpconneciont 命名 char buf[64]; snprintf(buf, sizeof buf, "-%s#%d", ipPort_.c_str(), nextConnId_); ++nextConnId_; string connName = name_ + buf; LOG_INFO << "TcpServer::newConnection [" << name_ << "] - new connection [" << connName << "] from " << peerAddr.toIpPort(); // 获取本端地址 InetAddress localAddr(sockets::getLocalAddr(sockfd)); // FIXME poll with zero timeout to double confirm the new connection // FIXME use make_shared if necessary // 然后创建 tcpconnection TcpConnectionPtr conn(new TcpConnection(ioLoop, connName, sockfd, localAddr, peerAddr)); // tcpserver 保存了每一个链接.每一个 // 应该是为了管理吗. connections_[connName] = conn; // 填充毁掉函数,注意这些毁掉函数,都不是tcpserver 的. // 是由再上一层设置给tcpserver的 conn->setConnectionCallback(connectionCallback_); // 注意这里, 绑定了 messageCallback_ conn->setMessageCallback(messageCallback_); conn->setWriteCompleteCallback(writeCompleteCallback_); conn->setCloseCallback( boost::bind(&TcpServer::removeConnection, this, _1)); // FIXME: unsafe // 到这里,tcpconnection就算是创建完了. // 然后下面就执行 tcpconnection 的connectEstablished(); ioLoop->runInLoop(boost::bind(&TcpConnection::connectEstablished, conn)); }
然后继续向上:
messageCallback_是 HttpServer 传入的
HttpServer::HttpServer(EventLoop* loop, const InetAddress& listenAddr, const string& name, TcpServer::Option option) : server_(loop, listenAddr, name, option), httpCallback_(detail::defaultHttpCallback) { server_.setConnectionCallback( boost::bind(&HttpServer::onConnection, this, _1)); // 当收到消息的时候,会调用的回调函数 server_.setMessageCallback( boost::bind(&HttpServer::onMessage, this, _1, _2, _3)); }
因此在TcpConnection 读出数据以后,实际执行的是这个:
void HttpServer::onMessage(const TcpConnectionPtr& conn, Buffer* buf, Timestamp receiveTime) { // 获取 TcpConn绑定的那个 HttpContext 对象,解析 HttpContext* context = boost::any_cast<HttpContext>(conn->getMutableContext()); if (!context->parseRequest(buf, receiveTime)) { conn->send("HTTP/1.1 400 Bad Request\r\n\r\n"); conn->shutdown(); } if (context->gotAll()) { // 然后调用 onRequest 去构造返 要发送给客户端的响应报文 onRequest(conn, context->request()); context->reset(); } }
至此,就明白是什么调用了吧