http request
HTTP请求包含: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表示短连接
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
http response
status line + header + body (header分为普通报头,响应报头与实体报头)
header与body之间有一空行(CRLF)
状态响应码
1XX ?提示信息 - 表示请求已被成功接收,继续处理
2XX ?成功 - 表示请求已被成功接收,理解,接受
3XX ?重定向 - 要完成请求必须进行更进一步的处理
4XX ?客户端错误 - ?请求有语法错误或请求无法实现
5XX ?服务器端错误 - ? 服务器执行一个有效请求失败
HTTP/1.1 200 OK
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>
HttpRequest:http请求类封装
#ifndef MUDUO_NET_HTTP_HTTPREQUEST_H
#define MUDUO_NET_HTTP_HTTPREQUEST_H
#include <muduo/base/copyable.h>
#include <muduo/base/Timestamp.h>
#include <muduo/base/Types.h>
#include <map>
#include <assert.h>
#include <stdio.h>
namespace muduo
{
namespace net
{
class HttpRequest : public muduo::copyable
{
public:
enum Method
{
kInvalid, kGet, kPost, kHead, kPut, kDelete // http 方法类型,当前只支持这些
};
enum Version
{
kUnknown, kHttp10, kHttp11
};
HttpRequest()
: method_(kInvalid),
version_(kUnknown)
{
}
void setVersion(Version v)
{
version_ = v;
}
Version getVersion() const
{ return version_; }
bool setMethod(const char* start, const char* end)
{
assert(method_ == kInvalid);
string m(start, end);
if (m == "GET")
{
method_ = kGet;
}
else if (m == "POST")
{
method_ = kPost;
}
else if (m == "HEAD")
{
method_ = kHead;
}
else if (m == "PUT")
{
method_ = kPut;
}
else if (m == "DELETE")
{
method_ = kDelete;
}
else
{
method_ = kInvalid;
}
return method_ != kInvalid;
}
Method method() const
{ return method_; }
const char* methodString() const
{
const char* result = "UNKNOWN";
switch(method_)
{
case kGet:
result = "GET";
break;
case kPost:
result = "POST";
break;
case kHead:
result = "HEAD";
break;
case kPut:
result = "PUT";
break;
case kDelete:
result = "DELETE";
break;
default:
break;
}
return result;
}
void setPath(const char* start, const char* end)
{
path_.assign(start, end);
}
const string& path() const
{ return path_; }
void setReceiveTime(Timestamp t)
{ receiveTime_ = t; }
Timestamp receiveTime() const
{ return receiveTime_; }
void addHeader(const char* start, const char* colon, const char* end)
{
string field(start, colon); // header域
++colon;
// 去除左空格
while (colon < end && isspace(*colon))
{
++colon;
}
string value(colon, end); // header值
// 去除右空格
while (!value.empty() && isspace(value[value.size()-1]))
{
value.resize(value.size()-1);
}
headers_[field] = value;
}
string getHeader(const string& field) const
{
string result;
std::map<string, string>::const_iterator it = headers_.find(field);
if (it != headers_.end())
{
result = it->second;
}
return result;
}
const std::map<string, string>& headers() const
{ return headers_; }
void swap(HttpRequest& that)
{
std::swap(method_, that.method_);
path_.swap(that.path_);
receiveTime_.swap(that.receiveTime_);
headers_.swap(that.headers_);
}
private:
Method method_; // 请求方法
Version version_; // 协议版本1.0/1.1
string path_; // 请求路径
Timestamp receiveTime_; // 请求时间
std::map<string, string> headers_; // header列表
};
}
}
#endif // MUDUO_NET_HTTP_HTTPREQUEST_H
HttpResponse:http响应类封装
#ifndef MUDUO_NET_HTTP_HTTPREQUEST_H
#define MUDUO_NET_HTTP_HTTPREQUEST_H
#include <muduo/base/copyable.h>
#include <muduo/base/Timestamp.h>
#include <muduo/base/Types.h>
#include <map>
#include <assert.h>
#include <stdio.h>
namespace muduo
{
namespace net
{
class HttpRequest : public muduo::copyable
{
public:
enum Method
{
kInvalid, kGet, kPost, kHead, kPut, kDelete
};
enum Version
{
kUnknown, kHttp10, kHttp11
};
HttpRequest()
: method_(kInvalid),
version_(kUnknown)
{
}
void setVersion(Version v)
{
version_ = v;
}
Version getVersion() const
{ return version_; }
bool setMethod(const char* start, const char* end)
{
assert(method_ == kInvalid);
string m(start, end);
if (m == "GET")
{
method_ = kGet;
}
else if (m == "POST")
{
method_ = kPost;
}
else if (m == "HEAD")
{
method_ = kHead;
}
else if (m == "PUT")
{
method_ = kPut;
}
else if (m == "DELETE")
{
method_ = kDelete;
}
else
{
method_ = kInvalid;
}
return method_ != kInvalid;
}
Method method() const
{ return method_; }
const char* methodString() const
{
const char* result = "UNKNOWN";
switch(method_)
{
case kGet:
result = "GET";
break;
case kPost:
result = "POST";
break;
case kHead:
result = "HEAD";
break;
case kPut:
result = "PUT";
break;
case kDelete:
result = "DELETE";
break;
default:
break;
}
return result;
}
void setPath(const char* start, const char* end)
{
path_.assign(start, end);
}
const string& path() const
{ return path_; }
void setReceiveTime(Timestamp t)
{ receiveTime_ = t; }
Timestamp receiveTime() const
{ return receiveTime_; }
void addHeader(const char* start, const char* colon, const char* end)
{
string field(start, colon); // header域
++colon;
// 去除左空格
while (colon < end && isspace(*colon))
{
++colon;
}
string value(colon, end); // header值
// 去除右空格
while (!value.empty() && isspace(value[value.size()-1]))
{
value.resize(value.size()-1);
}
headers_[field] = value;
}
string getHeader(const string& field) const
{
string result;
std::map<string, string>::const_iterator it = headers_.find(field);
if (it != headers_.end())
{
result = it->second;
}
return result;
}
const std::map<string, string>& headers() const
{ return headers_; }
void swap(HttpRequest& that)
{
std::swap(method_, that.method_);
path_.swap(that.path_);
receiveTime_.swap(that.receiveTime_);
headers_.swap(that.headers_);
}
private:
Method method_; // 请求方法
Version version_; // 协议版本1.0/1.1
string path_; // 请求路径
Timestamp receiveTime_; // 请求时间
std::map<string, string> headers_; // header列表
};
}
}
#endif // MUDUO_NET_HTTP_HTTPREQUEST_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];
// 添加响应头
snprintf(buf, sizeof buf, "HTTP/1.1 %d ", statusCode_);
output->append(buf);
output->append(statusMessage_);
output->append("\r\n");
if (closeConnection_)
{
// 如果是短连接,不需要告诉浏览器Content-Length,浏览器也能正确处理
output->append("Connection: close\r\n");
}
else
{
snprintf(buf, sizeof buf, "Content-Length: %zd\r\n", body_.size()); // 实体长度
output->append(buf);
output->append("Connection: Keep-Alive\r\n");
}
// header列表
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"); // header与body之间的空行
output->append(body_);
}
HttpContext:http协议解析类
#ifndef MUDUO_NET_HTTP_HTTPCONTEXT_H
#define MUDUO_NET_HTTP_HTTPCONTEXT_H
#include <muduo/base/copyable.h>
#include <muduo/net/http/HttpRequest.h>
namespace muduo
{
namespace net
{
class HttpContext : public muduo::copyable
{
public:
enum HttpRequestParseState
{
kExpectRequestLine,
kExpectHeaders,
kExpectBody,
kGotAll,
};
HttpContext()
: state_(kExpectRequestLine)
{
}
// default copy-ctor, dtor and assignment are fine
bool expectRequestLine() const
{ return state_ == kExpectRequestLine; }
bool expectHeaders() const
{ return state_ == kExpectHeaders; }
bool expectBody() const
{ return state_ == kExpectBody; }
bool gotAll() const
{ return state_ == kGotAll; }
void receiveRequestLine()
{ state_ = kExpectHeaders; }
void receiveHeaders()
{ state_ = kGotAll; } // FIXME
// 重置HttpContext状态
void reset()
{
state_ = kExpectRequestLine;
HttpRequest dummy;
request_.swap(dummy);
}
const HttpRequest& request() const
{ return request_; }
HttpRequest& request()
{ return request_; }
private:
HttpRequestParseState state_; // 请求解析状态
HttpRequest request_; // http请求
};
}
}
#endif // MUDUO_NET_HTTP_HTTPCONTEXT_H
HttpServer:http服务器类封装
#ifndef MUDUO_NET_HTTP_HTTPSERVER_H
#define MUDUO_NET_HTTP_HTTPSERVER_H
#include <muduo/net/TcpServer.h>
#include <boost/noncopyable.hpp>
namespace muduo
{
namespace net
{
class HttpRequest;
class HttpResponse;
/// 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;
HttpServer(EventLoop* loop,
const InetAddress& listenAddr,
const string& name);
~HttpServer(); // force out-line dtor, for scoped_ptr members.
/// 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();
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)的过程中回调此函数,对请求进行具体的处理
};
}
}
#endif // MUDUO_NET_HTTP_HTTPSERVER_H
#include <muduo/net/http/HttpServer.h>
#include <muduo/base/Logging.h>
#include <muduo/net/http/HttpContext.h>
#include <muduo/net/http/HttpRequest.h>
#include <muduo/net/http/HttpResponse.h>
#include <boost/bind.hpp>
using namespace muduo;
using namespace muduo::net;
namespace muduo
{
namespace net
{
namespace detail
{
// FIXME: move to HttpContext class
bool processRequestLine(const char* begin, const char* end, HttpContext* context)
{
bool succeed = false;
const char* start = begin;
const char* space = std::find(start, end, ' ');
HttpRequest& request = context->request();
if (space != end && request.setMethod(start, space)) // 解析请求方法
{
start = space+1;
space = std::find(start, end, ' ');
if (space != end)
{
request.setPath(start, space); // 解析PATH
start = space+1;
succeed = end-start == 8 && std::equal(start, end-1, "HTTP/1.");
if (succeed)
{
if (*(end-1) == '1')
{
request.setVersion(HttpRequest::kHttp11); // HTTP/1.1
}
else if (*(end-1) == '0')
{
request.setVersion(HttpRequest::kHttp10); // HTTP/1.0
}
else
{
succeed = false;
}
}
}
}
return succeed;
}
// FIXME: move to HttpContext class
// return false if any error
bool parseRequest(Buffer* buf, HttpContext* context, Timestamp receiveTime)
{
bool ok = true;
bool hasMore = true;
while (hasMore)
{
if (context->expectRequestLine()) // 处于解析请求行状态
{
const char* crlf = buf->findCRLF();
if (crlf)
{
ok = processRequestLine(buf->peek(), crlf, context); // 解析请求行
if (ok)
{
context->request().setReceiveTime(receiveTime); // 设置请求时间
buf->retrieveUntil(crlf + 2); // 将请求行从buf中取回,包括\r\n
context->receiveRequestLine(); // 将HttpContext状态改为kExpectHeaders
}
else
{
hasMore = false;
}
}
else
{
hasMore = false;
}
}
else if (context->expectHeaders()) // 解析header
{
const char* crlf = buf->findCRLF();
if (crlf)
{
const char* colon = std::find(buf->peek(), crlf, ':'); //冒号所在位置
if (colon != crlf)
{
context->request().addHeader(buf->peek(), colon, crlf);
}
else
{
// empty line, end of header
context->receiveHeaders(); // HttpContext将状态改为kGotAll
hasMore = !context->gotAll();
}
buf->retrieveUntil(crlf + 2); // 将header从buf中取回,包括\r\n
}
else
{
hasMore = false;
}
}
else if (context->expectBody()) // 当前还不支持请求中带body
{
// FIXME:
}
}
return ok;
}
void defaultHttpCallback(const HttpRequest&, HttpResponse* resp)
{
resp->setStatusCode(HttpResponse::k404NotFound);
resp->setStatusMessage("Not Found");
resp->setCloseConnection(true);
}
}
}
}
HttpServer::HttpServer(EventLoop* loop,
const InetAddress& listenAddr,
const string& name)
: server_(loop, listenAddr, name),
httpCallback_(detail::defaultHttpCallback)
{
server_.setConnectionCallback(
boost::bind(&HttpServer::onConnection, this, _1));
server_.setMessageCallback(
boost::bind(&HttpServer::onMessage, this, _1, _2, _3));
}
HttpServer::~HttpServer()
{
}
void HttpServer::start()
{
LOG_WARN << "HttpServer[" << server_.name()
<< "] starts listenning on " << server_.hostport();
server_.start();
}
void HttpServer::onConnection(const TcpConnectionPtr& conn)
{
if (conn->connected())
{
conn->setContext(HttpContext()); // TcpConnection与一个HttpContext绑定
}
}
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,适用于长连接
}
}
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);
Buffer buf;
response.appendToBuffer(&buf);
conn->send(&buf);
if (response.closeConnection())
{
conn->shutdown();
}
}
|