0x01 InitHTTPServer
初始化访问控制列表(ACL)
if (!InitHTTPAllowList())
return false;
if (gArgs.GetBoolArg("-rpcssl", false)) {
uiInterface.ThreadSafeMessageBox(
"SSL mode for RPC (-rpcssl) is no longer supported.",
"", CClientUIInterface::MSG_ERROR);
return false;
}
初始化HTTP Server首先通过InitHTTPAllowList
来初始化访问控制列表,然后看命令行中是否设置了rpcssl
参数,由于目前的版本不支持ssl,所以如果设置了这个参数就报错。再来看看InitHTTPAllowList
的实现,
/** Initialize ACL list for HTTP server
httpserver.cpp line 190
*/
static bool InitHTTPAllowList()
{
rpc_allow_subnets.clear();
CNetAddr localv4;
CNetAddr localv6;
LookupHost("127.0.0.1", localv4, false);
LookupHost("::1", localv6, false);
rpc_allow_subnets.push_back(CSubNet(localv4, 8)); // always allow IPv4 local subnet
rpc_allow_subnets.push_back(CSubNet(localv6)); // always allow IPv6 localhost
for (const std::string& strAllow : gArgs.GetArgs("-rpcallowip")) {
CSubNet subnet;
LookupSubNet(strAllow.c_str(), subnet);
if (!subnet.IsValid()) {
uiInterface.ThreadSafeMessageBox(
strprintf("Invalid -rpcallowip subnet specification: %s. Valid are a single IP (e.g. 1.2.3.4), a network/netmask (e.g. 1.2.3.4/255.255.255.0) or a network/CIDR (e.g. 1.2.3.4/24).", strAllow),
"", CClientUIInterface::MSG_ERROR);
return false;
}
rpc_allow_subnets.push_back(subnet);
}
std::string strAllowed;
for (const CSubNet& subnet : rpc_allow_subnets)
strAllowed += subnet.ToString() + " ";
LogPrint(BCLog::HTTP, "Allowing HTTP connections from: %s\n", strAllowed);
return true;
}
rpc_allow_subnets
是一个vector类型变量,保存所有允许访问的主机ip,首先先加入本地的地址,然后从命令行中的-rpcallowip
参数读取ip列表,读取时检测地址的有效性,最后打印出允许访问的所有ip列表。
重定向日志
// Redirect libevent's logging to our own log
event_set_log_callback(&libevent_log_cb);
// Update libevent's log handling. Returns false if our version of
// libevent doesn't support debug logging, in which case we should
// clear the BCLog::LIBEVENT flag.
if (!UpdateHTTPServerLogging(logCategories & BCLog::LIBEVENT)) {
logCategories &= ~BCLog::LIBEVENT;
}
这段代码就是将libevent的日志重定向到代码的日志系统中。
初始化基于libevent的http协议
#ifdef WIN32
evthread_use_windows_threads();
#else
evthread_use_pthreads();
#endif
raii_event_base base_ctr = obtain_event_base();
/* Create a new evhttp object to handle requests. */
raii_evhttp http_ctr = obtain_evhttp(base_ctr.get());
struct evhttp* http = http_ctr.get();
if (!http) {
LogPrintf("couldn't create evhttp. Exiting.\n");
return false;
}
evhttp_set_timeout(http, gArgs.GetArg("-rpcservertimeout", DEFAULT_HTTP_SERVER_TIMEOUT));
evhttp_set_max_headers_size(http, MAX_HEADERS_SIZE);
evhttp_set_max_body_size(http, MAX_SIZE);
evhttp_set_gencb(http, http_request_cb, nullptr);
if (!HTTPBindAddresses(http)) {
LogPrintf("Unable to bind any endpoint for RPC server\n");
return false;
}
LogPrint(BCLog::HTTP, "Initialized HTTP server\n");
int workQueueDepth = std::max((long)gArgs.GetArg("-rpcworkqueue", DEFAULT_HTTP_WORKQUEUE), 1L);
LogPrintf("HTTP: creating work queue of depth %d\n", workQueueDepth);
workQueue = new WorkQueue<HTTPClosure>(workQueueDepth);
// transfer ownership to eventBase/HTTP via .release()
eventBase = base_ctr.release();
eventHTTP = http_ctr.release();
return true;
首先代码根据系统环境判断使用windows线程还是其他环境下的线程,接下来就是基于libevent的http协议的实现,采用libevent的原因有以下几个方面:
- 事件驱动,高性能;
- 轻量级,专注于网络;
- 跨平台,支持各主流平台;
- 支持多种I/O多路复用技术,epoll、poll、dev/poll、select和kqueue等;
- 支持I/O,定时器和信号等事件。
基于libevent实现的http协议主要有以下这么几个步骤:
event_base base = event_base_new()
,首先新建event_base
对象;evhttp http = evhttp_new(base)
,然后新建evhttp
对象;evhttp_bind_socket(http, "0.0.0.0", port)
,接下来绑定ip地址和端口;evhttp_set_gencb(http, http_call_back, NULL)
,然后设置请求处理函数http_call_back
;event_base_dispatch(base)
, 最后派发事件循环。
所以一个基于libevent简单的http server代码如下:
#include "event2/http.h"
#include "event2/event.h"
#include "event2/buffer.h"
#include <stdlib.h>
#include <stdio.h>
void HttpGenericCallback(struct evhttp_request* request, void* arg)
{
const struct evhttp_uri* evhttp_uri = evhttp_request_get_evhttp_uri(request);
char url[8192];
evhttp_uri_join(const_cast<struct evhttp_uri*>(evhttp_uri), url, 8192);
printf("accept request url:%s\n", url);
struct evbuffer* evbuf = evbuffer_new();
if (!evbuf)
{
printf("create evbuffer failed!\n");
return ;
}
evbuffer_add_printf(evbuf, "Server response. Your request url is %s", url);
evhttp_send_reply(request, HTTP_OK, "OK", evbuf);
evbuffer_free(evbuf);
}
int main(int argc, char** argv)
{
if (argc != 2)
{
printf("usage:%s port\n", argv[0]);
return 1;
}
int port = atoi(argv[1]);
if (port == 0)
{
printf("port error:%s\n", argv[1]);
return 1;
}
struct event_base* base = event_base_new();
if (!base)
{
printf("create event_base failed!\n");
return 1;
}
struct evhttp* http = evhttp_new(base);
if (!http)
{
printf("create evhttp failed!\n");
return 1;
}
if (evhttp_bind_socket(http, "0.0.0.0", port) != 0)
{
printf("bind socket failed! port:%d\n", port);
return 1;
}
evhttp_set_gencb(http, HttpGenericCallback, NULL);
event_base_dispatch(base);
return 0;
}
作者:楚客
链接:http://www.jianshu.com/p/906c8b9f0629
來源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
再回到源代码中,我们发现整体步骤和上面的简单版http server很类似,但是使用的函数都换了个名字,像obtain_event_base()
和obtain_evhttp()
,看一下它们的实现,
inline raii_event_base obtain_event_base() {
auto result = raii_event_base(event_base_new());
if (!result.get())
throw std::runtime_error("cannot create event_base");
return result;
}
可以发现就是把原来的函数封装了一下,原来的变量类型也都换了一个新的类型,这个新类型的前面都加上了raii
,而这又是什么东西呢?
RAII
转自:http://blog.csdn.net/doc_sgl/article/details/43028009
什么是RAII 技术?
我们在C++中经常使用new申请了内存空间,但是却也经常忘记delete回收申请的空间,容易造成内存溢出,于是RAII技术就诞生了,来解决这样的问题。RAII(Resource Acquisition Is Initialization)机制是Bjarne Stroustrup首先提出的,是一种利用对象生命周期来控制程序资源(如内存、文件句柄、网络连接、互斥量等等)的简单技术。 我们知道在函数内部的一些成员是放置在栈空间上的,当函数返回时,这些栈上的局部变量就会立即释放空间,于是Bjarne Stroustrup就想到确保能运行资源释放代码的地方就是在这个程序段(栈)中放置的对象的析构函数了,因为stack winding会保证它们的析构函数都会被执行。RAII就利用了栈里面的变量的这一特点。RAII 的一般做法是这样的:在对象构造时获取资源,接着控制对资源的访问使之在对象的生命周期内始终保持有效,最后在对象析构的时候释放资源。借此,我们实际上把管理一份资源的责任托管给了一个存放在栈空间上的局部对象。
这种做法有两大好处:
(1)不需要显式地释放资源。
(2)采用这种方式,对象所需的资源在其生命期内始终保持有效。
概括一下,raii
就是为了避免申请内存但是没有释放从而导致内存泄漏的情况出现所使用的一种技术,这种技术能够在对象离开作用域是自动释放。
http server 请求回调函数
所以raii_event_base
和raii_evhttp
都是这两种对象的一个变种。接下来的几个evhttp_set_xxx
函数都是设置连接的限制条件以及请求的回调函数http_request_cb
,来看看这个回调函数的实现,
/** HTTP request callback */
static void http_request_cb(struct evhttp_request* req, void* arg)
{
std::unique_ptr<HTTPRequest> hreq(new HTTPRequest(req));
LogPrint(BCLog::HTTP, "Received a %s request for %s from %s\n",
RequestMethodString(hreq->GetRequestMethod()), hreq->GetURI(), hreq->GetPeer().ToString());
// Early address-based allow check
if (!ClientAllowed(hreq->GetPeer())) {
hreq->WriteReply(HTTP_FORBIDDEN);
return;
}
// Early reject unknown HTTP methods
if (hreq->GetRequestMethod() == HTTPRequest::UNKNOWN) {
hreq->WriteReply(HTTP_BADMETHOD);
return;
}
// Find registered handler for prefix
std::string strURI = hreq->GetURI();
std::string path;
std::vector<HTTPPathHandler>::const_iterator i = pathHandlers.begin();
std::vector<HTTPPathHandler>::const_iterator iend = pathHandlers.end();
// 查找处理函数时,有精确匹配和前缀匹配两种情形
for (; i != iend; ++i) {
bool match = false;
if (i->exactMatch)
match = (strURI == i->prefix);
else
match = (strURI.substr(0, i->prefix.size()) == i->prefix);
if (match) {
path = strURI.substr(i->prefix.size());
break;
}
}
// Dispatch to worker thread, 加到workQueue中
if (i != iend) {
std::unique_ptr<HTTPWorkItem> item(new HTTPWorkItem(std::move(hreq), path, i->handler));
assert(workQueue);
if (workQueue->Enqueue(item.get()))
item.release(); /* if true, queue took ownership */
else {
LogPrintf("WARNING: request rejected because http work queue depth exceeded, it can be increased with the -rpcworkqueue= setting\n");
item->req->WriteReply(HTTP_INTERNAL, "Work queue depth exceeded");
}
} else {
hreq->WriteReply(HTTP_NOTFOUND);
}
}
这个函数和之前例子中的HttpGenericCallback
函数所起的作用类似,都是用evhttp_set_gencb
进行设置的,不过这里并没有实际对请求进行处理,只是检查请求的路径是否有对应的处理函数,所有路径的处理函数都保存在在pathHandlers
变量中,通过RegisterHTTPHandler
进行添加,UnregisterHTTPHandler
进行删除。如果在pathHandlers
中找到了对应的处理函数,那么就将请求和对应的处理函数封装到一个新的对象HTTPWorkItem
中,然后再把HTTPWorkItem
加入到workQueue
中,这个workQueue
由单独的线程不断的执行。如果找不到对应的处理函数或者请求的格式错误,那么就返回对应的错误提示。