PageRenderTime 64ms CodeModel.GetById 26ms RepoModel.GetById 1ms app.codeStats 0ms

/src/runtime/base/server/http_request_handler.cpp

https://github.com/leonhong/hiphop-php
C++ | 398 lines | 319 code | 43 blank | 36 comment | 64 complexity | d40f741eb36128c27ac9d8a47147cc10 MD5 | raw file
  1. /*
  2. +----------------------------------------------------------------------+
  3. | HipHop for PHP |
  4. +----------------------------------------------------------------------+
  5. | Copyright (c) 2010 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. +----------------------------------------------------------------------+
  15. */
  16. #include <runtime/base/server/http_request_handler.h>
  17. #include <runtime/base/program_functions.h>
  18. #include <runtime/base/execution_context.h>
  19. #include <runtime/base/runtime_option.h>
  20. #include <util/timer.h>
  21. #include <runtime/base/server/static_content_cache.h>
  22. #include <runtime/base/server/dynamic_content_cache.h>
  23. #include <runtime/base/server/server_stats.h>
  24. #include <util/network.h>
  25. #include <runtime/base/preg.h>
  26. #include <runtime/ext/ext_function.h>
  27. #include <runtime/base/server/access_log.h>
  28. #include <runtime/base/server/source_root_info.h>
  29. #include <runtime/base/server/request_uri.h>
  30. #include <runtime/base/server/http_protocol.h>
  31. #include <runtime/base/time/datetime.h>
  32. #include <runtime/eval/debugger/debugger.h>
  33. using namespace std;
  34. namespace HPHP {
  35. ///////////////////////////////////////////////////////////////////////////////
  36. IMPLEMENT_THREAD_LOCAL(AccessLog::ThreadData,
  37. HttpRequestHandler::s_accessLogThreadData);
  38. AccessLog HttpRequestHandler::s_accessLog(
  39. &(HttpRequestHandler::getAccessLogThreadData));
  40. HttpRequestHandler::HttpRequestHandler()
  41. : m_pathTranslation(true) {
  42. }
  43. void HttpRequestHandler::sendStaticContent(Transport *transport,
  44. const char *data, int len,
  45. time_t mtime,
  46. bool compressed,
  47. const std::string &cmd) {
  48. size_t pos = cmd.rfind('.');
  49. ASSERT(pos != string::npos);
  50. const char *ext = cmd.c_str() + pos + 1;
  51. map<string, string>::const_iterator iter =
  52. RuntimeOption::StaticFileExtensions.find(ext);
  53. if (iter != RuntimeOption::StaticFileExtensions.end()) {
  54. string val = iter->second;
  55. if (val == "text/plain" || val == "text/html") {
  56. // Apache adds character set for these two types
  57. val += "; charset=";
  58. val += RuntimeOption::DefaultCharsetName;
  59. }
  60. transport->addHeader("Content-Type", val.c_str());
  61. }
  62. time_t base = time(NULL);
  63. if (RuntimeOption::ExpiresActive) {
  64. time_t expires = base + RuntimeOption::ExpiresDefault;
  65. char age[20];
  66. snprintf(age, sizeof(age), "max-age=%d", RuntimeOption::ExpiresDefault);
  67. transport->addHeader("Cache-Control", age);
  68. transport->addHeader
  69. ("Expires", DateTime(expires, true).toString(DateTime::HttpHeader));
  70. }
  71. if (mtime) {
  72. transport->addHeader
  73. ("Last-Modified", DateTime(mtime, true).toString(DateTime::HttpHeader));
  74. }
  75. transport->addHeader("Accept-Ranges", "bytes");
  76. for (unsigned int i = 0; i < RuntimeOption::FilesMatches.size(); i++) {
  77. FilesMatch &rule = *RuntimeOption::FilesMatches[i];
  78. if (rule.match(cmd)) {
  79. const vector<string> &headers = rule.getHeaders();
  80. for (unsigned int j = 0; j < headers.size(); j++) {
  81. transport->addHeader(String(headers[j]));
  82. }
  83. }
  84. }
  85. // misnomer, it means we have made decision on compression, transport
  86. // should not attempt to compress it.
  87. transport->disableCompression();
  88. transport->sendRaw((void*)data, len, 200, compressed);
  89. }
  90. void HttpRequestHandler::handleRequest(Transport *transport) {
  91. Logger::OnNewRequest();
  92. GetAccessLog().onNewRequest();
  93. transport->enableCompression();
  94. ServerStatsHelper ssh("all", true);
  95. Logger::Verbose("receiving %s", transport->getCommand().c_str());
  96. // will clear all extra logging when this function goes out of scope
  97. StackTraceNoHeap::ExtraLoggingClearer clearer;
  98. StackTraceNoHeap::AddExtraLogging("URL", transport->getUrl());
  99. // resolve virtual host
  100. const VirtualHost *vhost = HttpProtocol::GetVirtualHost(transport);
  101. ASSERT(vhost);
  102. if (vhost->disabled() ||
  103. vhost->isBlocking(transport->getCommand(), transport->getRemoteHost())) {
  104. transport->sendString("Not Found", 404);
  105. return;
  106. }
  107. ServerStats::StartRequest(transport->getCommand().c_str(),
  108. transport->getRemoteHost(),
  109. vhost->getName().c_str());
  110. // resolve source root
  111. string host = transport->getHeader("Host");
  112. SourceRootInfo sourceRootInfo(host.c_str());
  113. if (sourceRootInfo.error()) {
  114. sourceRootInfo.handleError(transport);
  115. return;
  116. }
  117. // request URI
  118. string pathTranslation = m_pathTranslation ?
  119. vhost->getPathTranslation().c_str() : "";
  120. RequestURI reqURI(vhost, transport, sourceRootInfo.path(), pathTranslation);
  121. if (reqURI.done()) {
  122. return; // already handled with redirection or 404
  123. }
  124. string path = reqURI.path().data();
  125. string absPath = reqURI.absolutePath().data();
  126. // determine whether we should compress response
  127. bool compressed = transport->decideCompression();
  128. const char *data; int len;
  129. size_t pos = path.rfind('.');
  130. const char *ext = (pos != string::npos) ? (path.c_str() + pos + 1) : NULL;
  131. bool cachableDynamicContent =
  132. (!RuntimeOption::StaticFileGenerators.empty() &&
  133. RuntimeOption::StaticFileGenerators.find(path) !=
  134. RuntimeOption::StaticFileGenerators.end());
  135. // If this is not a php file, check the static and dynamic content caches
  136. if (ext && strcasecmp(ext, "php") != 0) {
  137. if (RuntimeOption::EnableStaticContentCache) {
  138. bool original = compressed;
  139. // check against static content cache
  140. if (StaticContentCache::TheCache.find(path, data, len, compressed)) {
  141. struct stat st;
  142. st.st_mtime = 0;
  143. String str;
  144. // (qigao) not calling stat at this point because the timestamp of
  145. // local cache file is not valuable, maybe misleading. This way
  146. // the Last-Modified header will not show in response.
  147. // stat(RuntimeOption::FileCache.c_str(), &st);
  148. if (!original && compressed) {
  149. data = gzdecode(data, len);
  150. if (data == NULL) {
  151. throw FatalErrorException("cannot unzip compressed data");
  152. }
  153. compressed = false;
  154. str.assign(data, len, AttachString);
  155. }
  156. sendStaticContent(transport, data, len, st.st_mtime, compressed, path);
  157. StaticContentCache::TheFileCache->adviseOutMemory();
  158. ServerStats::LogPage(path, 200);
  159. return;
  160. }
  161. }
  162. if (RuntimeOption::EnableStaticContentFromDisk &&
  163. RuntimeOption::StaticFileExtensions.find(ext) !=
  164. RuntimeOption::StaticFileExtensions.end()) {
  165. String translated = File::TranslatePath(String(absPath));
  166. if (!translated.empty()) {
  167. StringBuffer sb(translated.data());
  168. if (sb.valid()) {
  169. struct stat st;
  170. st.st_mtime = 0;
  171. stat(translated.data(), &st);
  172. sendStaticContent(transport, sb.data(), sb.size(), st.st_mtime,
  173. false, path);
  174. ServerStats::LogPage(path, 200);
  175. return;
  176. }
  177. }
  178. }
  179. // check static contents that were generated by dynamic pages
  180. if (cachableDynamicContent) {
  181. // check against dynamic content cache
  182. ASSERT(transport->getUrl());
  183. string key = path + transport->getUrl();
  184. if (DynamicContentCache::TheCache.find(key, data, len, compressed)) {
  185. sendStaticContent(transport, data, len, 0, compressed, path);
  186. ServerStats::LogPage(path, 200);
  187. return;
  188. }
  189. }
  190. }
  191. // proxy any URLs that not specified in ServeURLs
  192. if (!RuntimeOption::ProxyOrigin.empty() &&
  193. ((RuntimeOption::UseServeURLs &&
  194. RuntimeOption::ServeURLs.find(path) ==
  195. RuntimeOption::ServeURLs.end()) ||
  196. (RuntimeOption::UseProxyURLs &&
  197. (RuntimeOption::ProxyURLs.find(path) !=
  198. RuntimeOption::ProxyURLs.end() ||
  199. MatchAnyPattern(path, RuntimeOption::ProxyPatterns) ||
  200. (abs(rand()) % 100) < RuntimeOption::ProxyPercentage)))) {
  201. for (int i = 0; i < RuntimeOption::ProxyRetry; i++) {
  202. bool force = (i == RuntimeOption::ProxyRetry - 1); // last one
  203. if (handleProxyRequest(transport, force)) break;
  204. }
  205. return;
  206. }
  207. // record request for debugging purpose
  208. std::string tmpfile = HttpProtocol::RecordRequest(transport);
  209. // main body
  210. hphp_session_init();
  211. bool ret = false;
  212. try {
  213. ret = executePHPRequest(transport, reqURI, sourceRootInfo,
  214. cachableDynamicContent);
  215. } catch (...) {
  216. Logger::Error("Unhandled exception in HPHP server engine.");
  217. }
  218. GetAccessLog().log(transport);
  219. hphp_session_exit();
  220. HttpProtocol::ClearRecord(ret, tmpfile);
  221. }
  222. bool HttpRequestHandler::executePHPRequest(Transport *transport,
  223. RequestURI &reqURI,
  224. SourceRootInfo &sourceRootInfo,
  225. bool cachableDynamicContent) {
  226. ExecutionContext *context = hphp_context_init();
  227. if (RuntimeOption::ImplicitFlush) {
  228. context->obSetImplicitFlush(true);
  229. }
  230. if (RuntimeOption::EnableOutputBuffering) {
  231. if (RuntimeOption::OutputHandler.empty()) {
  232. context->obStart();
  233. } else {
  234. context->obStart(String(RuntimeOption::OutputHandler));
  235. }
  236. }
  237. context->setTransport(transport);
  238. string file = reqURI.absolutePath().c_str();
  239. {
  240. ServerStatsHelper ssh("input");
  241. HttpProtocol::PrepareSystemVariables(transport, reqURI, sourceRootInfo);
  242. reqURI.clear();
  243. sourceRootInfo.clear();
  244. }
  245. int code;
  246. bool ret = true;
  247. if (!RuntimeOption::ForbiddenFileExtensions.empty()) {
  248. size_t pos = file.rfind('.');
  249. if (pos != string::npos) {
  250. const char *ext = file.c_str() + pos + 1;
  251. if (RuntimeOption::ForbiddenFileExtensions.find(ext) !=
  252. RuntimeOption::ForbiddenFileExtensions.end()) {
  253. code = 403;
  254. transport->sendString("Forbidden", 403);
  255. ret = false;
  256. }
  257. }
  258. }
  259. if (ret) {
  260. if (RuntimeOption::EnableDebugger) {
  261. Eval::Debugger::InterruptRequestStarted(transport->getUrl());
  262. }
  263. bool error = false;
  264. std::string errorMsg = "Internal Server Error";
  265. ret = hphp_invoke(context, file, false, Array(), null,
  266. RuntimeOption::WarmupDocument,
  267. RuntimeOption::RequestInitFunction,
  268. RuntimeOption::RequestInitDocument,
  269. error, errorMsg);
  270. if (ret) {
  271. String content = context->obDetachContents();
  272. if (cachableDynamicContent && !content.empty()) {
  273. ASSERT(transport->getUrl());
  274. string key = file + transport->getUrl();
  275. DynamicContentCache::TheCache.store(key, content.data(),
  276. content.size());
  277. }
  278. code = 200;
  279. transport->sendRaw((void*)content.data(), content.size());
  280. } else if (error) {
  281. code = 500;
  282. string errorPage = context->getErrorPage().data();
  283. if (errorPage.empty()) {
  284. errorPage = RuntimeOption::ErrorDocument500;
  285. }
  286. if (!errorPage.empty()) {
  287. context->obProtect(false);
  288. context->obEndAll();
  289. context->obStart();
  290. context->obProtect(true);
  291. ret = hphp_invoke(context, errorPage, false, Array(), null,
  292. RuntimeOption::WarmupDocument,
  293. RuntimeOption::RequestInitFunction,
  294. RuntimeOption::RequestInitDocument,
  295. error, errorMsg);
  296. if (ret) {
  297. String content = context->obDetachContents();
  298. transport->sendRaw((void*)content.data(), content.size());
  299. } else {
  300. errorPage.clear(); // so we fall back to 500 return
  301. }
  302. }
  303. if (errorPage.empty()) {
  304. if (RuntimeOption::ServerErrorMessage) {
  305. transport->sendString(errorMsg, 500);
  306. } else {
  307. transport->sendString(RuntimeOption::FatalErrorMessage, 500);
  308. }
  309. }
  310. } else {
  311. code = 404;
  312. transport->sendString("Not Found", 404);
  313. }
  314. if (RuntimeOption::EnableDebugger) {
  315. Eval::Debugger::InterruptRequestEnded(transport->getUrl());
  316. }
  317. }
  318. transport->onSendEnd();
  319. ServerStats::LogPage(file, code);
  320. hphp_context_exit(context, true, true, transport->getUrl());
  321. return ret;
  322. }
  323. bool HttpRequestHandler::handleProxyRequest(Transport *transport, bool force) {
  324. string url = RuntimeOption::ProxyOrigin + transport->getServerObject();
  325. int code = 0;
  326. std::string error;
  327. StringBuffer response;
  328. if (!HttpProtocol::ProxyRequest(transport, force, url, code, error,
  329. response)) {
  330. return false;
  331. }
  332. if (code == 0) {
  333. transport->sendString(error, 500);
  334. return true;
  335. }
  336. const char* respData = response.data();
  337. if (!respData) {
  338. respData = "";
  339. }
  340. transport->sendRaw((void*)respData, response.size(), code);
  341. return true;
  342. }
  343. bool HttpRequestHandler::MatchAnyPattern
  344. (const std::string &path, const std::vector<std::string> &patterns) {
  345. String spath(path.c_str(), path.size(), AttachLiteral);
  346. for (unsigned int i = 0; i < patterns.size(); i++) {
  347. Variant ret = preg_match(String(patterns[i].c_str(), patterns[i].size(),
  348. AttachLiteral),
  349. spath);
  350. if (ret.toInt64() > 0) return true;
  351. }
  352. return false;
  353. }
  354. ///////////////////////////////////////////////////////////////////////////////
  355. }