ee27bfa147
This refactors the RequestHandler code, to decouple RequestHandler behavior from the Server implementation. The goal is to make it easier to define additional Server implementations, in addition to just LibEventServer. This adds a RequestHandlerFactory function, rather than using a pure virtual method of the Server class. With the old model, you had to subclass each server implementation separately for each RequestHandler type you wanted to use, resulting in NxM classes if you have N server types and M request handler types. This also changes the behavior of the RequestHandler class somewhat: the code now only creates a single RequestHandler per thread, and uses that object for all request in that thread. Previously the LibEventServer code would attempt to create a new RequestHandler object for each request if supportReset() returned true. This was used by RPCRequestHandler. Now the RPCRequestHandler instead just resets itself automatically when necessary, without requiring external help from LibEventServer. contbuild test runs failed due to git server issues.
363 linhas
12 KiB
C++
363 linhas
12 KiB
C++
/*
|
|
+----------------------------------------------------------------------+
|
|
| HipHop for PHP |
|
|
+----------------------------------------------------------------------+
|
|
| Copyright (c) 2010-2013 Facebook, Inc. (http://www.facebook.com) |
|
|
+----------------------------------------------------------------------+
|
|
| This source file is subject to version 3.01 of the PHP license, |
|
|
| that is bundled with this package in the file LICENSE, and is |
|
|
| available through the world-wide-web at the following url: |
|
|
| http://www.php.net/license/3_01.txt |
|
|
| If you did not receive a copy of the PHP license and are unable to |
|
|
| obtain it through the world-wide-web, please send a note to |
|
|
| license@php.net so we can mail you a copy immediately. |
|
|
+----------------------------------------------------------------------+
|
|
*/
|
|
|
|
#include "hphp/runtime/base/server/rpc_request_handler.h"
|
|
|
|
#include "hphp/runtime/base/server/http_request_handler.h"
|
|
#include "hphp/runtime/base/program_functions.h"
|
|
#include "hphp/runtime/base/runtime_option.h"
|
|
#include "hphp/runtime/base/server/server_stats.h"
|
|
#include "hphp/runtime/base/server/http_protocol.h"
|
|
#include "hphp/runtime/base/server/access_log.h"
|
|
#include "hphp/runtime/base/server/source_root_info.h"
|
|
#include "hphp/runtime/base/server/request_uri.h"
|
|
#include "hphp/runtime/ext/ext_json.h"
|
|
#include "hphp/util/process.h"
|
|
|
|
using std::set;
|
|
|
|
namespace HPHP {
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
|
|
RPCRequestHandler::RPCRequestHandler(bool info /* = true */)
|
|
: m_requestsSinceReset(0),
|
|
m_reset(false),
|
|
m_logResets(info),
|
|
m_returnEncodeType(Json) {
|
|
initState();
|
|
}
|
|
|
|
RPCRequestHandler::~RPCRequestHandler() {
|
|
cleanupState();
|
|
}
|
|
|
|
void RPCRequestHandler::initState() {
|
|
hphp_session_init();
|
|
bool isServer = RuntimeOption::ServerExecutionMode();
|
|
if (isServer) {
|
|
m_context = hphp_context_init();
|
|
} else {
|
|
// In command line mode, we want the xbox workers to
|
|
// output to STDOUT
|
|
m_context = g_context.getNoCheck();
|
|
m_context->obSetImplicitFlush(true);
|
|
}
|
|
m_lastReset = time(0);
|
|
|
|
Logger::ResetRequestCount();
|
|
if (m_logResets) {
|
|
Logger::Info("initializing RPC request handler");
|
|
}
|
|
|
|
m_reset = false;
|
|
m_requestsSinceReset = 0;
|
|
}
|
|
|
|
void RPCRequestHandler::cleanupState() {
|
|
hphp_context_exit(m_context, false);
|
|
hphp_session_exit();
|
|
}
|
|
|
|
bool RPCRequestHandler::needReset() const {
|
|
return (m_reset ||
|
|
m_serverInfo->alwaysReset() ||
|
|
((time(0) - m_lastReset) > m_serverInfo->getMaxDuration()) ||
|
|
(m_requestsSinceReset >= m_serverInfo->getMaxRequest()));
|
|
}
|
|
|
|
void RPCRequestHandler::handleRequest(Transport *transport) {
|
|
if (needReset()) {
|
|
cleanupState();
|
|
initState();
|
|
}
|
|
++m_requestsSinceReset;
|
|
|
|
ExecutionProfiler ep(ThreadInfo::RuntimeFunctions);
|
|
|
|
Logger::OnNewRequest();
|
|
HttpRequestHandler::GetAccessLog().onNewRequest();
|
|
m_context->setTransport(transport);
|
|
transport->enableCompression();
|
|
|
|
ServerStatsHelper ssh("all", ServerStatsHelper::TRACK_MEMORY);
|
|
Logger::Verbose("receiving %s", transport->getCommand().c_str());
|
|
|
|
// will clear all extra logging when this function goes out of scope
|
|
StackTraceNoHeap::ExtraLoggingClearer clearer;
|
|
StackTraceNoHeap::AddExtraLogging("RPC-URL", transport->getUrl());
|
|
|
|
// authentication
|
|
const set<string> &passwords = m_serverInfo->getPasswords();
|
|
if (!passwords.empty()) {
|
|
set<string>::const_iterator iter =
|
|
passwords.find(transport->getParam("auth"));
|
|
if (iter == passwords.end()) {
|
|
transport->sendString("Unauthorized", 401);
|
|
transport->onSendEnd();
|
|
HttpRequestHandler::GetAccessLog().log(transport, nullptr);
|
|
/*
|
|
* HPHP logs may need to access data in ServerStats, so we have to
|
|
* clear the hashtable after writing the log entry.
|
|
*/
|
|
ServerStats::Reset();
|
|
return;
|
|
}
|
|
} else {
|
|
const string &password = m_serverInfo->getPassword();
|
|
if (!password.empty() && password != transport->getParam("auth")) {
|
|
transport->sendString("Unauthorized", 401);
|
|
transport->onSendEnd();
|
|
HttpRequestHandler::GetAccessLog().log(transport, nullptr);
|
|
/*
|
|
* HPHP logs may need to access data in ServerStats, so we have to
|
|
* clear the hashtable after writing the log entry.
|
|
*/
|
|
ServerStats::Reset();
|
|
return;
|
|
}
|
|
}
|
|
|
|
// return encoding type
|
|
ReturnEncodeType returnEncodeType = m_returnEncodeType;
|
|
if (transport->getParam("return") == "serialize") {
|
|
returnEncodeType = Serialize;
|
|
}
|
|
|
|
// resolve virtual host
|
|
const VirtualHost *vhost = HttpProtocol::GetVirtualHost(transport);
|
|
assert(vhost);
|
|
if (vhost->disabled()) {
|
|
transport->sendString("Virtual host disabled.", 404);
|
|
transport->onSendEnd();
|
|
HttpRequestHandler::GetAccessLog().log(transport, vhost);
|
|
return;
|
|
}
|
|
vhost->setRequestTimeoutSeconds();
|
|
|
|
// resolve source root
|
|
string host = transport->getHeader("Host");
|
|
SourceRootInfo sourceRootInfo(host.c_str());
|
|
|
|
// set thread type
|
|
switch (m_serverInfo->getType()) {
|
|
case SatelliteServer::KindOfRPCServer:
|
|
transport->setThreadType(Transport::RpcThread);
|
|
break;
|
|
case SatelliteServer::KindOfXboxServer:
|
|
transport->setThreadType(Transport::XboxThread);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
// record request for debugging purpose
|
|
std::string tmpfile = HttpProtocol::RecordRequest(transport);
|
|
bool ret = executePHPFunction(transport, sourceRootInfo, returnEncodeType);
|
|
HttpRequestHandler::GetAccessLog().log(transport, vhost);
|
|
/*
|
|
* HPHP logs may need to access data in ServerStats, so we have to
|
|
* clear the hashtable after writing the log entry.
|
|
*/
|
|
ServerStats::Reset();
|
|
HttpProtocol::ClearRecord(ret, tmpfile);
|
|
}
|
|
|
|
static const StaticString
|
|
s_output("output"),
|
|
s_return("return"),
|
|
s_HPHP_RPC("HPHP_RPC");
|
|
|
|
bool RPCRequestHandler::executePHPFunction(Transport *transport,
|
|
SourceRootInfo &sourceRootInfo,
|
|
ReturnEncodeType returnEncodeType) {
|
|
// reset timeout counter
|
|
ThreadInfo::s_threadInfo->m_reqInjectionData.started = time(0);
|
|
|
|
string rpcFunc = transport->getCommand();
|
|
{
|
|
ServerStatsHelper ssh("input");
|
|
RequestURI reqURI(rpcFunc);
|
|
HttpProtocol::PrepareSystemVariables(transport, reqURI, sourceRootInfo);
|
|
SystemGlobals *g = (SystemGlobals*)get_global_variables();
|
|
g->GV(_ENV).set(s_HPHP_RPC, 1);
|
|
}
|
|
|
|
bool isFile = rpcFunc.rfind('.') != string::npos;
|
|
string rpcFile;
|
|
bool error = false;
|
|
|
|
Array params;
|
|
string sparams = transport->getParam("params");
|
|
if (!sparams.empty()) {
|
|
Variant jparams = f_json_decode(String(sparams), true);
|
|
if (jparams.isArray()) {
|
|
params = jparams.toArray();
|
|
} else {
|
|
error = true;
|
|
}
|
|
} else {
|
|
vector<string> sparams;
|
|
transport->getArrayParam("p", sparams);
|
|
if (!sparams.empty()) {
|
|
for (unsigned int i = 0; i < sparams.size(); i++) {
|
|
Variant jparams = f_json_decode(String(sparams[i]), true);
|
|
if (same(jparams, false)) {
|
|
error = true;
|
|
break;
|
|
}
|
|
params.append(jparams);
|
|
}
|
|
} else {
|
|
// single string parameter, used by xbox to avoid any en/decoding
|
|
int size;
|
|
const void *data = transport->getPostData(size);
|
|
if (data && size) {
|
|
params.append(String((char*)data, size, AttachLiteral));
|
|
}
|
|
}
|
|
}
|
|
|
|
if (transport->getIntParam("reset") == 1) {
|
|
m_reset = true;
|
|
}
|
|
int output = transport->getIntParam("output");
|
|
|
|
int code;
|
|
if (!error) {
|
|
Variant funcRet;
|
|
string errorMsg = "Internal Server Error";
|
|
string reqInitFunc, reqInitDoc;
|
|
reqInitDoc = transport->getHeader("ReqInitDoc");
|
|
if (reqInitDoc.empty() && m_serverInfo) {
|
|
reqInitFunc = m_serverInfo->getReqInitFunc();
|
|
reqInitDoc = m_serverInfo->getReqInitDoc();
|
|
}
|
|
|
|
if (!reqInitDoc.empty()) {
|
|
reqInitDoc = (std::string)canonicalize_path(reqInitDoc, "", 0);
|
|
}
|
|
if (!reqInitDoc.empty()) {
|
|
reqInitDoc = getSourceFilename(reqInitDoc, sourceRootInfo);
|
|
}
|
|
|
|
bool runOnce = false;
|
|
bool ret = true;
|
|
if (isFile) {
|
|
rpcFile = rpcFunc;
|
|
rpcFunc.clear();
|
|
} else {
|
|
rpcFile = transport->getParam("include");
|
|
if (rpcFile.empty()) {
|
|
rpcFile = transport->getParam("include_once");
|
|
runOnce = true;
|
|
}
|
|
}
|
|
if (!rpcFile.empty()) {
|
|
// invoking a file through rpc
|
|
bool forbidden = false;
|
|
if (!RuntimeOption::ForbiddenFileExtensions.empty()) {
|
|
const char *ext = rpcFile.c_str() + rpcFile.rfind('.') + 1;
|
|
if (RuntimeOption::ForbiddenFileExtensions.find(ext) !=
|
|
RuntimeOption::ForbiddenFileExtensions.end()) {
|
|
forbidden = true;
|
|
}
|
|
}
|
|
if (!forbidden) {
|
|
rpcFile = (std::string) canonicalize_path(rpcFile, "", 0);
|
|
rpcFile = getSourceFilename(rpcFile, sourceRootInfo);
|
|
ret = hphp_invoke(m_context, rpcFile, false, Array(), uninit_null(),
|
|
reqInitFunc, reqInitDoc, error, errorMsg, runOnce);
|
|
}
|
|
// no need to do the initialization for a second time
|
|
reqInitFunc.clear();
|
|
reqInitDoc.clear();
|
|
}
|
|
if (ret && !rpcFunc.empty()) {
|
|
ret = hphp_invoke(m_context, rpcFunc, true, params, ref(funcRet),
|
|
reqInitFunc, reqInitDoc, error, errorMsg);
|
|
}
|
|
if (ret) {
|
|
bool serializeFailed = false;
|
|
String response;
|
|
switch (output) {
|
|
case 0: {
|
|
assert(returnEncodeType == Json ||
|
|
returnEncodeType == Serialize);
|
|
try {
|
|
response = (returnEncodeType == Json) ? f_json_encode(funcRet)
|
|
: f_serialize(funcRet);
|
|
} catch (...) {
|
|
serializeFailed = true;
|
|
}
|
|
break;
|
|
}
|
|
case 1: response = m_context->obDetachContents(); break;
|
|
case 2:
|
|
response =
|
|
f_json_encode(CREATE_MAP2(s_output, m_context->obDetachContents(),
|
|
s_return, f_json_encode(funcRet)));
|
|
break;
|
|
case 3: response = f_serialize(funcRet); break;
|
|
}
|
|
if (serializeFailed) {
|
|
code = 500;
|
|
transport->sendString(
|
|
"Serialization of the return value failed", 500);
|
|
m_reset = true;
|
|
} else {
|
|
transport->sendRaw((void*)response.data(), response.size());
|
|
code = transport->getResponseCode();
|
|
}
|
|
} else if (error) {
|
|
code = 500;
|
|
transport->sendString(errorMsg, 500);
|
|
m_reset = true;
|
|
} else {
|
|
code = 404;
|
|
transport->sendString("Not Found", 404);
|
|
}
|
|
} else {
|
|
code = 400;
|
|
transport->sendString("Bad Request", 400);
|
|
}
|
|
|
|
params.reset();
|
|
sourceRootInfo.clear();
|
|
|
|
transport->onSendEnd();
|
|
ServerStats::LogPage(isFile ? rpcFile : rpcFunc, code);
|
|
|
|
m_context->onShutdownPostSend();
|
|
m_context->obClean(); // in case postsend/cleanup output something
|
|
m_context->restoreSession();
|
|
return !error;
|
|
}
|
|
|
|
string RPCRequestHandler::getSourceFilename(const string &path,
|
|
SourceRootInfo &sourceRootInfo) {
|
|
if (path.empty() || path[0] == '/') return path;
|
|
// If it is not a sandbox, sourceRoot will be the same as
|
|
// RuntimeOption::SourceRoot.
|
|
string sourceRoot = sourceRootInfo.path();
|
|
if (sourceRoot.empty()) {
|
|
return Process::GetCurrentDirectory() + "/" + path;
|
|
}
|
|
return sourceRoot + path;
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
}
|