1、http request:
request line + header + body (header分为普通报头,请求报头与实体报头) header与body之间有一空行(CRLF) 请求方法有: Get, Post, Head, Put, Delete等 协议版本1.0、1.1
常用请求头
Accept:浏览器可接受的媒体(MIME)类型; Accept-Language:浏览器所希望的语言种类 Accept-Encoding:浏览器能够解码的编码方法,如gzip,deflate等 User-Agent:告诉HTTP服务器, 客户端使用的操作系统和浏览器的名称和版本 Connection:表示是否需要持久连接,Keep-Alive表示长连接,close表示短连接
一个典型的http 请求:
GET / HTTP/1.1 Accept: image/jpeg, application/x-ms-application, image/gif, application/xaml+xml, image/pjpeg, application/x-ms-xbap, application/vnd.ms-excel, application/vnd.ms-powerpoint, application/msword, */* Accept-Language: zh-CN User-Agent: Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 6.1; Trident/4.0; SLCC2; .NET CLR 2.0.50727; .NET CLR 3.5.30729; .NET CLR 3.0.30729; Media Center PC 6.0; Tablet PC 2.0) Accept-Encoding: gzip, deflate Host: 192.168.159.188:8000 Connection: Keep-Alive 2、http response:
status line + header + body (header分为普通报头,响应报头与实体报头) header与body之间有一空行(CRLF) 状态响应码
1XX 提示信息 - 表示请求已被成功接收,继续处理 2XX 成功 - 表示请求已被成功接收,理解,接受 3XX 重定向 - 要完成请求必须进行更进一步的处理 4XX 客户端错误 - 请求有语法错误或请求无法实现 5XX 服务器端错误 - 服务器执行一个有效请求失败
一个典型的http 响应: HTTP/1.1 200 O Content-Length: 112 Connection: Keep-Alive Content-Type: text/html Server: Muduo
<html><head><title>This is title</title></head><body><h1>Hello</h1>Now is 20130611 02:14:31.518462</body></html>
3、muduo_http 库
HttpRequest:http请求类封装,主要有以下几个成员:
class HttpRequest : public muduo::copyable
{
public:
void addHeader(const char *start, const char *colon, const char *end);
private:
Method method_; // 请求方法
Version version_; // 协议版本1.0/1.1
string path_; // 请求路径
Timestamp receiveTime_; // 请求时间
std::map<string, string> headers_; // header列表
}
HttpResponse:http响应类封装,主要有以下几个成员:
class HttpResponse : public muduo::copyable
{
public:
void appendToBuffer(Buffer *output) const; // 将HttpResponse添加到Buffer
private:
std::map<string, string> headers_; // header列表
HttpStatusCode statusCode_; // 状态响应码
// FIXME: add http version
string statusMessage_; // 状态响应码对应的文本信息
bool closeConnection_; // 是否关闭连接
string body_; // 实体
};
HttpContext:http协议解析类,主要有以下几个成员:
class HttpContext : public muduo::copyable
{
private:
HttpRequestParseState state_; // 请求解析状态
HttpRequest request_; // http请求
};
HttpServer:http服务器类封装,主要有几个成员:
/// A simple embeddable HTTP server designed for report status of a program.
/// It is not a fully HTTP 1.1 compliant server, but provides minimum features
/// that can communicate with HttpClient and Web browser.
/// It is synchronous, just like Java Servlet.
class HttpServer : boost::noncopyable
{
public:
typedef boost::function < void (const HttpRequest &,
HttpResponse *) > HttpCallback;
/// Not thread safe, callback be registered before calling start().
void setHttpCallback(const HttpCallback &cb)
{
httpCallback_ = cb;
}
void setThreadNum(int numThreads)
{
server_.setThreadNum(numThreads);
}
void start() { server_.start(); }
private:
void onConnection(const TcpConnectionPtr &conn);
void onMessage(const TcpConnectionPtr &conn, Buffer *buf, Timestamp receiveTime);
void onRequest(const TcpConnectionPtr &, const HttpRequest &);
TcpServer server_;
HttpCallback httpCallback_; // 在处理http请求(即调用onRequest)的过程中回调此函数,对请求进行具体的处理
};
在HttpServer 构造函数中:
server_.setConnectionCallback(
boost::bind(&HttpServer::onConnection, this, _1));
server_.setMessageCallback(
boost::bind(&HttpServer::onMessage, this, _1, _2, _3));
即通过设置server_ ,最终设置到TcpConnection 的回调函数, 当客户端如浏览器连接上来,根据以前的分析可知,调用HttpServer::onConnection(), 绑定HttpContext到TcpConnection 中的 boost::any context_;
if (conn->connected())
{
conn->setContext(HttpContext()); // TcpConnection与一个HttpContext绑定
}
接着客户端发出请求,比如访问服务器的某个路径,调用HttpServer::onMessage(),
void HttpServer::onMessage(const TcpConnectionPtr &conn,
Buffer *buf,
Timestamp receiveTime)
{
HttpContext *context = boost::any_cast<HttpContext>(conn->getMutableContext());
if (!detail::parseRequest(buf, context, receiveTime))
{
conn->send("HTTP/1.1 400 Bad Request\r\n\r\n");
conn->shutdown();
}
// 请求消息解析完毕
if (context->gotAll())
{
onRequest(conn, context->request());
context->reset(); // 本次请求处理完毕,重置HttpContext,适用于长连接(一个连接多次请求)
}
}
其中parseRequest() 会将存放在Buffer 中的请求解析到server_.TcpConnection.context_.request_ 中,最后调用HttpServer::onRequest(),
void HttpServer::onRequest(const TcpConnectionPtr &conn, const HttpRequest &req)
{
const string &connection = req.getHeader("Connection");
bool close = connection == "close" ||
(req.getVersion() == HttpRequest::kHttp10 && connection != "Keep-Alive");
HttpResponse response(close);
httpCallback_(req, &response); // 客户代码设置的回调函数,填充response
Buffer buf;
response.appendToBuffer(&buf); // 将响应填充到buf
conn->send(&buf); // 将buf 中的响应发送给客户端
if (response.closeConnection())
{
conn->shutdown(); //短连接直接关闭
}
}
即要用客户代码设置的httpCallback_ 函数来填充httpResponse,然后发送给客户端。
测试代码:
HttpServer_test.cc:
#include <muduo/net/http/HttpServer.h>
#include <muduo/net/http/HttpRequest.h>
#include <muduo/net/http/HttpResponse.h>
#include <muduo/net/EventLoop.h>
#include <muduo/base/Logging.h>
#include <iostream>
#include <map>
using namespace muduo;
using namespace muduo::net;
extern char favicon[555];
bool benchmark = false;
// 实际的请求处理
void onRequest(const HttpRequest &req, HttpResponse *resp)
{
std::cout << "Headers " << req.methodString() << " " << req.path() << std::endl;
if (!benchmark)
{
const std::map<string, string> &headers = req.headers();
for (std::map<string, string>::const_iterator it = headers.begin();
it != headers.end();
++it)
{
std::cout << it->first << ": " << it->second << std::endl;
}
}
if (req.path() == "/")
{
resp->setStatusCode(HttpResponse::k200Ok);
resp->setStatusMessage("OK");
resp->setContentType("text/html");
resp->addHeader("Server", "Muduo");
string now = Timestamp::now().toFormattedString();
resp->setBody("<html><head><title>This is title</title></head>"
"<body><h1>Hello</h1>Now is " + now +
"</body></html>");
}
else if (req.path() == "/favicon.ico")
{
resp->setStatusCode(HttpResponse::k200Ok);
resp->setStatusMessage("OK");
resp->setContentType("image/png");
resp->setBody(string(favicon, sizeof favicon));
}
else if (req.path() == "/hello")
{
resp->setStatusCode(HttpResponse::k200Ok);
resp->setStatusMessage("OK");
resp->setContentType("text/plain");
resp->addHeader("Server", "Muduo");
resp->setBody("hello, world!\n");
}
else
{
resp->setStatusCode(HttpResponse::k404NotFound);
resp->setStatusMessage("Not Found");
resp->setCloseConnection(true);
}
}
int main(int argc, char *argv[])
{
int numThreads = 0;
if (argc > 1)
{
benchmark = true;
Logger::setLogLevel(Logger::WARN);
numThreads = atoi(argv[1]);
}
EventLoop loop;
HttpServer server(&loop, InetAddress(8000), "dummy");
server.setHttpCallback(onRequest);
server.setThreadNum(numThreads);
server.start();
loop.loop();
}
// 这是一个图片数据
char favicon[555] =
{
.....
};
运行程序,使用浏览器访问目录,如下:
服务器端输出如下:
simba@ubuntu:~/Documents/build/debug/bin$ ./httpserver_test 20131113 08:15:30.110059Z 3637 TRACE IgnoreSigPipe Ignore SIGPIPE - EventLoop.cc:51 20131113 08:15:30.112625Z 3637 TRACE updateChannel fd = 4 events = 3 - EPollPoller.cc:104 20131113 08:15:30.113241Z 3637 TRACE EventLoop EventLoop created 0xBFBC8EF4 in thread 3637 - EventLoop.cc:76 20131113 08:15:30.113677Z 3637 TRACE updateChannel fd = 5 events = 3 - EPollPoller.cc:104 20131113 08:15:30.114767Z 3637 WARN HttpServer[dummy] starts listenning on 0.0.0.0:8000 - HttpServer.cc:155 20131113 08:15:30.115687Z 3637 TRACE updateChannel fd = 6 events = 3 - EPollPoller.cc:104 20131113 08:15:30.116272Z 3637 TRACE loop EventLoop 0xBFBC8EF4 start looping - EventLoop.cc:108 20131113 08:15:32.870784Z 3637 TRACE poll 1 events happended - EPollPoller.cc:65 20131113 08:15:32.871784Z 3637 TRACE printActiveChannels {6: IN } - EventLoop.cc:271 20131113 08:15:32.872287Z 3637 INFO TcpServer::newConnection [dummy] - new connection [dummy:0.0.0.0:8000#1] from 192.168.56.1:2794 - TcpServer.cc:93 20131113 08:15:32.872898Z 3637 DEBUG TcpConnection TcpConnection::ctor[dummy:0.0.0.0:8000#1] at 0x9BD77E0 fd=8 - TcpConnection.cc:65 20131113 08:15:32.873280Z 3637 TRACE newConnection [1] usecount=1 - TcpServer.cc:111 20131113 08:15:32.873640Z 3637 TRACE newConnection [2] usecount=2 - TcpServer.cc:113 20131113 08:15:32.874050Z 3637 TRACE updateChannel fd = 8 events = 3 - EPollPoller.cc:104 20131113 08:15:32.874459Z 3637 TRACE newConnection [5] usecount=2 - TcpServer.cc:123 20131113 08:15:32.890691Z 3637 TRACE poll 1 events happended - EPollPoller.cc:65 20131113 08:15:32.890825Z 3637 TRACE printActiveChannels {8: IN } - EventLoop.cc:271 Headers GET /hello Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8 Accept-Encoding: gzip,deflate,sdch Accept-Language: zh-CN,zh;q=0.8 Connection: keep-alive Cookie: Hm_lvt_3d143f0a07b6487f65609d8411e5464f=1380329613 Host: 192.168.56.188:8000 User-Agent: Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/29.0.1547.66 Safari/537.36 20131113 08:15:42.902273Z 3637 TRACE poll nothing happended - EPollPoller.cc:74 20131113 08:15:52.912887Z 3637 TRACE poll nothing happended - EPollPoller.cc:74 ...... 20131113 08:17:11.421249Z 3637 TRACE poll 1 events happended - EPollPoller.cc:65 20131113 08:17:11.421328Z 3637 TRACE printActiveChannels {8: IN } - EventLoop.cc:271 Headers GET /favicon.ico Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8 Accept-Encoding: gzip,deflate,sdch Accept-Language: zh-CN,zh;q=0.8 Connection: keep-alive Cookie: Hm_lvt_3d143f0a07b6487f65609d8411e5464f=1380329613 Host: 192.168.56.188:8000 User-Agent: Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/29.0.1547.66 Safari/537.36 20131113 08:17:21.432017Z 3637 TRACE poll nothing happended - EPollPoller.cc:74 20131113 08:17:31.439508Z 3637 TRACE poll nothing happended - EPollPoller.cc:74
从输出中看到,fd=6是监听套接字,fd=8是与服务器连接起来的已连接套接字(fd=7 是idlefd_)。浏览器访问某个目录,fd=8可读事件发生,服务器端最终执行到httpCallback_(onRequest() 函数),首先从HttpRequest中读取解析到的请求头部等信息并打印出来,即Headers GET /hello 开始的几行输出。这是一个长连接,所以第二次切换目录访问时,也是fd=8可读事件发生,只不过填充到HttpResponse 的数据不同,故浏览器端看到的输出就不一样了。
注意:muduo只实现了最简单的http应答功能,并不能处理带有body的http 请求,比如POST。实现http库主要是为了让muduo inspect 库可以通过HTTP方式为服务器提供监控接口。
参考:
《UNP》
muduo manual.pdf
《linux 多线程服务器编程:使用muduo c++网络库》