Updating submodules
[hiphop-php.git] / hphp / runtime / server / transport.cpp
blobd1cea95fbada487393945257c890ed66b5b06772
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 +----------------------------------------------------------------------+
17 #include "hphp/runtime/server/transport.h"
19 #include "hphp/runtime/server/server.h"
20 #include "hphp/runtime/server/upload.h"
21 #include "hphp/runtime/server/server-stats.h"
22 #include "hphp/runtime/server/stream-transport.h"
23 #include "hphp/runtime/base/builtin-functions.h"
24 #include "hphp/runtime/base/file.h"
25 #include "hphp/runtime/base/string-util.h"
26 #include "hphp/runtime/base/datetime.h"
27 #include "hphp/runtime/base/runtime-option.h"
28 #include "hphp/runtime/base/url.h"
29 #include "hphp/runtime/base/zend-url.h"
30 #include "hphp/runtime/base/tv-type.h"
31 #include "hphp/runtime/server/access-log.h"
32 #include "hphp/runtime/server/http-protocol.h"
33 #include "hphp/runtime/server/compression.h"
34 #include "hphp/runtime/ext/openssl/ext_openssl.h"
35 #include "hphp/runtime/ext/string/ext_string.h"
37 #include "hphp/util/compatibility.h"
38 #include "hphp/util/configs/server.h"
39 #include "hphp/util/hardware-counter.h"
40 #include "hphp/util/logger.h"
41 #include "hphp/util/service-data.h"
42 #include "hphp/util/struct-log.h"
43 #include "hphp/util/text-util.h"
44 #include "hphp/util/timer.h"
46 #include <folly/Random.h>
47 #include <folly/String.h>
49 namespace HPHP {
50 ///////////////////////////////////////////////////////////////////////////////
52 static const char HTTP_RESPONSE_STATS_PREFIX[] = "http_response_";
53 const StaticString s_defaultCharset("default_charset");
55 Transport::Transport() {}
57 Transport::~Transport() {
58 if (m_url) {
59 free(m_url);
61 if (m_postData) {
62 free(m_postData);
66 void Transport::onRequestStart(const timespec &queueTime) {
67 m_queueTime = queueTime;
68 Timer::GetMonotonicTime(m_wallTime);
69 #ifdef CLOCK_THREAD_CPUTIME_ID
70 gettime(CLOCK_THREAD_CPUTIME_ID, &m_cpuTime);
71 #endif
73 * The hardware counter is only 48 bits, so reset this at the beginning
74 * of every request to make sure we don't overflow.
76 HardwareCounter::Reset();
77 m_instructions = HardwareCounter::GetInstructionCount();
79 auto const rate = RuntimeOption::EvalTraceServerRequestRate;
80 if (rate && folly::Random::rand32(rate) == 0) forceInitRequestTrace();
83 const char *Transport::getMethodName() {
84 switch (getMethod()) {
85 case Method::GET: return "GET";
86 case Method::HEAD: return "HEAD";
87 case Method::POST: {
88 const char *m = getExtendedMethod();
89 return m ? m : "POST";
91 case Method::Unknown: {
92 // m should really never be nullptr. "Unknown" is an actionable log line
93 // since it indicates a missing HTTP method (since it is not capitalized).
94 const char *m = getExtendedMethod();
95 return m ? m : "Unknown";
97 default:
98 break;
100 return "";
103 ///////////////////////////////////////////////////////////////////////////////
104 // url
106 const char *Transport::getServerObject() {
107 const char *url = getUrl();
108 return URL::getServerObject(url);
111 std::string Transport::getCommand() {
112 const char *url = getServerObject();
113 return URL::getCommand(url);
116 ///////////////////////////////////////////////////////////////////////////////
117 // parameters
119 // copied and re-factored from clearsilver-0.10.5/cgi/cgi.c
120 void Transport::urlUnescape(char *value) {
121 assertx(value && *value); // check before calling this function
123 int i = 0, o = 0;
124 unsigned char *s = (unsigned char *)value;
126 while (s[i]) {
127 if (s[i] == '+') {
128 s[o++] = ' ';
129 i++;
130 } else if (s[i] == '%' && isxdigit(s[i+1]) && isxdigit(s[i+2])) {
131 char num;
132 num = (s[i+1] >= 'A') ? ((s[i+1] & 0xdf) - 'A') + 10 : (s[i+1] - '0');
133 num *= 16;
134 num += (s[i+2] >= 'A') ? ((s[i+2] & 0xdf) - 'A') + 10 : (s[i+2] - '0');
135 s[o++] = num;
136 i+=3;
137 } else {
138 s[o++] = s[i++];
141 if (i && o) s[o] = '\0';
144 void Transport::parseQuery(char *query, ParamMap &params) {
145 if (!query || !*query) return;
147 char *l;
148 char *k = strtok_r(query, "&", &l);
149 while (k && *k) {
150 char *v = strchr(k, '=');
151 const char *fv;
152 if (v == nullptr) {
153 fv = "";
154 } else {
155 *v = '\0';
156 v++;
157 if (*v) urlUnescape(v);
158 fv = v;
160 if (*k) urlUnescape(k);
161 params[k].push_back(fv);
162 k = strtok_r(nullptr, "&", &l);
166 void Transport::parseGetParams() {
167 if (m_url == nullptr) {
168 const char *url = getServerObject();
169 assertx(url);
171 const char *p = strchr(url, '?');
172 if (p) {
173 m_url = strdup(p + 1);
174 } else {
175 m_url = strdup("");
178 parseQuery(m_url, m_getParams);
182 void Transport::parsePostParams() {
183 if (!m_postDataParsed) {
184 assertx(m_postData == nullptr);
185 size_t size;
186 const char *data = (const char *)getPostData(size);
187 if (data && *data && size) {
188 // Post data may be binary, but if parsePostParams() is called, any
189 // wellformed data cannot have embedded NULs. If it does, we simply
190 // truncate it.
191 m_postData = strndup(data, size);
192 parseQuery(m_postData, m_postParams);
194 m_postDataParsed = true;
198 std::string Transport::getParam(const char *name,
199 Method method /* = Method::GET */) {
200 assertx(name && *name);
202 if (method == Method::GET || method == Method::AUTO) {
203 if (m_url == nullptr) {
204 parseGetParams();
206 ParamMap::const_iterator iter = m_getParams.find(name);
207 if (iter != m_getParams.end()) {
208 return iter->second[0];
212 if (method == Method::POST || method == Method::AUTO) {
213 if (!m_postDataParsed) {
214 parsePostParams();
216 ParamMap::const_iterator iter = m_postParams.find(name);
217 if (iter != m_postParams.end()) {
218 return iter->second[0];
222 return "";
225 int Transport::getIntParam(const char *name,
226 Method method /* = Method::GET */) {
227 std::string param = getParam(name, method);
228 if (param.empty()) {
229 return 0;
231 return atoi(param.c_str());
234 long long Transport::getInt64Param(const char *name,
235 Method method /* = Method::GET */) {
236 std::string param = getParam(name, method);
237 if (param.empty()) {
238 return 0;
240 return atoll(param.c_str());
243 void Transport::getArrayParam(const char *name,
244 std::vector<std::string> &values,
245 Method method /* = GET */) {
246 if (method == Method::GET || method == Method::AUTO) {
247 if (m_url == nullptr) {
248 parseGetParams();
250 ParamMap::const_iterator iter = m_getParams.find(name);
251 if (iter != m_getParams.end()) {
252 auto const& params = iter->second;
253 values.insert(values.end(), params.begin(), params.end());
257 if (method == Method::POST || method == Method::AUTO) {
258 if (!m_postDataParsed) {
259 parsePostParams();
261 ParamMap::const_iterator iter = m_postParams.find(name);
262 if (iter != m_postParams.end()) {
263 auto const& params = iter->second;
264 values.insert(values.end(), params.begin(), params.end());
269 void Transport::getSplitParam(const char *name,
270 std::vector<std::string> &values,
271 char delimiter,
272 Method method /* = Method::GET */) {
273 std::string param = getParam(name, method);
274 if (!param.empty()) {
275 folly::split(delimiter, param, values);
279 ///////////////////////////////////////////////////////////////////////////////
280 // headers
282 bool Transport::splitHeader(const String& header, String &name, const char *&value) {
283 int pos = header.find(':');
285 if (pos != String::npos) {
286 name = header.substr(0, pos);
287 value = header.data() + pos;
289 do {
290 value++;
291 } while (*value == ' ');
293 return true;
296 // header("HTTP/1.0 404 Not Found");
297 // header("HTTP/1.0 404");
298 if (strncasecmp(header.data(), "http/", 5) == 0) {
299 int pos1 = header.find(' ');
300 if (pos1 != String::npos) {
301 int pos2 = header.find(' ', pos1 + 1);
302 if (pos2 == String::npos) pos2 = header.size();
303 if (pos2 - pos1 > 1) {
304 setResponse(atoi(header.data() + pos1),
305 header.size() - pos2 > 1 ? header.data() + pos2 : nullptr);
306 return false;
311 throw ExtendedException(
312 "Invalid argument \"header\": [%s]", header.c_str());
315 void Transport::addHeaderNoLock(const char *name, const char *value) {
316 assertx(name && *name);
317 assertx(value);
319 if (!m_firstHeaderSet) {
320 m_firstHeaderSet = true;
321 m_firstHeaderFile = g_context->getContainingFileName()->data();
322 m_firstHeaderLine = g_context->getLine();
325 std::string svalue = value;
326 replaceAll(svalue, "\n", "");
327 m_responseHeaders[name].push_back(svalue);
329 if (strcasecmp(name, "Location") == 0 && m_responseCode != 201 &&
330 !(m_responseCode >= 300 && m_responseCode <=307)) {
331 /* Zend seems to set 303 on a post with HTTP version > 1.0 in the code but
332 * in our testing we can only get it to give 302.
333 Method m = getMethod();
334 if (m != Method::GET && m != Method::HEAD) {
335 setResponse(303);
336 } else {
337 setResponse(302);
340 setResponse(302);
344 void Transport::addHeader(const char *name, const char *value) {
345 assertx(name && *name);
346 assertx(value);
347 addHeaderNoLock(name, value);
350 void Transport::addHeader(const String& header) {
351 String name;
352 const char *value;
353 if (splitHeader(header, name, value)) {
354 addHeader(name.data(), value);
358 void Transport::replaceHeader(const char *name, const char *value) {
359 assertx(name && *name);
360 assertx(value);
361 m_responseHeaders[name].clear();
362 addHeaderNoLock(name, value);
365 void Transport::replaceHeader(const String& header) {
366 String name;
367 const char *value;
368 if (splitHeader(header, name, value)) {
369 replaceHeader(name.data(), value);
373 void Transport::removeHeader(const char *name) {
374 if (name && *name) {
375 m_responseHeaders.erase(name);
376 if (strcasecmp(name, "Set-Cookie") == 0) {
377 m_responseCookies.clear();
382 void Transport::removeAllHeaders() {
383 m_responseHeaders.clear();
384 m_responseCookies.clear();
387 void Transport::getResponseHeaders(HeaderMap &headers) {
388 headers = m_responseHeaders;
390 auto& cookies = headers["Set-Cookie"];
391 for (auto& cookie : m_responseCookies) {
392 cookies.push_back(cookie.second);
396 void Transport::addToCommaSeparatedHeader(const char* name, const char* value) {
397 assertx(name && *name);
398 assertx(value);
399 const auto it = m_responseHeaders.find(name);
400 if (it != m_responseHeaders.end() && !it->second.empty()) {
401 it->second[0] = it->second[0] + std::string(", ") + value;
402 } else {
403 addHeader(name, value);
407 bool Transport::cookieExists(const char *name) {
408 assertx(name && *name);
409 std::string header = getHeader("Cookie");
410 int len = strlen(name);
411 bool hasValue = (strchr(name, '=') != nullptr);
412 for (size_t pos = header.find(name); pos != std::string::npos;
413 pos = header.find(name, pos + 1)) {
414 if (pos == 0 || isspace(header[pos-1]) || header[pos-1] == ';') {
415 pos += len;
416 if (hasValue) {
417 if (pos == header.size() || header[pos] == ';') return true;
418 } else {
419 if (pos < header.size() && header[pos] == '=') return true;
423 return false;
426 std::string Transport::getCookie(const std::string &name) {
427 assertx(!name.empty());
428 std::string header = getHeader("Cookie");
429 for (size_t pos = header.find(name); pos != std::string::npos;
430 pos = header.find(name, pos + 1)) {
431 if (pos == 0 || isspace(header[pos-1]) || header[pos-1] == ';') {
432 pos += name.size();
433 if (pos < header.size() && header[pos] == '=') {
434 size_t end = header.find(';', pos + 1);
435 if (end != std::string::npos) end -= pos + 1;
436 return header.substr(pos + 1, end);
440 return "";
443 bool Transport::acceptEncoding(const char *encoding) {
444 return acceptsEncoding(this, encoding);
447 void Transport::setResponse(int code, const char *info) {
448 m_responseCode = code;
449 m_responseCodeInfo = info ? info : HttpProtocol::GetReasonString(code);
452 std::string Transport::getHTTPVersion() const {
453 return "1.1";
456 size_t Transport::getRequestSize() const {
457 return 0;
460 void Transport::setMimeType(const String& mimeType) {
461 m_mimeType = mimeType.data();
464 String Transport::getMimeType() {
465 return String(m_mimeType);
468 ///////////////////////////////////////////////////////////////////////////////
469 // cookies
471 namespace {
473 // Make sure cookie names do not contain any illegal characters.
474 // Throw a fatal exception if one does.
475 void validateCookieNameString(const String& str) {
476 if (!str.empty() && strpbrk(str.data(), "=,; \t\r\n\013\014")) {
477 raise_error("Cookie names can not contain any of the following "
478 "'=,; \\t\\r\\n\\013\\014'");
482 // Make sure a component (path, value, domain) of a cookie does not
483 // contain any illegal characters. Throw a fatal exception if it
484 // does.
485 void validateCookieString(const String& str, const char* component) {
486 if (!str.empty() && strpbrk(str.data(), ",; \t\r\n\013\014")) {
487 raise_error("Cookie %s can not contain any of the following "
488 "',; \\t\\r\\n\\013\\014'", component);
494 bool Transport::setCookie(const String& name, const String& value, int64_t expire /* = 0 */,
495 const String& path /* = "" */, const String& domain /* = "" */,
496 bool secure /* = false */,
497 bool httponly /* = false */,
498 bool encode_url /* = true */) {
499 validateCookieNameString(name);
501 if (!encode_url) {
502 validateCookieString(value, "values");
505 validateCookieString(path, "paths");
507 validateCookieString(domain, "domains");
509 String encoded_value;
510 int len = 0;
511 if (!value.empty()) {
512 encoded_value = encode_url ? url_encode(value.data(), value.size())
513 : value;
514 len += encoded_value.size();
516 len += path.size();
517 len += domain.size();
519 std::string cookie;
520 cookie.reserve(len + 100);
521 if (value.empty()) {
523 * MSIE doesn't delete a cookie when you set it to a null value
524 * so in order to force cookies to be deleted, even on MSIE, we
525 * pick an expiry date in the past
527 String sdt = req::make<DateTime>(1, true)->
528 toString(DateTime::DateFormat::Cookie);
529 cookie += name.data();
530 cookie += "=deleted; expires=";
531 cookie += sdt.data();
532 cookie += "; Max-Age=0";
533 } else {
534 cookie += name.data();
535 cookie += "=";
536 cookie += encoded_value.isNull() ? "" : encoded_value.data();
537 if (expire > 0) {
538 if (expire > 253402300799LL) {
539 raise_warning("Expiry date cannot have a year greater than 9999");
540 return false;
542 cookie += "; expires=";
543 String sdt = req::make<DateTime>(expire, true)->
544 toString(DateTime::DateFormat::Cookie);
545 cookie += sdt.data();
546 cookie += "; Max-Age=";
547 String sdelta = String(expire - time(0));
548 cookie += sdelta.data();
552 if (!path.empty()) {
553 cookie += "; path=";
554 cookie += path.data();
556 if (!domain.empty()) {
557 cookie += "; domain=";
558 cookie += domain.data();
560 if (secure) {
561 cookie += "; secure";
563 if (httponly) {
564 cookie += "; httponly";
567 // PHP5 does not deduplicate cookies. That behavior is preserved when
568 // CookieDeduplicate is not enabled. Otherwise, we will only keep the
569 // last cookie for a given name-domain-path triplet.
570 String dedup_key = name + "\n" + domain + "\n" + path;
572 m_responseCookies[dedup_key.data()] = cookie;
574 return true;
577 ///////////////////////////////////////////////////////////////////////////////
579 void Transport::prepareHeaders(bool precompressed, bool chunked,
580 const StringHolder &response, const StringHolder& orig_response) {
581 for (HeaderMap::const_iterator iter = m_responseHeaders.begin();
582 iter != m_responseHeaders.end(); ++iter) {
583 const auto& values = iter->second;
584 for (unsigned int i = 0; i < values.size(); i++) {
585 addHeaderImpl(iter->first.c_str(), values[i].c_str());
589 for (auto& cookie : m_responseCookies) {
590 addHeaderImpl("Set-Cookie", cookie.second.c_str());
593 auto& compressor = getCompressor();
595 // should never double-compress
596 assertx(!precompressed || !compressor.isCompressed());
598 if (precompressed) {
599 // pre-compressed content is currently always gzip compressed.
600 addHeaderImpl("Content-Encoding", "gzip");
601 if (Cfg::Server::AddVaryEncoding) {
602 addHeaderImpl("Vary", "Accept-Encoding");
606 if (precompressed || compressor.isCompressed()) {
607 removeHeaderImpl("Content-Length");
608 // Remove the Content-MD5 header coming from PHP if we compressed the data,
609 // as the checksum is going to be invalid.
610 auto it = m_responseHeaders.find("Content-MD5");
611 if (it != m_responseHeaders.end()) {
612 removeHeaderImpl("Content-MD5");
613 // Re-add it back unless this is a chunked response. We'd have to buffer
614 // the response completely to compute the MD5, which defeats the purpose
615 // of chunking.
616 if (chunked) {
617 raise_warning("Cannot use chunked HTTP response and Content-MD5 header "
618 "at the same time. Dropping Content-MD5.");
619 } else {
620 std::string cur_md5 = it->second[0];
621 String expected_md5 = StringUtil::Base64Encode(StringUtil::MD5(
622 orig_response.data(), orig_response.size(), true));
623 // Can never trust these PHP people...
624 if (expected_md5.c_str() != cur_md5) {
625 raise_warning("Content-MD5 mismatch. Expected: %s, Got: %s",
626 expected_md5.c_str(), cur_md5.c_str());
628 addHeaderImpl("Content-MD5", StringUtil::Base64Encode(StringUtil::MD5(
629 response.data(), response.size(), true)).c_str());
634 if (m_responseHeaders.find("Content-Type") == m_responseHeaders.end() &&
635 m_responseCode != 304) {
636 std::string contentType = "text/html";
637 auto const defaultCharset = IniSetting::Get(s_defaultCharset);
638 if (defaultCharset != "") {
639 contentType += "; charset=" + defaultCharset;
641 addHeaderImpl("Content-Type", contentType.c_str());
644 if (Cfg::Server::ExposeHPHP) {
645 addHeaderImpl("X-Powered-By", (String("HHVM/") + HHVM_VERSION).c_str());
648 if ((Cfg::Server::ExposeXFBServer || Cfg::Server::ExposeXFBDebug) &&
649 !Cfg::Server::XFBDebugSSLKey.empty() &&
650 m_responseHeaders.find("X-FB-Debug") == m_responseHeaders.end()) {
651 String ip = this->getServerAddr();
652 String key = Cfg::Server::XFBDebugSSLKey;
653 String cipher("AES-256-CBC");
654 bool crypto_strong = false;
655 auto const iv_len = (int)HHVM_FN(openssl_cipher_iv_length)(cipher).toInt64();
656 auto const iv = HHVM_FN(openssl_random_pseudo_bytes)(
657 iv_len, crypto_strong
658 ).toString();
659 auto const encrypted = HHVM_FN(openssl_encrypt)(
660 ip, cipher, key, k_OPENSSL_RAW_DATA, iv
661 ).toString();
662 auto const output = StringUtil::Base64Encode(iv + encrypted);
663 if (debug) {
664 auto const decrypted = HHVM_FN(openssl_decrypt)(
665 encrypted, cipher, key, k_OPENSSL_RAW_DATA, iv
666 ).toString();
667 assertx(decrypted.get()->same(ip.get()));
669 addHeaderImpl("X-FB-Debug", output.c_str());
672 // shutting down servers, so need to terminate all Keep-Alive connections
673 if (!Cfg::Server::EnableKeepAlive || isServerStopping()) {
674 addHeaderImpl("Connection", "close");
675 removeHeaderImpl("Keep-Alive");
677 // so lower level transports can ignore incoming "Connection: keep-alive"
678 removeRequestHeaderImpl("Connection");
681 if (Cfg::Server::ExposeLoadhint) {
682 static auto const counter = ServiceData::createCounter("loadhint.new");
683 auto const loadhint = counter->getValue();
684 if (loadhint >= 0) {
685 char buf[16];
686 std::snprintf(buf, sizeof(buf), "%d", static_cast<int>(loadhint));
687 addHeaderImpl("X-FB-Server-Load", buf);
692 namespace {
694 void LogException(const char* msg) {
695 try {
696 throw;
697 } catch (Exception& e) {
698 Logger::Error("%s: %s", msg, e.getMessage().c_str());
699 } catch (std::exception& e) {
700 Logger::Error("%s: %s", msg, e.what());
701 } catch (Object& e) {
702 try {
703 Logger::Error("%s: %s", msg, throwable_to_string(e.get()).c_str());
704 } catch (...) {
705 Logger::Error("%s: (e.toString() failed)", msg);
707 } catch (...) {
708 Logger::Error("%s: (unknown exception)", msg);
711 } // anonymous namespace
713 StringHolder Transport::compressResponse(
714 const char* data, int size, bool last) {
715 StringHolder response(data, size, FreeType::NoFree);
716 auto compressedResponse = getCompressor().compressResponse(data, size, last);
717 if (compressedResponse.data() != nullptr) {
718 response = std::move(compressedResponse);
721 return response;
724 ResponseCompressorManager& Transport::getCompressor() {
725 if (!m_compressor) {
726 m_compressor = std::make_unique<ResponseCompressorManager>(this);
728 return *m_compressor;
731 void Transport::enableCompression() {
732 getCompressor().enable();
735 void Transport::disableCompression() {
736 getCompressor().disable();
739 bool Transport::isCompressionEnabled() {
740 return getCompressor().isEnabled();
743 void Transport::sendRaw(const char *data, int size, int code /* = 200 */,
744 bool precompressed /* = false */,
745 bool chunked /* = false */,
746 const char *codeInfo /* = nullptr */
748 // There are post-send functions that can run. Any output from them should
749 // be ignored as it doesn't make sense to try and send data after the
750 // request has ended.
751 if (m_sendEnded) {
752 return;
755 // Note: This API is used when `isStreamTransport()` to report request errors
756 // (such as 404) but if we already started sending then ignore this.
757 if (m_sendStarted && isStreamTransport()) {
758 return;
761 if (!precompressed && Cfg::Server::ForceChunkedEncoding) {
762 chunked = true;
765 // I don't think there is any need to send an empty chunk, other than sending
766 // out headers earlier, which seems to be a useless feature.
767 if (size == 0 && (chunked || m_chunkedEncoding)) {
768 return;
771 if (chunked) {
772 m_chunkedEncoding = true;
774 if (m_chunkedEncoding) {
775 assertx(!precompressed);
778 sendRawInternal(data, size, code, precompressed, codeInfo);
781 void Transport::sendRawInternal(const char *data, int size,
782 int code /* = 200 */,
783 bool precompressed /* = false */,
784 const char *codeInfo /* = nullptr */
787 bool chunked = m_chunkedEncoding;
789 if (!g_context->m_headerCallbackDone &&
790 !tvIsNull(&g_context->m_headerCallback)) {
791 // We could use m_headerSent here, however it seems we can still
792 // end up in an infinite loop when:
793 // m_headerCallback calls flush()
794 // flush() triggers php's recursion guard
795 // the recursion guard calls back into m_headerCallback
796 g_context->m_headerCallbackDone = true;
797 try {
798 vm_call_user_func(tvAsVariant(g_context->m_headerCallback),
799 init_null_variant);
800 } catch (...) {
801 LogException("HeaderCallback");
805 // compression handling
806 ServerStatsHelper ssh("send");
808 if (precompressed) {
809 disableCompression();
811 StringHolder response = compressResponse(data, size, !chunked);
813 if (m_responseCode < 0) {
814 setResponse(code, codeInfo);
817 // HTTP header handling
818 if (!m_headerSent) {
819 prepareHeaders(precompressed, chunked, response,
820 StringHolder(data, size, FreeType::NoFree));
821 m_headerSent = true;
824 m_responseSize += response.size();
825 ServerStats::SetThreadMode(ServerStats::ThreadMode::Writing);
826 sendImpl(response.data(), response.size(), m_responseCode, chunked, false);
827 ServerStats::SetThreadMode(ServerStats::ThreadMode::Processing);
829 ServerStats::LogBytes(size);
830 if (RuntimeOption::EnableStats && RuntimeOption::EnableWebStats) {
831 ServerStats::Log("network.uncompressed", size);
832 ServerStats::Log("network.compressed", response.size());
836 void Transport::onSendEnd() {
837 if (auto stream = getStreamTransport()) {
838 stream->close();
839 return;
841 bool eomSent = false;
842 if (m_chunkedEncoding) {
843 assertx(m_headerSent);
844 StringHolder response = compressResponse("", 0, true);
845 sendImpl(response.data(), response.size(), m_responseCode, true, true);
846 eomSent = true;
847 } else if (!m_headerSent) {
848 sendRawInternal("", 0);
850 auto httpResponseStats = ServiceData::createTimeSeries(
851 folly::to<std::string>(HTTP_RESPONSE_STATS_PREFIX, getResponseCode()),
852 {ServiceData::StatsType::SUM});
853 httpResponseStats->addValue(1);
854 if (!eomSent) {
855 onSendEndImpl();
857 // Record that we have ended the request so any further output is discarded.
858 m_sendEnded = true;
861 void Transport::redirect(const char *location, int code /* = 302 */,
862 const char *info /* = nullptr */) {
863 if (auto stream = getStreamTransport()) {
864 stream->close();
865 return;
867 addHeaderImpl("Location", location);
868 setResponse(code, info);
869 sendString("Moved", code);
872 void Transport::onFlushProgress(int writtenSize, int64_t delayUs) {
873 m_responseSentSize += writtenSize;
874 m_flushTimeUs += delayUs;
875 m_chunksSentSizes.push_back(writtenSize);
878 void Transport::onChunkedProgress(int writtenSize) {
879 m_responseSentSize += writtenSize;
880 m_chunksSentSizes.push_back(writtenSize);
883 void Transport::getChunkSentSizes(Array &ret) {
884 for (unsigned int i = 0; i < m_chunksSentSizes.size(); i++) {
885 ret.append(m_chunksSentSizes[i]);
889 int Transport::getLastChunkSentSize() {
890 size_t size = m_chunksSentSizes.size();
891 return size == 0 ? 0 : m_chunksSentSizes.back();
894 ///////////////////////////////////////////////////////////////////////////////
895 // support rfc1867
897 bool Transport::isUploadedFile(const String& filename) {
898 return is_uploaded_file(filename.c_str());
901 ///////////////////////////////////////////////////////////////////////////////
902 // IDebuggable
904 const char *Transport::getThreadTypeName() const {
905 switch (m_threadType) {
906 case ThreadType::RequestThread: return "Web Request";
907 case ThreadType::PageletThread: return "Pagelet Thread";
908 case ThreadType::XboxThread: return "Xbox Thread";
909 case ThreadType::RpcThread: return "RPC Thread";
911 return "(unknown)";
914 void Transport::debuggerInfo(InfoVec &info) {
915 Add(info, "Thread Type", getThreadTypeName());
916 Add(info, "URL", getCommand());
917 Add(info, "HTTP", getHTTPVersion());
918 Add(info, "Method", getMethodName());
919 if (getMethod() == Method::POST) {
920 size_t size; getPostData(size);
921 Add(info, "Post Data", FormatSize(size));
925 ///////////////////////////////////////////////////////////////////////////////
926 // StructuredLog
928 StructuredLogEntry* Transport::createStructuredLogEntry() {
929 assertx(!m_structLogEntry);
930 m_structLogEntry = std::make_unique<StructuredLogEntry>();
931 return m_structLogEntry.get();
934 StructuredLogEntry* Transport::getStructuredLogEntry() {
935 return m_structLogEntry.get();
938 void Transport::resetStructuredLogEntry() {
939 m_structLogEntry.reset();
942 ///////////////////////////////////////////////////////////////////////////////
943 // Request tracing
945 void Transport::forceInitRequestTrace() {
946 if (m_requestTrace) return;
947 m_requestTrace.emplace();
948 rqtrace::ScopeGuard(
949 getRequestTrace(), "REQUEST_QUEUE", getQueueTime()
950 ).finish(getWallTime());
953 ///////////////////////////////////////////////////////////////////////////////