muduo源码中内嵌了一个简单的HTTP server程序,源码见muduo/net/http
。看了源码之后受益匪浅。
muduo里面的HttpServer目前只支持GET和HEAD方法,获取资源的Content-Type
在源代码中写死,本文主要就是添加了一个读取mime.type的功能。希望之后能继续扩展这个简单的HttpServer,支持CGI和POST。
跟简单的echo server套路一样,首先定义一个HttpServer
,它需要用EventLoop对象构造,包含一个TcpServer成员等。唯一的区别就是多了一个onRequest
回调
//HttpServer.h
#ifndef __HTTPSERVER_H__
#define __HTTPSERVER_H__
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include "HttpContext.h"
#include "HttpRequest.h"
#include "HttpResponse.h"
using namespace std;
class HttpServer : boost::noncopyable
{
public:
//http回调类型
typedef boost::function<void (const HttpRequest&,
HttpResponse*)> HttpCallback;
//构造函数
HttpServer(muduo::net::EventLoop* loop,
const muduo::net::InetAddress& listenAddr);
~HttpServer(); // force out-line dtor, for scoped_ptr members.
muduo::net::EventLoop* getLoop() const { return server_.getLoop(); }
void setHttpCallback(const HttpCallback& cb)
{
httpCallback_ = cb;
}
void setThreadNum(int numThreads)
{
server_.setThreadNum(numThreads);
}
void start();
private:
//onConnection
void onConnection(const muduo::net::TcpConnectionPtr &conn);
//onMessage
void onMessage(const muduo::net::TcpConnectionPtr&conn,
muduo::net::Buffer*buf,
muduo::Timestamp receiveTime);
//+++++
void onRequest(const muduo::net::TcpConnectionPtr&,const HttpRequest&);
muduo::net::TcpServer server_;
HttpCallback httpCallback_;
};
#endif
//HttpServer.cpp
#include "HttpServer.h"
#if 1
//默认HTTP回调,返回错误码
void defaultHttpCallback(const HttpRequest&, HttpResponse* resp)
{
#if 1
resp->setStatusCode(HttpResponse::CODE_400);
resp->setStatusMessage("Not Found");
resp->setCloseConnection(true);
#endif
}
#endif
#if 1
//构造函数
HttpServer::HttpServer(muduo::net::EventLoop* loop,
const muduo::net::InetAddress& listenAddr)
: server_(loop, listenAddr, "MyHttpd"),httpCallback_(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_.ipPort();
server_.start();
}
#endif
//新连接回调
void HttpServer::onConnection(const muduo::net::TcpConnectionPtr& conn)
{
if (conn->connected())
{
conn->setContext(HttpContext());
}
}
//消息回调
void HttpServer::onMessage(const muduo::net::TcpConnectionPtr& conn,
muduo::net::Buffer* buf,
muduo::Timestamp receiveTime)
{
HttpContext*context=boost::any_cast(conn->getMutableContext());
//解析请求
if(!context->parseRequest(buf,receiveTime))
{
conn->send("HTTP/1.1 400 Bad Request\r\n\r\n");
conn->shutdown();
}
if (context->gotAll())//state_==gotALL
{
//请求已经解析完毕
onRequest(conn, context->request());
context->reset();//context reset
}
}
#if 1
void HttpServer::onRequest(const muduo::net::TcpConnectionPtr& conn, const HttpRequest& req)
{
#if 1
const string& connection = req.getHeader("Connection");
bool close = connection == "close" ||
(req.getVersion() == HttpRequest::HTTP10 && connection != "Keep-Alive");
HttpResponse response(close);//构造响应
httpCallback_(req, &response);//用户回调
muduo::net::Buffer buf;
//此时response已经构造好,将向客户发送Response添加到buffer中
response.appendToBuffer(&buf);
conn->send(&buf);
//如果非Keep-Alive则直接关掉
if (response.closeConnection())
{
conn->shutdown();
}
#endif
}
#endif
按照TcpServer接收连接->处理连接这个顺序来看,首先将调用onConnection,该函数为Tcp连接创建Http上下文变量HttpContext
,这个类主要用于接收客户请求(这里为Http请求),并解析请求。
//HttpContext.h
#ifndef __HTTPCONTEXT_H__
#define __HTTPCONTEXT_H__
#include
#include
#include "HttpRequest.h"
class Buffer;
class HttpContext:public muduo::copyable
{
public:
//解析请求的状态
enum HttpRequestParseState
{
kExpectRequestLine,
kExpectHeaders,
kExpectBody,
kGotAll,
};
HttpContext()
: state_(kExpectRequestLine)
{
}
bool parseRequest(muduo::net::Buffer* buf, muduo::Timestamp receiveTime);
bool gotAll() const
{ return state_ == kGotAll; }
void reset()
{
state_ = kExpectRequestLine;
HttpRequest dummy;
request_.swap(dummy);
}
const HttpRequest& request() const
{ return request_; }
//返回Http请求
HttpRequest& request()
{ return request_; }
private:
//解析请求行
bool processRequestLine(const char* begin, const char* end);
HttpRequestParseState state_;
//解析的结果将保存在request_成员中
HttpRequest request_;
};
#endif
#include "HttpContext.h"
//解析请求头
bool HttpContext::processRequestLine(const char*begin,const char*end)
{
bool succeed=false;
const char*start=begin;
const char*space = std::find(start,end,' ');
//设置请求方法//method_
if(space!=end&&request_.setMethod(start,space))
{
start=space+1;
space = std::find(start,end,' ');
if(space !=end)
{
//解析URI
const char* question = std::find(start, space, '?');
if (question != space)
{
request_.setPath(start, question);
request_.setQuery(question, space);
}
else
{
request_.setPath(start, space);
}
//解析HTTP版本号
start = space+1;
succeed = end-start == 8 && std::equal(start, end-1, "HTTP/1.");
if (succeed)
{
if (*(end-1) == '1')
{
request_.setVersion(HttpRequest::HTTP11);
}
else if (*(end-1) == '0')
{
request_.setVersion(HttpRequest::HTTP10);
}
else
{
succeed = false;
}
}//endif
}//endif
}//endif
return succeed;
}
//解析请求头
bool HttpContext::parseRequest(muduo::net::Buffer*buf,muduo::Timestamp receiveTime)
{
bool ok= true;
bool hasMore=true;
while(hasMore)
{
//解析请求行
if(state_==kExpectRequestLine)
{
//
const char*crlf=buf->findCRLF();
if(crlf)
{
//开始解析请求行
ok=processRequestLine(buf->peek(),crlf);
if(ok)
{
//解析成功
request_.setReceiveTime(receiveTime);
//回收请求行buffer
buf->retrieveUntil(crlf+2);
state_=kExpectHeaders;
}
else
{
hasMore=false;
}
}
else
{
hasMore=false;
}
}
//解析请求头
else if(state_==kExpectHeaders)
{
const char* crlf = buf->findCRLF();
if (crlf)
{
//冒号
const char* colon = std::find(buf->peek(), crlf, ':');
if (colon != crlf)
{
request_.addHeader(buf->peek(), colon, crlf);
}
else
{
// empty line, end of header
// FIXME:
state_ = kGotAll;
hasMore = false;
}
buf->retrieveUntil(crlf + 2);//回收
}
else
{
hasMore = false;
}
}
else if(state_==kExpectBody)
{
// FIXME:
}
}//endwhile
return ok;
}
HttpContext
类对客户端发来的请求进行接收和解析,最终将解析出来的消息保存到了HttpRequest对象中,下面来看看这个类中都包含哪些内容:
//HttpRequest.h
#ifndef __HTTPREQUEST_H__
#define __HTTPREQUEST_H__
#include
#include
#include
#include
#include
using namespace std;
class HttpRequest:muduo::copyable
{
public:
enum Method
{
INVALID,
GET,
POST,
HEAD,
PUT,
DELETE
};
enum Version
{
UNKNOWN,
HTTP10,
HTTP11
};
HttpRequest();
//set和get方法声明
void setVersion(Version v);
Version getVersion() const;
bool setMethod(const char *start,const char* end);
Method method()const;
const char*methodString()const;
void setPath(const char*start,const char*end);
const string& path()const;
void setQuery(const char* start, const char* end);
const string& query() const;
void setReceiveTime(muduo::Timestamp t);
muduo::Timestamp receiveTime() const;
void addHeader(const char*start,const char*colon,const char*end);
string getHeader(const string& field)const;
const map<string,string>&headers()const;
void swap(HttpRequest& that);
private:
Method method_;
Version version_;
string path_;
string query_;
muduo::Timestamp receiveTime_;
std::map<string,string>headers_;
};
#endif
//HttpRequest.cpp
#include "HttpRequest.h"
HttpRequest::HttpRequest(): method_(INVALID),
version_(UNKNOWN)
{
}
void HttpRequest::setVersion(Version v)
{
version_=v;
}
HttpRequest::Version HttpRequest::getVersion()const
{
return version_;
}
bool HttpRequest::setMethod(const char* start, const char* end)
{
assert(method_ == INVALID);
string m(start, end);
if (m == "GET")
{
method_ = GET;
}
else if (m == "POST")
{
method_ = POST;
}
else if (m == "HEAD")
{
method_ = HEAD;
}
else if (m == "PUT")
{
method_ = PUT;
}
else if (m == "DELETE")
{
method_ = DELETE;
}
else
{
method_ = INVALID;
}
return method_ != INVALID;
}
HttpRequest::Method HttpRequest::method() const
{
return method_;
}
const char* HttpRequest::methodString() const
{
const char* result = "UNKNOWN";
switch(method_)
{
case GET:
result = "GET";
break;
case POST:
result = "POST";
break;
case HEAD:
result = "HEAD";
break;
case PUT:
result = "PUT";
break;
case DELETE:
result = "DELETE";
break;
default:
break;
}
return result;
}
void HttpRequest::setPath(const char* start, const char* end)
{
path_.assign(start, end);
}
const string& HttpRequest::path() const
{
return path_;
}
void HttpRequest::setQuery(const char* start, const char* end)
{
query_.assign(start, end);
}
const string& HttpRequest::query() const
{
return query_;
}
void HttpRequest::setReceiveTime(muduo::Timestamp t)
{
receiveTime_ = t;
}
muduo::Timestamp HttpRequest::receiveTime() const
{
return receiveTime_;
}
void HttpRequest::addHeader(const char* start,
const char* colon,
const char* end)
{
string field(start, colon);
++colon;
while (colon < end && isspace(*colon))
{
++colon;
}
string value(colon, end);
//while循环用来去掉后导的空格
while (!value.empty() && isspace(value[value.size()-1]))
{
value.resize(value.size()-1);
}
headers_[field] = value;//将解析出来的头信息放入map中
}
string HttpRequest::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>& HttpRequest::headers() const
{
return headers_;
}
void HttpRequest::swap(HttpRequest& that)
{
std::swap(method_, that.method_);
path_.swap(that.path_);
query_.swap(that.query_);
receiveTime_.swap(that.receiveTime_);
headers_.swap(that.headers_);
}
解析出来请求头后就需要按照不同的请求方法、类型等构造响应了,muduo封装了HttpResponse
类将响应实体保存到Buffer
对象中,最后通过TcpConnection
发送给客户端。
//HttpResponse.h
#ifndef __HTTPRESPONSE_H__
#define __HTTPRESPONSE_H__
#include
#include
#include
#include
#include
using namespace std;
class HttpResponse:public muduo::copyable
{
public:
enum HttpStatusCode
{
CODE_UNKNOWN,
CODE_200=200,
CODE_301=301,
CODE_400=400,
CODE_404=404
};
explicit HttpResponse(bool close)
: statusCode_(CODE_UNKNOWN),
closeConnection_(close)
{
}
void setStatusCode(HttpStatusCode code);
void setStatusMessage(const string&message);
void setCloseConnection(bool on);
bool closeConnection()const;
void setContentType(const string&contentType);
void addHeader(const string&key,const string&value);
void setBody(const string &body);
void appendToBuffer(muduo::net::Buffer*output)const;
private:
//响应头
std::map<string,string>headers_;
//响应码
HttpStatusCode statusCode_;
//状态信息
string statusMessage_;
//是否 keep-alive
bool closeConnection_;
//响应报文
string body_;
};
#endif
//HttpResponse.cpp
#include "HttpResponse.h"
//set和get方法
void HttpResponse::setStatusCode(HttpStatusCode code)
{
statusCode_=code;
}
void HttpResponse::setStatusMessage(const string& message)
{
statusMessage_ = message;
}
void HttpResponse::setCloseConnection(bool on)
{
closeConnection_ = on;
}
bool HttpResponse::closeConnection() const
{
return closeConnection_;
}
void HttpResponse::setContentType(const string& contentType)
{
addHeader("Content-Type", contentType);
}
void HttpResponse::addHeader(const string& key, const string& value)
{
headers_[key] = value;
}
void HttpResponse::setBody(const string& body)
{
body_ = body;
}
void HttpResponse::appendToBuffer(muduo::net::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_)
{
//
output->append("Connection: close\r\n");
}
else
{
//Keep-Alive需要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_);
}
除此之外我写了一个简单的工具类用来解析mime.type
//Util.cpp
#include "Util.h"
namespace Util
{
//获取请求的绝对路径
string ConstructPath(const string &path)
{
string webHome="/home/zhangxiao/zxtest/myhttpd/httpservermuduo";
if(path=="/")
{
return webHome+"/index.html";
}
else
{
return webHome+path;
}
}
//获取文件扩展名
string GetExtent(const string &path)
{
int i;
for(i=path.size()-1;;--i)
{
if(path[i]=='.')
break;
}
return string(path.begin()+i+1,path.end());
}
//获取content-Type
void GetContentType(const string &tmpExtension,
string &contentType)
{
ifstream mimeFile("mime.types");
string line;
while(NULL!=getline(mimeFile,line))
{
if(line[0]!='#')
{
stringstream lineStream(line);
contentType.clear();
lineStream>>contentType;
vector<string>extensions;
string extension;
while(lineStream>>extension)
{
extensions.push_back(extension);
}
for(int i=0;iif(tmpExtension==extensions[i])
{
mimeFile.close();
return ;
}
}
}//endif
}//end while
//如果都不匹配就默认为text/plain
contentType="text/plain";
mimeFile.close();
}
//获取Content
string GetContent(const string &fileName)
{
std::ifstream fin(fileName, std::ios::in | std::ios::binary);
if(fin.fail())
{
return string("");
}
std::ostringstream oss;
oss << fin.rdbuf();
return std::string(oss.str());
}
}
利用上述封装的类,写出服务端测试代码:
#include "HttpServer.h"
#include "HttpRequest.h"
#include "HttpResponse.h"
#include
#include
#include "Util.h"
#include
#include
#include
using namespace std;
void onRequest(const HttpRequest& req, HttpResponse* resp)
{
string path=Util::ConstructPath(req.path());
string extent=Util::GetExtent(path);
string contentType="";
Util::GetContentType(extent,contentType);
string content=Util::GetContent(path);
if(content.empty())
{
resp->setStatusCode(HttpResponse::CODE_404);
resp->setStatusMessage("Not Found");
}
else
{
resp->setBody(content);
resp->setStatusCode(HttpResponse::CODE_200);
resp->setStatusMessage("OK");
resp->setContentType(contentType);
}
}
int main(int argc, char* argv[])
{
int numThreads = 0;
if (argc > 1)
{
//benchmark = true;
muduo::Logger::setLogLevel(muduo::Logger::WARN);
numThreads = atoi(argv[1]);
}
muduo::net::EventLoop loop;
HttpServer server(&loop, muduo::net::InetAddress(8000) );
server.setHttpCallback(onRequest);
server.setThreadNum(numThreads);
server.start();
loop.loop();
return 0;
}
本地运行,用chrome做客户端测试:
其中图是我自己截图的一个png格式文件,放在工程目录下,浏览器会自动发送请求。
index.html文件:
<html>
<head>
<title>Welcome to nginx!title>
head>
<body bgcolor="white" text="black">
<img src="/baidu.png" />
<center><h1>zxtest Welcome to nginx!h1>center>
body>
html>
Makefile文件
PROGS=myhttpd
MUDUO_DIRECTORY = ${HOME}/build/release-install
MUDUO_INCLUDE = $(MUDUO_DIRECTORY)/include
MUDUO_LIBRARY = $(MUDUO_DIRECTORY)/lib
all : ${PROGS}
CXXFLAGS+=-g -std=c++11 -I${MUDUO_INCLUDE}
LDFLAGS+=-L${MUDUO_LIBRARY} -lmuduo_net -lmuduo_base -lpthread -lrt
myhttpd:myhttpd.o HttpServer.o HttpRequest.o HttpResponse.o HttpContext.o Util.o
@${CXX} ${CXXFLAGS} -o $@ $^ ${LDFLAGS}
clean:
@rm -rf ${PROGS} *.o
.PHONY: all clean
图解http
linux多线程服务端编程使用muduo网络库