2018-10-24 18:02:42 +00:00
|
|
|
#include <abyss/server.hpp>
|
2018-10-19 11:41:36 +00:00
|
|
|
#include <llarp/time.h>
|
2018-10-23 11:29:37 +00:00
|
|
|
#include <sstream>
|
|
|
|
#include <unordered_map>
|
2018-10-24 18:02:42 +00:00
|
|
|
#include <string>
|
|
|
|
#include <llarp/logger.hpp>
|
|
|
|
#include <algorithm>
|
2018-10-19 11:41:36 +00:00
|
|
|
|
|
|
|
namespace abyss
|
|
|
|
{
|
|
|
|
namespace http
|
|
|
|
{
|
2018-10-23 11:29:37 +00:00
|
|
|
struct RequestHeader
|
2018-10-19 11:41:36 +00:00
|
|
|
{
|
2018-10-23 11:29:37 +00:00
|
|
|
typedef std::unordered_multimap< std::string, std::string > Headers_t;
|
2018-10-24 18:02:42 +00:00
|
|
|
Headers_t Headers;
|
2018-10-23 11:29:37 +00:00
|
|
|
std::string Method;
|
2018-10-24 18:02:42 +00:00
|
|
|
std::string Path;
|
2018-10-23 11:29:37 +00:00
|
|
|
};
|
2018-10-19 11:41:36 +00:00
|
|
|
|
2018-10-23 11:29:37 +00:00
|
|
|
struct ConnImpl
|
|
|
|
{
|
|
|
|
llarp_tcp_conn* _conn;
|
|
|
|
IRPCHandler* handler;
|
|
|
|
BaseReqHandler* _parent;
|
2018-10-19 11:41:36 +00:00
|
|
|
llarp_time_t m_LastActive;
|
|
|
|
llarp_time_t m_ReadTimeout;
|
2018-10-23 11:29:37 +00:00
|
|
|
bool m_Bad;
|
|
|
|
RequestHeader m_Header;
|
2018-10-25 17:03:25 +00:00
|
|
|
std::unique_ptr< abyss::json::IParser > m_BodyParser;
|
|
|
|
json::Document m_Request;
|
|
|
|
json::Document m_Response;
|
2018-10-19 11:41:36 +00:00
|
|
|
|
2018-10-23 11:29:37 +00:00
|
|
|
enum HTTPState
|
2018-10-19 11:41:36 +00:00
|
|
|
{
|
2018-10-23 11:29:37 +00:00
|
|
|
eReadHTTPMethodLine,
|
|
|
|
eReadHTTPHeaders,
|
|
|
|
eReadHTTPBody,
|
|
|
|
eWriteHTTPStatusLine,
|
|
|
|
eWriteHTTPHeaders,
|
|
|
|
eWriteHTTPBody,
|
|
|
|
eCloseMe
|
|
|
|
};
|
|
|
|
|
|
|
|
HTTPState m_State;
|
|
|
|
|
|
|
|
ConnImpl(BaseReqHandler* p, llarp_tcp_conn* c, llarp_time_t readtimeout)
|
|
|
|
: _conn(c), _parent(p)
|
|
|
|
{
|
|
|
|
handler = nullptr;
|
2018-10-29 16:48:36 +00:00
|
|
|
m_LastActive = p->now();
|
2018-10-19 11:41:36 +00:00
|
|
|
m_ReadTimeout = readtimeout;
|
2018-10-24 18:02:42 +00:00
|
|
|
// set up tcp members
|
|
|
|
_conn->user = this;
|
|
|
|
_conn->read = &ConnImpl::OnRead;
|
|
|
|
_conn->tick = &ConnImpl::OnTick;
|
|
|
|
_conn->closed = &ConnImpl::OnClosed;
|
2018-10-23 11:29:37 +00:00
|
|
|
m_Bad = false;
|
|
|
|
m_State = eReadHTTPMethodLine;
|
|
|
|
}
|
|
|
|
|
|
|
|
~ConnImpl()
|
|
|
|
{
|
|
|
|
}
|
|
|
|
|
|
|
|
bool
|
2018-10-24 18:02:42 +00:00
|
|
|
FeedLine(std::string& line)
|
|
|
|
{
|
|
|
|
switch(m_State)
|
|
|
|
{
|
|
|
|
case eReadHTTPMethodLine:
|
|
|
|
return ProcessMethodLine(line);
|
|
|
|
case eReadHTTPHeaders:
|
|
|
|
return ProcessHeaderLine(line);
|
|
|
|
default:
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
bool
|
2018-10-25 13:40:07 +00:00
|
|
|
ProcessMethodLine(string_view line)
|
2018-10-24 18:02:42 +00:00
|
|
|
{
|
|
|
|
auto idx = line.find_first_of(' ');
|
2018-10-25 13:40:07 +00:00
|
|
|
if(idx == string_view::npos)
|
2018-10-24 18:02:42 +00:00
|
|
|
return false;
|
|
|
|
m_Header.Method = line.substr(0, idx);
|
|
|
|
line = line.substr(idx + 1);
|
|
|
|
idx = line.find_first_of(' ');
|
2018-10-25 13:40:07 +00:00
|
|
|
if(idx == string_view::npos)
|
2018-10-24 18:02:42 +00:00
|
|
|
return false;
|
|
|
|
m_Header.Path = line.substr(0, idx);
|
|
|
|
m_State = eReadHTTPHeaders;
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool
|
2018-10-25 13:40:07 +00:00
|
|
|
ShouldProcessHeader(const string_view& name) const
|
2018-10-24 18:02:42 +00:00
|
|
|
{
|
|
|
|
// TODO: header whitelist
|
2018-10-25 17:03:25 +00:00
|
|
|
return name == "content-type" || name == "content-length";
|
2018-10-24 18:02:42 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
bool
|
2018-10-25 13:40:07 +00:00
|
|
|
ProcessHeaderLine(string_view line)
|
2018-10-24 18:02:42 +00:00
|
|
|
{
|
2018-10-25 13:40:07 +00:00
|
|
|
if(line.size() == 0)
|
|
|
|
{
|
|
|
|
// end of headers
|
|
|
|
m_State = eReadHTTPBody;
|
|
|
|
return true;
|
|
|
|
}
|
2018-10-24 18:02:42 +00:00
|
|
|
auto idx = line.find_first_of(':');
|
2018-10-25 13:40:07 +00:00
|
|
|
if(idx == string_view::npos)
|
2018-10-24 18:02:42 +00:00
|
|
|
return false;
|
2018-10-25 13:40:07 +00:00
|
|
|
string_view header = line.substr(0, idx);
|
2018-10-25 17:03:25 +00:00
|
|
|
string_view val = line.substr(1 + idx);
|
2018-10-24 18:02:42 +00:00
|
|
|
// to lowercase
|
|
|
|
std::transform(header.begin(), header.end(), header.begin(),
|
|
|
|
[](char ch) -> char { return ::tolower(ch); });
|
|
|
|
if(ShouldProcessHeader(header))
|
|
|
|
{
|
|
|
|
val = val.substr(val.find_first_not_of(' '));
|
|
|
|
m_Header.Headers.insert(std::make_pair(header, val));
|
|
|
|
}
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool
|
|
|
|
WriteStatusLine(int code, const std::string& message)
|
|
|
|
{
|
|
|
|
char buf[128] = {0};
|
|
|
|
int sz = snprintf(buf, sizeof(buf), "HTTP/1.0 %d %s\r\n", code,
|
|
|
|
message.c_str());
|
|
|
|
if(sz > 0)
|
|
|
|
{
|
|
|
|
return llarp_tcp_conn_async_write(_conn, buf, sz);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool
|
|
|
|
WriteResponseSimple(int code, const std::string& msg,
|
|
|
|
const char* contentType, const char* content)
|
|
|
|
{
|
|
|
|
if(!WriteStatusLine(code, msg))
|
|
|
|
return false;
|
|
|
|
char buf[128] = {0};
|
|
|
|
int sz =
|
|
|
|
snprintf(buf, sizeof(buf), "Content-Type: %s\r\n", contentType);
|
|
|
|
if(sz <= 0)
|
|
|
|
return false;
|
|
|
|
if(!llarp_tcp_conn_async_write(_conn, buf, sz))
|
|
|
|
return false;
|
|
|
|
size_t contentLength = strlen(content);
|
|
|
|
sz = snprintf(buf, sizeof(buf), "Content-Length: %zu\r\n\r\n",
|
|
|
|
contentLength);
|
|
|
|
if(sz <= 0)
|
|
|
|
return false;
|
|
|
|
if(!llarp_tcp_conn_async_write(_conn, buf, sz))
|
|
|
|
return false;
|
|
|
|
if(!llarp_tcp_conn_async_write(_conn, content, contentLength))
|
|
|
|
return false;
|
|
|
|
m_State = eWriteHTTPBody;
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool
|
|
|
|
FeedBody(const char* buf, size_t sz)
|
2018-10-23 11:29:37 +00:00
|
|
|
{
|
2018-10-25 17:03:25 +00:00
|
|
|
if(m_Header.Method != "POST")
|
2018-10-24 18:02:42 +00:00
|
|
|
{
|
2018-10-25 17:03:25 +00:00
|
|
|
return WriteResponseSimple(405, "Method Not Allowed", "text/plain",
|
|
|
|
"nope");
|
2018-10-24 18:02:42 +00:00
|
|
|
}
|
|
|
|
{
|
2018-10-25 17:03:25 +00:00
|
|
|
auto itr = m_Header.Headers.find("content-type");
|
|
|
|
if(itr == m_Header.Headers.end())
|
|
|
|
{
|
|
|
|
return WriteResponseSimple(415, "Unsupported Media Type",
|
|
|
|
"text/plain",
|
|
|
|
"no content type provided");
|
|
|
|
}
|
|
|
|
else if(itr->second != "application/json")
|
|
|
|
{
|
|
|
|
return WriteResponseSimple(415, "Unsupported Media Type",
|
|
|
|
"text/plain",
|
|
|
|
"this does not look like jsonrpc 2.0");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// initialize body parser
|
|
|
|
if(m_BodyParser == nullptr)
|
|
|
|
{
|
|
|
|
ssize_t contentLength = 0;
|
|
|
|
auto itr = m_Header.Headers.find("content-length");
|
|
|
|
if(itr == m_Header.Headers.end())
|
|
|
|
{
|
|
|
|
return WriteResponseSimple(400, "Bad Request", "text/plain",
|
|
|
|
"no content length");
|
|
|
|
}
|
|
|
|
contentLength = std::stoll(itr->second);
|
|
|
|
if(contentLength <= 0)
|
|
|
|
{
|
|
|
|
return WriteResponseSimple(400, "Bad Request", "text/plain",
|
|
|
|
"bad content length");
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
m_BodyParser.reset(abyss::json::MakeParser(contentLength));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if(!m_BodyParser->FeedData(buf, sz))
|
|
|
|
{
|
|
|
|
return WriteResponseSimple(400, "Bad Request", "text/plain",
|
|
|
|
"invalid body size");
|
|
|
|
}
|
2018-10-26 13:02:15 +00:00
|
|
|
|
2018-10-25 17:03:25 +00:00
|
|
|
switch(m_BodyParser->Parse(m_Request))
|
|
|
|
{
|
|
|
|
case json::IParser::eNeedData:
|
|
|
|
return true;
|
|
|
|
case json::IParser::eParseError:
|
|
|
|
return WriteResponseSimple(400, "Bad Request", "text/plain",
|
|
|
|
"bad json object");
|
|
|
|
case json::IParser::eDone:
|
|
|
|
if(m_Request.IsObject() && m_Request.HasMember("params")
|
2018-10-25 17:19:53 +00:00
|
|
|
&& m_Request.HasMember("method") && m_Request.HasMember("id")
|
|
|
|
&& (m_Request["id"].IsString() || m_Request["id"].IsNumber())
|
2018-10-25 17:03:25 +00:00
|
|
|
&& m_Request["method"].IsString()
|
|
|
|
&& m_Request["params"].IsObject())
|
|
|
|
{
|
|
|
|
m_Response.SetObject();
|
2018-10-25 17:19:53 +00:00
|
|
|
m_Response.AddMember("jsonrpc",
|
|
|
|
abyss::json::Value().SetString("2.0"),
|
|
|
|
m_Response.GetAllocator());
|
|
|
|
m_Response.AddMember("id", m_Request["id"],
|
|
|
|
m_Response.GetAllocator());
|
2018-10-25 17:03:25 +00:00
|
|
|
if(handler->HandleJSONRPC(m_Request["method"].GetString(),
|
2018-10-26 13:02:15 +00:00
|
|
|
m_Request["params"], m_Response))
|
2018-10-25 17:19:53 +00:00
|
|
|
{
|
2018-10-25 17:03:25 +00:00
|
|
|
return WriteResponseJSON();
|
2018-10-25 17:19:53 +00:00
|
|
|
}
|
2018-10-25 17:03:25 +00:00
|
|
|
}
|
|
|
|
return WriteResponseSimple(500, "internal error", "text/plain",
|
|
|
|
"nope");
|
|
|
|
default:
|
|
|
|
return false;
|
2018-10-24 18:02:42 +00:00
|
|
|
}
|
2018-10-25 17:03:25 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
bool
|
|
|
|
WriteResponseJSON()
|
|
|
|
{
|
|
|
|
std::string response;
|
|
|
|
json::ToString(m_Response, response);
|
|
|
|
return WriteResponseSimple(200, "OK", "application/json",
|
|
|
|
response.c_str());
|
2018-10-23 11:29:37 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
bool
|
|
|
|
ProcessRead(const char* buf, size_t sz)
|
|
|
|
{
|
|
|
|
if(m_Bad)
|
2018-10-24 18:02:42 +00:00
|
|
|
{
|
2018-10-23 11:29:37 +00:00
|
|
|
return false;
|
2018-10-24 18:02:42 +00:00
|
|
|
}
|
|
|
|
|
2018-10-29 16:48:36 +00:00
|
|
|
m_LastActive = _parent->now();
|
2018-10-24 18:02:42 +00:00
|
|
|
if(m_State < eReadHTTPBody)
|
|
|
|
{
|
2018-10-25 13:40:07 +00:00
|
|
|
const char* end = strstr(buf, "\r\n");
|
|
|
|
while(end)
|
2018-10-24 18:02:42 +00:00
|
|
|
{
|
2018-10-30 12:30:21 +00:00
|
|
|
string_view line(buf, end);
|
2018-10-25 13:40:07 +00:00
|
|
|
switch(m_State)
|
2018-10-24 18:02:42 +00:00
|
|
|
{
|
2018-10-25 13:40:07 +00:00
|
|
|
case eReadHTTPMethodLine:
|
|
|
|
if(!ProcessMethodLine(line))
|
|
|
|
return false;
|
2018-10-25 17:03:25 +00:00
|
|
|
sz -= line.size() + (2 * sizeof(char));
|
2018-10-25 13:40:07 +00:00
|
|
|
break;
|
|
|
|
case eReadHTTPHeaders:
|
|
|
|
if(!ProcessHeaderLine(line))
|
|
|
|
return false;
|
2018-10-25 17:03:25 +00:00
|
|
|
sz -= line.size() + (2 * sizeof(char));
|
2018-10-25 13:40:07 +00:00
|
|
|
break;
|
|
|
|
default:
|
|
|
|
break;
|
2018-10-24 18:02:42 +00:00
|
|
|
}
|
2018-10-25 17:03:25 +00:00
|
|
|
buf = end + (2 * sizeof(char));
|
|
|
|
end = strstr(buf, "\r\n");
|
2018-10-24 18:02:42 +00:00
|
|
|
}
|
|
|
|
}
|
2018-10-25 13:40:07 +00:00
|
|
|
if(m_State == eReadHTTPBody)
|
2018-10-24 18:02:42 +00:00
|
|
|
return FeedBody(buf, sz);
|
2018-10-25 13:40:07 +00:00
|
|
|
return false;
|
2018-10-23 11:29:37 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
|
|
OnRead(llarp_tcp_conn* conn, const void* buf, size_t sz)
|
|
|
|
{
|
|
|
|
ConnImpl* self = static_cast< ConnImpl* >(conn->user);
|
|
|
|
if(!self->ProcessRead((const char*)buf, sz))
|
|
|
|
self->MarkBad();
|
|
|
|
}
|
|
|
|
|
2018-10-24 18:02:42 +00:00
|
|
|
static void
|
|
|
|
OnClosed(llarp_tcp_conn* conn)
|
|
|
|
{
|
|
|
|
ConnImpl* self = static_cast< ConnImpl* >(conn->user);
|
|
|
|
self->_conn = nullptr;
|
|
|
|
}
|
|
|
|
|
2018-10-23 11:29:37 +00:00
|
|
|
static void
|
|
|
|
OnTick(llarp_tcp_conn* conn)
|
|
|
|
{
|
|
|
|
ConnImpl* self = static_cast< ConnImpl* >(conn->user);
|
|
|
|
self->Tick();
|
|
|
|
}
|
|
|
|
|
|
|
|
void
|
|
|
|
Tick()
|
|
|
|
{
|
|
|
|
}
|
|
|
|
|
|
|
|
/// mark bad so next tick we are closed
|
|
|
|
void
|
|
|
|
MarkBad()
|
|
|
|
{
|
|
|
|
m_Bad = true;
|
2018-10-19 11:41:36 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
bool
|
|
|
|
ShouldClose(llarp_time_t now) const
|
|
|
|
{
|
2018-10-24 18:02:42 +00:00
|
|
|
return now - m_LastActive > m_ReadTimeout || m_Bad
|
|
|
|
|| m_State == eCloseMe;
|
2018-10-19 11:41:36 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void
|
2018-10-23 11:29:37 +00:00
|
|
|
Close()
|
2018-10-19 11:41:36 +00:00
|
|
|
{
|
2018-10-24 18:02:42 +00:00
|
|
|
if(_conn)
|
|
|
|
{
|
|
|
|
llarp_tcp_conn_close(_conn);
|
|
|
|
_conn = nullptr;
|
|
|
|
}
|
2018-10-19 11:41:36 +00:00
|
|
|
}
|
2018-10-25 13:40:07 +00:00
|
|
|
}; // namespace http
|
2018-10-19 11:41:36 +00:00
|
|
|
|
2018-10-23 11:29:37 +00:00
|
|
|
IRPCHandler::IRPCHandler(ConnImpl* conn) : m_Impl(conn)
|
|
|
|
{
|
|
|
|
}
|
|
|
|
|
|
|
|
IRPCHandler::~IRPCHandler()
|
|
|
|
{
|
|
|
|
m_Impl->Close();
|
|
|
|
delete m_Impl;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool
|
|
|
|
IRPCHandler::ShouldClose(llarp_time_t now) const
|
|
|
|
{
|
|
|
|
return m_Impl->ShouldClose(now);
|
|
|
|
}
|
|
|
|
|
2018-10-19 11:41:36 +00:00
|
|
|
BaseReqHandler::BaseReqHandler(llarp_time_t reqtimeout)
|
|
|
|
: m_ReqTimeout(reqtimeout)
|
|
|
|
{
|
|
|
|
m_loop = nullptr;
|
|
|
|
m_Logic = nullptr;
|
|
|
|
m_acceptor.accepted = &BaseReqHandler::OnAccept;
|
|
|
|
m_acceptor.user = this;
|
2018-10-24 18:02:42 +00:00
|
|
|
m_acceptor.tick = &OnTick;
|
|
|
|
m_acceptor.closed = nullptr;
|
2018-10-19 11:41:36 +00:00
|
|
|
}
|
|
|
|
|
2018-10-23 11:29:37 +00:00
|
|
|
bool
|
|
|
|
BaseReqHandler::ServeAsync(llarp_ev_loop* loop, llarp_logic* logic,
|
|
|
|
const sockaddr* bindaddr)
|
|
|
|
{
|
|
|
|
m_loop = loop;
|
|
|
|
m_Logic = logic;
|
2018-10-24 18:02:42 +00:00
|
|
|
return llarp_tcp_serve(m_loop, &m_acceptor, bindaddr);
|
2018-10-23 11:29:37 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void
|
2018-10-24 18:02:42 +00:00
|
|
|
BaseReqHandler::OnTick(llarp_tcp_acceptor* tcp)
|
2018-10-23 11:29:37 +00:00
|
|
|
{
|
2018-10-24 18:02:42 +00:00
|
|
|
BaseReqHandler* self = static_cast< BaseReqHandler* >(tcp->user);
|
2018-10-23 11:29:37 +00:00
|
|
|
|
|
|
|
self->Tick();
|
|
|
|
}
|
|
|
|
|
|
|
|
void
|
|
|
|
BaseReqHandler::Tick()
|
|
|
|
{
|
2018-10-29 16:48:36 +00:00
|
|
|
auto _now = now();
|
|
|
|
auto itr = m_Conns.begin();
|
2018-10-23 11:29:37 +00:00
|
|
|
while(itr != m_Conns.end())
|
|
|
|
{
|
2018-10-30 12:30:21 +00:00
|
|
|
if((*itr)->ShouldClose(_now))
|
2018-10-23 11:29:37 +00:00
|
|
|
itr = m_Conns.erase(itr);
|
|
|
|
else
|
|
|
|
++itr;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-10-19 11:41:36 +00:00
|
|
|
BaseReqHandler::~BaseReqHandler()
|
|
|
|
{
|
|
|
|
llarp_tcp_acceptor_close(&m_acceptor);
|
|
|
|
}
|
|
|
|
|
|
|
|
void
|
|
|
|
BaseReqHandler::OnAccept(llarp_tcp_acceptor* acceptor, llarp_tcp_conn* conn)
|
|
|
|
{
|
2018-10-23 11:29:37 +00:00
|
|
|
BaseReqHandler* self = static_cast< BaseReqHandler* >(acceptor->user);
|
|
|
|
ConnImpl* connimpl = new ConnImpl(self, conn, self->m_ReqTimeout);
|
|
|
|
IRPCHandler* rpcHandler = self->CreateHandler(connimpl);
|
|
|
|
if(rpcHandler == nullptr)
|
|
|
|
{
|
|
|
|
connimpl->Close();
|
|
|
|
delete connimpl;
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
connimpl->handler = rpcHandler;
|
|
|
|
self->m_Conns.emplace_back(rpcHandler);
|
2018-10-19 11:41:36 +00:00
|
|
|
}
|
|
|
|
} // namespace http
|
|
|
|
} // namespace abyss
|