de-dup THREAD_LOCAL macros
[hiphop-php.git] / hphp / runtime / server / http-request-handler.cpp
blobd4c5d694acec568020941c1b8dd2733a2ac318cf
1 /*
2 +----------------------------------------------------------------------+
3 | HipHop for PHP |
4 +----------------------------------------------------------------------+
5 | Copyright (c) 2010-present Facebook, Inc. (http://www.facebook.com) |
6 +----------------------------------------------------------------------+
7 | This source file is subject to version 3.01 of the PHP license, |
8 | that is bundled with this package in the file LICENSE, and is |
9 | available through the world-wide-web at the following url: |
10 | http://www.php.net/license/3_01.txt |
11 | If you did not receive a copy of the PHP license and are unable to |
12 | obtain it through the world-wide-web, please send a note to |
13 | license@php.net so we can mail you a copy immediately. |
14 +----------------------------------------------------------------------+
16 #include "hphp/runtime/server/http-request-handler.h"
18 #include <string>
19 #include <vector>
21 #include "hphp/runtime/base/datetime.h"
22 #include "hphp/runtime/base/execution-context.h"
23 #include "hphp/runtime/base/hhprof.h"
24 #include "hphp/runtime/base/init-fini-node.h"
25 #include "hphp/runtime/base/preg.h"
26 #include "hphp/runtime/base/program-functions.h"
27 #include "hphp/runtime/base/resource-data.h"
28 #include "hphp/runtime/base/runtime-option.h"
29 #include "hphp/runtime/debugger/debugger.h"
30 #include "hphp/runtime/ext/extension-registry.h"
31 #include "hphp/runtime/ext/std/ext_std_function.h"
32 #include "hphp/runtime/ext/xdebug/status.h"
33 #include "hphp/runtime/server/access-log.h"
34 #include "hphp/runtime/server/files-match.h"
35 #include "hphp/runtime/server/http-protocol.h"
36 #include "hphp/runtime/server/request-uri.h"
37 #include "hphp/runtime/server/server-stats.h"
38 #include "hphp/runtime/server/source-root-info.h"
39 #include "hphp/runtime/server/static-content-cache.h"
40 #include "hphp/runtime/vm/debugger-hook.h"
42 #include "hphp/util/alloc.h"
43 #include "hphp/util/hardware-counter.h"
44 #include "hphp/util/lock.h"
45 #include "hphp/util/mutex.h"
46 #include "hphp/util/network.h"
47 #include "hphp/util/service-data.h"
48 #include "hphp/util/stack-trace.h"
49 #include "hphp/util/struct-log.h"
50 #include "hphp/util/timer.h"
52 namespace HPHP {
54 using std::string;
55 using std::vector;
57 ///////////////////////////////////////////////////////////////////////////////
59 static ReadWriteMutex s_proxyMutex;
60 static __thread unsigned int s_randState = 0xfaceb00c;
62 static bool matchAnyPattern(const std::string &path,
63 const std::vector<std::string> &patterns) {
64 String spath(path.c_str(), path.size(), CopyString);
65 for (unsigned int i = 0; i < patterns.size(); i++) {
66 Variant ret = preg_match(String(patterns[i].c_str(), patterns[i].size(),
67 CopyString),
68 spath);
69 if (ret.toInt64() > 0) return true;
71 return false;
75 * Returns true iff a request to the given path should be delegated to the
76 * proxy origin.
78 static bool shouldProxyPath(const std::string& path) {
79 ReadLock lock(s_proxyMutex);
81 if (RuntimeOption::ProxyOriginRaw.empty()) return false;
83 if (RuntimeOption::UseServeURLs && RuntimeOption::ServeURLs.count(path)) {
84 return true;
87 if (RuntimeOption::UseProxyURLs) {
88 if (RuntimeOption::ProxyURLs.count(path)) return true;
89 if (matchAnyPattern(path, RuntimeOption::ProxyPatterns)) return true;
92 if (RuntimeOption::ProxyPercentageRaw > 0) {
93 if ((abs(rand_r(&s_randState)) % 100) < RuntimeOption::ProxyPercentageRaw) {
94 return true;
98 return false;
101 static std::string getProxyPath(const char* origPath) {
102 ReadLock lock(s_proxyMutex);
104 return RuntimeOption::ProxyOriginRaw + origPath;
107 void setProxyOriginPercentage(const std::string& origin, int percentage) {
108 WriteLock lock(s_proxyMutex);
110 RuntimeOption::ProxyOriginRaw = origin;
111 RuntimeOption::ProxyPercentageRaw = percentage;
112 Logger::Warning("Updated proxy origin to `%s' and percentage to %d\n",
113 origin.c_str(), percentage);
116 ///////////////////////////////////////////////////////////////////////////////
118 THREAD_LOCAL(AccessLog::ThreadData, HttpRequestHandler::s_accessLogThreadData);
120 AccessLog HttpRequestHandler::s_accessLog(
121 &(HttpRequestHandler::getAccessLogThreadData));
123 HttpRequestHandler::HttpRequestHandler(int timeout)
124 : RequestHandler(timeout), m_pathTranslation(true)
125 , m_requestTimedOutOnQueue(ServiceData::createTimeSeries(
126 "requests_timed_out_on_queue",
127 {ServiceData::StatsType::COUNT})) { }
129 void HttpRequestHandler::sendStaticContent(Transport *transport,
130 const char *data, int len,
131 time_t mtime,
132 bool compressed,
133 const std::string &cmd,
134 const char *ext) {
135 assert(ext);
136 assert(cmd.rfind('.') != std::string::npos);
137 assert(strcmp(ext, cmd.c_str() + cmd.rfind('.') + 1) == 0);
139 auto iter = RuntimeOption::StaticFileExtensions.find(ext);
140 if (iter != RuntimeOption::StaticFileExtensions.end()) {
141 string val = iter->second;
142 const char *valp = val.c_str();
143 if (strncmp(valp, "text/", 5) == 0 &&
144 (strcmp(valp + 5, "plain") == 0 ||
145 strcmp(valp + 5, "html") == 0)) {
146 // Apache adds character set for these two types
147 val += "; charset=";
148 val += IniSetting::Get("default_charset");
149 valp = val.c_str();
151 transport->addHeader("Content-Type", valp);
152 } else {
153 transport->addHeader("Content-Type", "application/octet-stream");
156 time_t base = time(nullptr);
157 if (RuntimeOption::ExpiresActive) {
158 time_t exp = base + RuntimeOption::ExpiresDefault;
159 char age[20];
160 snprintf(age, sizeof(age), "max-age=%d", RuntimeOption::ExpiresDefault);
161 transport->addHeader("Cache-Control", age);
162 transport->addHeader("Expires",
163 req::make<DateTime>(exp, true)->toString(
164 DateTime::DateFormat::HttpHeader).c_str());
167 if (mtime) {
168 transport->addHeader("Last-Modified",
169 req::make<DateTime>(mtime, true)->toString(
170 DateTime::DateFormat::HttpHeader).c_str());
172 transport->addHeader("Accept-Ranges", "bytes");
174 for (unsigned int i = 0; i < RuntimeOption::FilesMatches.size(); i++) {
175 FilesMatch &rule = *RuntimeOption::FilesMatches[i];
176 if (rule.match(cmd)) {
177 const vector<string> &headers = rule.getHeaders();
178 for (unsigned int j = 0; j < headers.size(); j++) {
179 transport->addHeader(String(headers[j]));
184 // misnomer, it means we have made decision on compression, transport
185 // should not attempt to compress it.
186 transport->disableCompression();
188 transport->sendRaw((void*)data, len, 200, compressed);
189 transport->onSendEnd();
192 void HttpRequestHandler::logToAccessLog(Transport* transport) {
193 GetAccessLog().onNewRequest();
194 GetAccessLog().log(transport, VirtualHost::GetCurrent());
197 void HttpRequestHandler::setupRequest(Transport* transport) {
198 MemoryManager::requestInit();
199 HHProf::Request::Setup(transport);
201 g_context.getCheck();
202 GetAccessLog().onNewRequest();
204 // Set current virtual host.
205 HttpProtocol::GetVirtualHost(transport);
208 void HttpRequestHandler::teardownRequest(Transport* transport) noexcept {
209 SCOPE_EXIT { always_assert(tl_heap->empty()); };
211 const VirtualHost *vhost = VirtualHost::GetCurrent();
212 GetAccessLog().log(transport, vhost);
214 // HPHP logs may need to access data in ServerStats, so we have to clear the
215 // hashtable after writing the log entry.
216 ServerStats::Reset();
217 m_sourceRootInfo.clear();
219 if (is_hphp_session_initialized()) {
220 hphp_session_exit(transport);
221 } else {
222 // Even though there are no sessions, memory is allocated to perform
223 // INI setting bindings when the thread is initialized.
224 hphp_memory_cleanup();
227 MemoryManager::requestShutdown();
228 HHProf::Request::Teardown();
231 void HttpRequestHandler::handleRequest(Transport *transport) {
232 ExecutionProfiler ep(ThreadInfo::RuntimeFunctions);
234 Logger::OnNewRequest();
235 transport->enableCompression();
237 ServerStatsHelper ssh("all",
238 ServerStatsHelper::TRACK_MEMORY |
239 ServerStatsHelper::TRACK_HWINST);
240 Logger::Verbose("receiving %s", transport->getCommand().c_str());
242 // will clear all extra logging when this function goes out of scope
243 StackTraceNoHeap::ExtraLoggingClearer clearer;
244 StackTraceNoHeap::AddExtraLogging("URL", transport->getUrl());
246 // resolve virtual host
247 const VirtualHost *vhost = VirtualHost::GetCurrent();
248 assert(vhost);
249 if (vhost->disabled() ||
250 vhost->isBlocking(transport->getCommand(), transport->getRemoteHost())) {
251 transport->sendString("Not Found", 404);
252 transport->onSendEnd();
253 return;
256 // don't serve the request if it's been sitting in queue for longer than our
257 // allowed request timeout.
258 int requestTimeoutSeconds =
259 vhost->getRequestTimeoutSeconds(getDefaultTimeout());
260 if (requestTimeoutSeconds > 0) {
261 timespec now;
262 Timer::GetMonotonicTime(now);
263 const timespec& queueTime = transport->getQueueTime();
265 if (gettime_diff_us(queueTime, now) > requestTimeoutSeconds * 1000000) {
266 transport->sendString("Service Unavailable", 503);
267 transport->onSendEnd();
268 m_requestTimedOutOnQueue->addValue(1);
269 return;
273 ServerStats::StartRequest(transport->getCommand().c_str(),
274 transport->getRemoteHost(),
275 vhost->getName().c_str());
277 // resolve source root
278 always_assert(!m_sourceRootInfo.hasValue());
279 m_sourceRootInfo.emplace(transport);
280 if (m_sourceRootInfo->error()) {
281 m_sourceRootInfo->handleError(transport);
282 return;
285 // request URI
286 string pathTranslation = m_pathTranslation ?
287 vhost->getPathTranslation().c_str() : "";
288 RequestURI reqURI(vhost, transport, pathTranslation,
289 m_sourceRootInfo->path());
290 if (reqURI.done()) {
291 return; // already handled with redirection or 404
293 string path = reqURI.path().data();
294 string absPath = reqURI.absolutePath().data();
296 // determine whether we should compress response
297 bool compressed = transport->decideCompression();
299 const char *data; int len;
300 const char *ext = reqURI.ext();
302 if (reqURI.forbidden()) {
303 transport->sendString("Forbidden", 403);
304 transport->onSendEnd();
305 return;
308 // Determine which extensions should be treated as php
309 // source code. If the execution engine doesn't understand
310 // the source, the content will be spit out verbatim.
311 bool treatAsContent = ext &&
312 strcasecmp(ext, "php") != 0 &&
313 strcasecmp(ext, "hh") != 0 &&
314 (RuntimeOption::PhpFileExtensions.empty() ||
315 !RuntimeOption::PhpFileExtensions.count(ext));
317 // If this is not a php file, check the static content cache
318 if (treatAsContent) {
319 bool original = compressed;
320 // check against static content cache
321 if (StaticContentCache::TheCache.find(path, data, len, compressed)) {
322 ScopedMem decompressed_data;
323 // (qigao) not calling stat at this point because the timestamp of
324 // local cache file is not valuable, maybe misleading. This way
325 // the Last-Modified header will not show in response.
326 // stat(RuntimeOption::FileCache.c_str(), &st);
327 if (!original && compressed) {
328 data = gzdecode(data, len);
329 if (data == nullptr) {
330 raise_fatal_error("cannot unzip compressed data");
332 decompressed_data = const_cast<char*>(data);
333 compressed = false;
335 sendStaticContent(transport, data, len, 0, compressed, path, ext);
336 ServerStats::LogPage(path, 200);
337 return;
340 if (RuntimeOption::EnableStaticContentFromDisk) {
341 String translated = File::TranslatePath(String(absPath));
342 if (!translated.empty()) {
343 CstrBuffer sb(translated.data());
344 if (sb.valid()) {
345 struct stat st;
346 st.st_mtime = 0;
347 stat(translated.data(), &st);
348 sendStaticContent(transport, sb.data(), sb.size(), st.st_mtime,
349 false, path, ext);
350 ServerStats::LogPage(path, 200);
351 return;
357 // proxy any URLs that not specified in ServeURLs
358 if (shouldProxyPath(path)) {
359 for (int i = 0; i < RuntimeOption::ProxyRetry; i++) {
360 bool force = (i == RuntimeOption::ProxyRetry - 1); // last one
361 if (handleProxyRequest(transport, force)) break;
363 return;
366 // record request for debugging purpose
367 std::string tmpfile = HttpProtocol::RecordRequest(transport);
369 // main body
370 hphp_session_init();
371 ThreadInfo::s_threadInfo->m_reqInjectionData.
372 setTimeout(requestTimeoutSeconds);
374 bool ret = false;
375 try {
376 ret = executePHPRequest(transport, reqURI, m_sourceRootInfo.value());
377 } catch (...) {
378 string emsg;
379 string response;
380 int code = 500;
381 try {
382 throw;
383 } catch (const Eval::DebuggerException &e) {
384 code = 200;
385 response = e.what();
386 } catch (const XDebugExitExn& e) {
387 code = 200;
388 response = e.what();
389 } catch (Object &e) {
390 try {
391 emsg = e.toString().data();
392 } catch (...) {
393 emsg = "Unknown";
395 } catch (const std::exception &e) {
396 emsg = e.what();
397 } catch (...) {
398 emsg = "Unknown";
400 g_context->onShutdownPostSend();
401 Eval::Debugger::InterruptPSPEnded(transport->getUrl());
402 if (code != 200) {
403 Logger::Error("Unhandled server exception: %s", emsg.c_str());
405 transport->sendString(response, code);
406 transport->onSendEnd();
407 hphp_context_exit();
409 HttpProtocol::ClearRecord(ret, tmpfile);
412 void HttpRequestHandler::abortRequest(Transport* transport) {
413 // TODO: t5284137 add some tests for abortRequest
414 transport->sendString("Service Unavailable", 503);
415 transport->onSendEnd();
418 bool HttpRequestHandler::executePHPRequest(Transport *transport,
419 RequestURI &reqURI,
420 SourceRootInfo &sourceRootInfo) {
421 auto context = g_context.getNoCheck();
422 OBFlags obFlags = OBFlags::Default;
423 if (transport->getHTTPVersion() != "1.1") {
424 obFlags |= OBFlags::OutputDisabled;
426 context->obStart(uninit_null(), 0, obFlags);
427 context->obProtect(true);
428 if (RuntimeOption::ImplicitFlush) {
429 context->obSetImplicitFlush(true);
431 if (RuntimeOption::EnableOutputBuffering) {
432 if (RuntimeOption::OutputHandler.empty()) {
433 context->obStart();
434 } else {
435 context->obStart(String(RuntimeOption::OutputHandler));
438 context->setTransport(transport);
439 InitFiniNode::RequestStart();
441 string file = reqURI.absolutePath().c_str();
443 ServerStatsHelper ssh("input");
444 HttpProtocol::PrepareSystemVariables(transport, reqURI, sourceRootInfo);
445 InitFiniNode::GlobalsInit();
447 if (RuntimeOption::EnableDebugger) {
448 Eval::DSandboxInfo sInfo = sourceRootInfo.getSandboxInfo();
449 Eval::Debugger::RegisterSandbox(sInfo);
450 context->setSandboxId(sInfo.id());
452 reqURI.clear();
453 sourceRootInfo.clear();
456 int code;
457 bool ret = true;
459 // Let the debugger initialize.
460 // FIXME: hphpd can be initialized this way as well
461 DEBUGGER_ATTACHED_ONLY(phpDebuggerRequestInitHook());
462 if (RuntimeOption::EnableDebugger) {
463 Eval::Debugger::InterruptRequestStarted(transport->getUrl());
466 bool error = false;
467 std::string errorMsg = "Internal Server Error";
468 ret = hphp_invoke(context, file, false, Array(), uninit_null(),
469 RuntimeOption::RequestInitFunction,
470 RuntimeOption::RequestInitDocument,
471 error, errorMsg,
472 true /* once */,
473 false /* warmupOnly */,
474 false /* richErrorMessage */);
476 if (ret) {
477 String content = context->obDetachContents();
478 transport->sendRaw((void*)content.data(), content.size());
479 code = transport->getResponseCode();
480 } else if (error) {
481 code = 500;
483 string errorPage = context->getErrorPage().data();
484 if (errorPage.empty()) {
485 errorPage = RuntimeOption::ErrorDocument500;
487 if (!errorPage.empty()) {
488 context->obProtect(false);
489 context->obEndAll();
490 context->obStart();
491 context->obProtect(true);
492 ret = hphp_invoke(context, errorPage, false, Array(), uninit_null(),
493 RuntimeOption::RequestInitFunction,
494 RuntimeOption::RequestInitDocument,
495 error, errorMsg,
496 true /* once */,
497 false /* warmupOnly */,
498 false /* richErrorMessage */);
499 if (ret) {
500 String content = context->obDetachContents();
501 transport->sendRaw((void*)content.data(), content.size());
502 code = transport->getResponseCode();
503 } else {
504 Logger::Error("Unable to invoke error page %s", errorPage.c_str());
505 errorPage.clear(); // so we fall back to 500 return
508 if (errorPage.empty()) {
509 if (RuntimeOption::ServerErrorMessage) {
510 transport->sendString(errorMsg, 500, false, false, "hphp_invoke");
511 } else {
512 transport->sendString(RuntimeOption::FatalErrorMessage,
513 500, false, false, "hphp_invoke");
516 } else {
517 code = 404;
518 transport->sendString("RequestInitDocument Not Found", 404);
521 if (RuntimeOption::EnableDebugger) {
522 Eval::Debugger::InterruptRequestEnded(transport->getUrl());
525 std::unique_ptr<StructuredLogEntry> entry;
526 if (RuntimeOption::EvalProfileHWStructLog) {
527 entry = std::make_unique<StructuredLogEntry>();
528 entry->setInt("response_code", code);
529 auto queueBegin = transport->getQueueTime();
530 auto const queueTimeUs = gettime_diff_us(queueBegin,
531 transport->getWallTime());
532 entry->setInt("queue-time-us", queueTimeUs);
534 HardwareCounter::UpdateServiceData(transport->getCpuTime(),
535 transport->getWallTime(),
536 entry.get(),
537 false /*psp*/);
538 if (entry) StructuredLog::log("hhvm_request_perf", *entry);
540 // If we have registered post-send shutdown functions, end the request before
541 // executing them. If we don't, be compatible with Zend by allowing usercode
542 // in hphp_context_shutdown to run before we end the request.
543 bool hasPostSend =
544 context->hasShutdownFunctions(ExecutionContext::ShutdownType::PostSend);
545 if (hasPostSend) {
546 transport->onSendEnd();
548 context->onShutdownPostSend();
549 Eval::Debugger::InterruptPSPEnded(transport->getUrl());
550 hphp_context_shutdown();
551 if (!hasPostSend) {
552 transport->onSendEnd();
554 hphp_context_exit(false);
555 ServerStats::LogPage(file, code);
556 return ret;
559 bool HttpRequestHandler::handleProxyRequest(Transport *transport, bool force) {
560 auto const url = getProxyPath(transport->getServerObject());
562 int code = 0;
563 std::string error;
564 StringBuffer response;
565 if (!HttpProtocol::ProxyRequest(transport, force, url, code, error,
566 response)) {
567 return false;
569 if (code == 0) {
570 transport->sendString(error, 500, false, false, "handleProxyRequest");
571 return true;
574 const char* respData = response.data();
575 if (!respData) {
576 respData = "";
578 transport->sendRaw((void*)respData, response.size(), code);
579 return true;
582 ///////////////////////////////////////////////////////////////////////////////