2 +----------------------------------------------------------------------+
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>
50 ///////////////////////////////////////////////////////////////////////////////
52 static const char HTTP_RESPONSE_STATS_PREFIX
[] = "http_response_";
53 const StaticString
s_defaultCharset("default_charset");
55 Transport::Transport() {}
57 Transport::~Transport() {
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
);
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";
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";
103 ///////////////////////////////////////////////////////////////////////////////
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 ///////////////////////////////////////////////////////////////////////////////
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
124 unsigned char *s
= (unsigned char *)value
;
130 } else if (s
[i
] == '%' && isxdigit(s
[i
+1]) && isxdigit(s
[i
+2])) {
132 num
= (s
[i
+1] >= 'A') ? ((s
[i
+1] & 0xdf) - 'A') + 10 : (s
[i
+1] - '0');
134 num
+= (s
[i
+2] >= 'A') ? ((s
[i
+2] & 0xdf) - 'A') + 10 : (s
[i
+2] - '0');
141 if (i
&& o
) s
[o
] = '\0';
144 void Transport::parseQuery(char *query
, ParamMap
¶ms
) {
145 if (!query
|| !*query
) return;
148 char *k
= strtok_r(query
, "&", &l
);
150 char *v
= strchr(k
, '=');
157 if (*v
) urlUnescape(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();
171 const char *p
= strchr(url
, '?');
173 m_url
= strdup(p
+ 1);
178 parseQuery(m_url
, m_getParams
);
182 void Transport::parsePostParams() {
183 if (!m_postDataParsed
) {
184 assertx(m_postData
== nullptr);
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
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) {
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
) {
216 ParamMap::const_iterator iter
= m_postParams
.find(name
);
217 if (iter
!= m_postParams
.end()) {
218 return iter
->second
[0];
225 int Transport::getIntParam(const char *name
,
226 Method method
/* = Method::GET */) {
227 std::string param
= getParam(name
, method
);
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
);
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) {
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
) {
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
,
272 Method method
/* = Method::GET */) {
273 std::string param
= getParam(name
, method
);
274 if (!param
.empty()) {
275 folly::split(delimiter
, param
, values
);
279 ///////////////////////////////////////////////////////////////////////////////
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
;
291 } while (*value
== ' ');
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);
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
);
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) {
344 void Transport::addHeader(const char *name
, const char *value
) {
345 assertx(name
&& *name
);
347 addHeaderNoLock(name
, value
);
350 void Transport::addHeader(const String
& header
) {
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
);
361 m_responseHeaders
[name
].clear();
362 addHeaderNoLock(name
, value
);
365 void Transport::replaceHeader(const String
& header
) {
368 if (splitHeader(header
, name
, value
)) {
369 replaceHeader(name
.data(), value
);
373 void Transport::removeHeader(const char *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
);
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
;
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] == ';') {
417 if (pos
== header
.size() || header
[pos
] == ';') return true;
419 if (pos
< header
.size() && header
[pos
] == '=') return true;
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] == ';') {
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
);
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 {
456 size_t Transport::getRequestSize() const {
460 void Transport::setMimeType(const String
& mimeType
) {
461 m_mimeType
= mimeType
.data();
464 String
Transport::getMimeType() {
465 return String(m_mimeType
);
468 ///////////////////////////////////////////////////////////////////////////////
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
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
);
502 validateCookieString(value
, "values");
505 validateCookieString(path
, "paths");
507 validateCookieString(domain
, "domains");
509 String encoded_value
;
511 if (!value
.empty()) {
512 encoded_value
= encode_url
? url_encode(value
.data(), value
.size())
514 len
+= encoded_value
.size();
517 len
+= domain
.size();
520 cookie
.reserve(len
+ 100);
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";
534 cookie
+= name
.data();
536 cookie
+= encoded_value
.isNull() ? "" : encoded_value
.data();
538 if (expire
> 253402300799LL) {
539 raise_warning("Expiry date cannot have a year greater than 9999");
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();
554 cookie
+= path
.data();
556 if (!domain
.empty()) {
557 cookie
+= "; domain=";
558 cookie
+= domain
.data();
561 cookie
+= "; secure";
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
;
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());
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
617 raise_warning("Cannot use chunked HTTP response and Content-MD5 header "
618 "at the same time. Dropping Content-MD5.");
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
659 auto const encrypted
= HHVM_FN(openssl_encrypt
)(
660 ip
, cipher
, key
, k_OPENSSL_RAW_DATA
, iv
662 auto const output
= StringUtil::Base64Encode(iv
+ encrypted
);
664 auto const decrypted
= HHVM_FN(openssl_decrypt
)(
665 encrypted
, cipher
, key
, k_OPENSSL_RAW_DATA
, iv
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();
686 std::snprintf(buf
, sizeof(buf
), "%d", static_cast<int>(loadhint
));
687 addHeaderImpl("X-FB-Server-Load", buf
);
694 void LogException(const char* msg
) {
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
) {
703 Logger::Error("%s: %s", msg
, throwable_to_string(e
.get()).c_str());
705 Logger::Error("%s: (e.toString() failed)", msg
);
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
);
724 ResponseCompressorManager
& Transport::getCompressor() {
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.
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()) {
761 if (!precompressed
&& Cfg::Server::ForceChunkedEncoding
) {
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
)) {
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;
798 vm_call_user_func(tvAsVariant(g_context
->m_headerCallback
),
801 LogException("HeaderCallback");
805 // compression handling
806 ServerStatsHelper
ssh("send");
809 disableCompression();
811 StringHolder response
= compressResponse(data
, size
, !chunked
);
813 if (m_responseCode
< 0) {
814 setResponse(code
, codeInfo
);
817 // HTTP header handling
819 prepareHeaders(precompressed
, chunked
, response
,
820 StringHolder(data
, size
, FreeType::NoFree
));
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()) {
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);
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);
857 // Record that we have ended the request so any further output is discarded.
861 void Transport::redirect(const char *location
, int code
/* = 302 */,
862 const char *info
/* = nullptr */) {
863 if (auto stream
= getStreamTransport()) {
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 ///////////////////////////////////////////////////////////////////////////////
897 bool Transport::isUploadedFile(const String
& filename
) {
898 return is_uploaded_file(filename
.c_str());
901 ///////////////////////////////////////////////////////////////////////////////
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";
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 ///////////////////////////////////////////////////////////////////////////////
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 ///////////////////////////////////////////////////////////////////////////////
945 void Transport::forceInitRequestTrace() {
946 if (m_requestTrace
) return;
947 m_requestTrace
.emplace();
949 getRequestTrace(), "REQUEST_QUEUE", getQueueTime()
950 ).finish(getWallTime());
953 ///////////////////////////////////////////////////////////////////////////////