Arquivos
hhvm/hphp/runtime/base/server/http_request_handler.cpp
T
Stephen Chen fd5d218e8e HPHP: Drop the request when it's been sitting on the select queue longer than the virtual host timeout allows for.
Drop the request when it's been sitting on the select queue longer than the
virtual host timeout allows for. This is orthogonal to the expiration timeout on
the queue itself.

This can be useful if we ever allow select queue timeout to be configured to be
higher than the virtual host request timeout.
2013-07-06 11:12:22 -07:00

437 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"
#include "hphp/util/service_data.h"
namespace HPHP {
///////////////////////////////////////////////////////////////////////////////
IMPLEMENT_THREAD_LOCAL(AccessLog::ThreadData,
HttpRequestHandler::s_accessLogThreadData);
AccessLog HttpRequestHandler::s_accessLog(
&(HttpRequestHandler::getAccessLogThreadData));
HttpRequestHandler::HttpRequestHandler()
: m_pathTranslation(true),
m_requestTimedOutOnQueue(ServiceData::createTimeseries(
"requests_timed_out_on_queue",
{ServiceData::StatsType::COUNT})) {
}
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;
gettime(CLOCK_MONOTONIC, &now);
const timespec& queueTime = transport->getQueueTime();
if (gettime_diff_us(queueTime, now) > requestTimeoutSeconds * 1000000) {
transport->sendString("Service Unavailable", 503);
m_requestTimedOutOnQueue->addValue(1);
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;
}
///////////////////////////////////////////////////////////////////////////////
}