948040f7d8
- fix merge issues - remove platform-specific neo defines that were getting pulled in everywhere - fix asm alignment directive on OSX
444 linhas
16 KiB
C++
444 linhas
16 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/http_request_handler.h"
|
|
#include "hphp/runtime/base/program_functions.h"
|
|
#include "hphp/runtime/base/execution_context.h"
|
|
#include "hphp/runtime/base/runtime_option.h"
|
|
#include "hphp/util/timer.h"
|
|
#include "hphp/runtime/base/server/static_content_cache.h"
|
|
#include "hphp/runtime/base/server/dynamic_content_cache.h"
|
|
#include "hphp/runtime/base/server/server_stats.h"
|
|
#include "hphp/util/network.h"
|
|
#include "hphp/runtime/base/preg.h"
|
|
#include "hphp/runtime/ext/ext_function.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/base/server/http_protocol.h"
|
|
#include "hphp/runtime/base/time/datetime.h"
|
|
#include "hphp/runtime/debugger/debugger.h"
|
|
#include "hphp/util/alloc.h"
|
|
#ifndef __APPLE__
|
|
#include "hphp/util/service_data.h"
|
|
#endif
|
|
|
|
namespace HPHP {
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
|
|
IMPLEMENT_THREAD_LOCAL(AccessLog::ThreadData,
|
|
HttpRequestHandler::s_accessLogThreadData);
|
|
|
|
AccessLog HttpRequestHandler::s_accessLog(
|
|
&(HttpRequestHandler::getAccessLogThreadData));
|
|
|
|
HttpRequestHandler::HttpRequestHandler()
|
|
: m_pathTranslation(true)
|
|
#ifndef __APPLE__
|
|
,m_requestTimedOutOnQueue(ServiceData::createTimeseries(
|
|
"requests_timed_out_on_queue",
|
|
{ServiceData::StatsType::COUNT}))
|
|
#endif
|
|
{
|
|
}
|
|
|
|
void HttpRequestHandler::sendStaticContent(Transport *transport,
|
|
const char *data, int len,
|
|
time_t mtime,
|
|
bool compressed,
|
|
const std::string &cmd,
|
|
const char *ext) {
|
|
assert(ext);
|
|
assert(cmd.rfind('.') != string::npos);
|
|
assert(strcmp(ext, cmd.c_str() + cmd.rfind('.') + 1) == 0);
|
|
|
|
hphp_string_imap<string>::const_iterator iter =
|
|
RuntimeOption::StaticFileExtensions.find(ext);
|
|
if (iter != RuntimeOption::StaticFileExtensions.end()) {
|
|
string val = iter->second;
|
|
const char *valp = val.c_str();
|
|
if (strncmp(valp, "text/", 5) == 0 &&
|
|
(strcmp(valp + 5, "plain") == 0 ||
|
|
strcmp(valp + 5, "html") == 0)) {
|
|
// Apache adds character set for these two types
|
|
val += "; charset=";
|
|
val += RuntimeOption::DefaultCharsetName;
|
|
valp = val.c_str();
|
|
}
|
|
transport->addHeader("Content-Type", valp);
|
|
} else {
|
|
transport->addHeader("Content-Type", "application/octet-stream");
|
|
}
|
|
|
|
time_t base = time(nullptr);
|
|
if (RuntimeOption::ExpiresActive) {
|
|
time_t exp = base + RuntimeOption::ExpiresDefault;
|
|
char age[20];
|
|
snprintf(age, sizeof(age), "max-age=%d", RuntimeOption::ExpiresDefault);
|
|
transport->addHeader("Cache-Control", age);
|
|
transport->addHeader("Expires",
|
|
DateTime(exp, true).toString(DateTime::DateFormat::HttpHeader).c_str());
|
|
}
|
|
|
|
if (mtime) {
|
|
transport->addHeader("Last-Modified",
|
|
DateTime(mtime, true).toString(DateTime::DateFormat::HttpHeader).c_str());
|
|
}
|
|
transport->addHeader("Accept-Ranges", "bytes");
|
|
|
|
for (unsigned int i = 0; i < RuntimeOption::FilesMatches.size(); i++) {
|
|
FilesMatch &rule = *RuntimeOption::FilesMatches[i];
|
|
if (rule.match(cmd)) {
|
|
const vector<string> &headers = rule.getHeaders();
|
|
for (unsigned int j = 0; j < headers.size(); j++) {
|
|
transport->addHeader(String(headers[j]));
|
|
}
|
|
}
|
|
}
|
|
|
|
// misnomer, it means we have made decision on compression, transport
|
|
// should not attempt to compress it.
|
|
transport->disableCompression();
|
|
|
|
transport->sendRaw((void*)data, len, 200, compressed);
|
|
}
|
|
|
|
void HttpRequestHandler::handleRequest(Transport *transport) {
|
|
ExecutionProfiler ep(ThreadInfo::RuntimeFunctions);
|
|
|
|
Logger::OnNewRequest();
|
|
GetAccessLog().onNewRequest();
|
|
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("URL", transport->getUrl());
|
|
|
|
// resolve virtual host
|
|
const VirtualHost *vhost = HttpProtocol::GetVirtualHost(transport);
|
|
assert(vhost);
|
|
if (vhost->disabled() ||
|
|
vhost->isBlocking(transport->getCommand(), transport->getRemoteHost())) {
|
|
transport->sendString("Not Found", 404);
|
|
return;
|
|
}
|
|
|
|
// don't serve the request if it's been sitting in queue for longer than our
|
|
// allowed request timeout.
|
|
int requestTimeoutSeconds = (vhost->getRequestTimeoutSeconds() > 0 ?
|
|
vhost->getRequestTimeoutSeconds() :
|
|
RuntimeOption::RequestTimeoutSeconds);
|
|
if (requestTimeoutSeconds > 0) {
|
|
timespec now;
|
|
Timer::GetMonotonicTime(now);
|
|
const timespec& queueTime = transport->getQueueTime();
|
|
|
|
if (gettime_diff_us(queueTime, now) > requestTimeoutSeconds * 1000000) {
|
|
transport->sendString("Service Unavailable", 503);
|
|
#ifndef __APPLE__
|
|
m_requestTimedOutOnQueue->addValue(1);
|
|
#endif
|
|
return;
|
|
}
|
|
}
|
|
|
|
ServerStats::StartRequest(transport->getCommand().c_str(),
|
|
transport->getRemoteHost(),
|
|
vhost->getName().c_str());
|
|
|
|
// resolve source root
|
|
string host = transport->getHeader("Host");
|
|
SourceRootInfo sourceRootInfo(host.c_str());
|
|
|
|
if (sourceRootInfo.error()) {
|
|
sourceRootInfo.handleError(transport);
|
|
return;
|
|
}
|
|
|
|
// request URI
|
|
string pathTranslation = m_pathTranslation ?
|
|
vhost->getPathTranslation().c_str() : "";
|
|
RequestURI reqURI(vhost, transport, sourceRootInfo.path(), pathTranslation);
|
|
if (reqURI.done()) {
|
|
return; // already handled with redirection or 404
|
|
}
|
|
string path = reqURI.path().data();
|
|
string absPath = reqURI.absolutePath().data();
|
|
|
|
// determine whether we should compress response
|
|
bool compressed = transport->decideCompression();
|
|
|
|
const char *data; int len;
|
|
const char *ext = reqURI.ext();
|
|
|
|
if (reqURI.forbidden()) {
|
|
transport->sendString("Forbidden", 403);
|
|
return;
|
|
}
|
|
|
|
bool cachableDynamicContent =
|
|
(!RuntimeOption::StaticFileGenerators.empty() &&
|
|
RuntimeOption::StaticFileGenerators.find(path) !=
|
|
RuntimeOption::StaticFileGenerators.end());
|
|
|
|
// If this is not a php file, check the static and dynamic content caches
|
|
if (ext && strcasecmp(ext, "php") != 0) {
|
|
if (RuntimeOption::EnableStaticContentCache) {
|
|
bool original = compressed;
|
|
// check against static content cache
|
|
if (StaticContentCache::TheCache.find(path, data, len, compressed)) {
|
|
Util::ScopedMem decompressed_data;
|
|
// (qigao) not calling stat at this point because the timestamp of
|
|
// local cache file is not valuable, maybe misleading. This way
|
|
// the Last-Modified header will not show in response.
|
|
// stat(RuntimeOption::FileCache.c_str(), &st);
|
|
if (!original && compressed) {
|
|
data = gzdecode(data, len);
|
|
if (data == nullptr) {
|
|
throw FatalErrorException("cannot unzip compressed data");
|
|
}
|
|
decompressed_data = const_cast<char*>(data);
|
|
compressed = false;
|
|
}
|
|
sendStaticContent(transport, data, len, 0, compressed, path, ext);
|
|
StaticContentCache::TheFileCache->adviseOutMemory();
|
|
ServerStats::LogPage(path, 200);
|
|
GetAccessLog().log(transport, vhost);
|
|
return;
|
|
}
|
|
}
|
|
|
|
if (RuntimeOption::EnableStaticContentFromDisk) {
|
|
String translated = File::TranslatePath(String(absPath));
|
|
if (!translated.empty()) {
|
|
CstrBuffer sb(translated.data());
|
|
if (sb.valid()) {
|
|
struct stat st;
|
|
st.st_mtime = 0;
|
|
stat(translated.data(), &st);
|
|
sendStaticContent(transport, sb.data(), sb.size(), st.st_mtime,
|
|
false, path, ext);
|
|
ServerStats::LogPage(path, 200);
|
|
GetAccessLog().log(transport, vhost);
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
// check static contents that were generated by dynamic pages
|
|
if (cachableDynamicContent) {
|
|
// check against dynamic content cache
|
|
assert(transport->getUrl());
|
|
string key = path + transport->getUrl();
|
|
if (DynamicContentCache::TheCache.find(key, data, len, compressed)) {
|
|
sendStaticContent(transport, data, len, 0, compressed, path, ext);
|
|
ServerStats::LogPage(path, 200);
|
|
GetAccessLog().log(transport, vhost);
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
// proxy any URLs that not specified in ServeURLs
|
|
if (!RuntimeOption::ProxyOrigin.empty() &&
|
|
((RuntimeOption::UseServeURLs &&
|
|
RuntimeOption::ServeURLs.find(path) ==
|
|
RuntimeOption::ServeURLs.end()) ||
|
|
(RuntimeOption::UseProxyURLs &&
|
|
(RuntimeOption::ProxyURLs.find(path) !=
|
|
RuntimeOption::ProxyURLs.end() ||
|
|
MatchAnyPattern(path, RuntimeOption::ProxyPatterns) ||
|
|
(abs(rand()) % 100) < RuntimeOption::ProxyPercentage)))) {
|
|
for (int i = 0; i < RuntimeOption::ProxyRetry; i++) {
|
|
bool force = (i == RuntimeOption::ProxyRetry - 1); // last one
|
|
if (handleProxyRequest(transport, force)) break;
|
|
}
|
|
return;
|
|
}
|
|
|
|
// record request for debugging purpose
|
|
std::string tmpfile = HttpProtocol::RecordRequest(transport);
|
|
|
|
// main body
|
|
hphp_session_init();
|
|
vhost->setRequestTimeoutSeconds();
|
|
|
|
bool ret = false;
|
|
try {
|
|
ret = executePHPRequest(transport, reqURI, sourceRootInfo,
|
|
cachableDynamicContent);
|
|
} catch (const Eval::DebuggerException &e) {
|
|
transport->sendString(e.what(), 200);
|
|
transport->onSendEnd();
|
|
hphp_context_exit(g_context.getNoCheck(), true, true, transport->getUrl());
|
|
} catch (...) {
|
|
Logger::Error("Unhandled exception in HPHP server engine.");
|
|
}
|
|
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();
|
|
hphp_session_exit();
|
|
|
|
HttpProtocol::ClearRecord(ret, tmpfile);
|
|
}
|
|
|
|
bool HttpRequestHandler::executePHPRequest(Transport *transport,
|
|
RequestURI &reqURI,
|
|
SourceRootInfo &sourceRootInfo,
|
|
bool cachableDynamicContent) {
|
|
ExecutionContext *context = hphp_context_init();
|
|
if (RuntimeOption::ImplicitFlush) {
|
|
context->obSetImplicitFlush(true);
|
|
}
|
|
if (RuntimeOption::EnableOutputBuffering) {
|
|
if (RuntimeOption::OutputHandler.empty()) {
|
|
context->obStart();
|
|
} else {
|
|
context->obStart(String(RuntimeOption::OutputHandler));
|
|
}
|
|
}
|
|
context->setTransport(transport);
|
|
|
|
string file = reqURI.absolutePath().c_str();
|
|
{
|
|
ServerStatsHelper ssh("input");
|
|
HttpProtocol::PrepareSystemVariables(transport, reqURI, sourceRootInfo);
|
|
|
|
if (RuntimeOption::EnableDebugger) {
|
|
Eval::DSandboxInfo sInfo = sourceRootInfo.getSandboxInfo();
|
|
Eval::Debugger::RegisterSandbox(sInfo);
|
|
context->setSandboxId(sInfo.id());
|
|
}
|
|
reqURI.clear();
|
|
sourceRootInfo.clear();
|
|
}
|
|
|
|
int code;
|
|
bool ret = true;
|
|
|
|
if (RuntimeOption::EnableDebugger) {
|
|
Eval::Debugger::InterruptRequestStarted(transport->getUrl());
|
|
}
|
|
|
|
bool error = false;
|
|
std::string errorMsg = "Internal Server Error";
|
|
ret = hphp_invoke(context, file, false, Array(), uninit_null(),
|
|
RuntimeOption::RequestInitFunction,
|
|
RuntimeOption::RequestInitDocument,
|
|
error, errorMsg);
|
|
|
|
if (ret) {
|
|
String content = context->obDetachContents();
|
|
if (cachableDynamicContent && !content.empty()) {
|
|
assert(transport->getUrl());
|
|
string key = file + transport->getUrl();
|
|
DynamicContentCache::TheCache.store(key, content.data(),
|
|
content.size());
|
|
}
|
|
transport->sendRaw((void*)content.data(), content.size());
|
|
code = transport->getResponseCode();
|
|
} else if (error) {
|
|
code = 500;
|
|
|
|
string errorPage = context->getErrorPage().data();
|
|
if (errorPage.empty()) {
|
|
errorPage = RuntimeOption::ErrorDocument500;
|
|
}
|
|
if (!errorPage.empty()) {
|
|
context->obProtect(false);
|
|
context->obEndAll();
|
|
context->obStart();
|
|
context->obProtect(true);
|
|
ret = hphp_invoke(context, errorPage, false, Array(), uninit_null(),
|
|
RuntimeOption::RequestInitFunction,
|
|
RuntimeOption::RequestInitDocument,
|
|
error, errorMsg);
|
|
if (ret) {
|
|
String content = context->obDetachContents();
|
|
transport->sendRaw((void*)content.data(), content.size());
|
|
code = transport->getResponseCode();
|
|
} else {
|
|
Logger::Error("Unable to invoke error page %s", errorPage.c_str());
|
|
errorPage.clear(); // so we fall back to 500 return
|
|
}
|
|
}
|
|
if (errorPage.empty()) {
|
|
if (RuntimeOption::ServerErrorMessage) {
|
|
transport->sendString(errorMsg, 500, false, false, "hphp_invoke");
|
|
} else {
|
|
transport->sendString(RuntimeOption::FatalErrorMessage,
|
|
500, false, false, "hphp_invoke");
|
|
}
|
|
}
|
|
} else {
|
|
code = 404;
|
|
transport->sendString("Not Found", 404);
|
|
}
|
|
|
|
if (RuntimeOption::EnableDebugger) {
|
|
Eval::Debugger::InterruptRequestEnded(transport->getUrl());
|
|
}
|
|
|
|
transport->onSendEnd();
|
|
hphp_context_exit(context, true, true, transport->getUrl());
|
|
ServerStats::LogPage(file, code);
|
|
return ret;
|
|
}
|
|
|
|
bool HttpRequestHandler::handleProxyRequest(Transport *transport, bool force) {
|
|
string url = RuntimeOption::ProxyOrigin + transport->getServerObject();
|
|
|
|
int code = 0;
|
|
std::string error;
|
|
StringBuffer response;
|
|
if (!HttpProtocol::ProxyRequest(transport, force, url, code, error,
|
|
response)) {
|
|
return false;
|
|
}
|
|
if (code == 0) {
|
|
transport->sendString(error, 500, false, false, "handleProxyRequest");
|
|
return true;
|
|
}
|
|
|
|
const char* respData = response.data();
|
|
if (!respData) {
|
|
respData = "";
|
|
}
|
|
transport->sendRaw((void*)respData, response.size(), code);
|
|
return true;
|
|
}
|
|
|
|
bool HttpRequestHandler::MatchAnyPattern
|
|
(const std::string &path, const std::vector<std::string> &patterns) {
|
|
String spath(path.c_str(), path.size(), AttachLiteral);
|
|
for (unsigned int i = 0; i < patterns.size(); i++) {
|
|
Variant ret = preg_match(String(patterns[i].c_str(), patterns[i].size(),
|
|
AttachLiteral),
|
|
spath);
|
|
if (ret.toInt64() > 0) return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
}
|