Your IP : 3.22.79.165
/*
* Phusion Passenger - https://www.phusionpassenger.com/
* Copyright (c) 2011-2017 Phusion Holding B.V.
*
* "Passenger", "Phusion Passenger" and "Union Station" are registered
* trademarks of Phusion Holding B.V.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
#include <Core/Controller.h>
/*************************************************************************
*
* Implements Core::Controller methods pertaining sending application
* response data to the client. This happens in parallel to the process
* of sending request data to the application.
*
*************************************************************************/
namespace Passenger {
namespace Core {
using namespace std;
using namespace boost;
/****************************
*
* Private methods
*
****************************/
ServerKit::Channel::Result
Controller::_onAppSourceData(Channel *_channel, const MemoryKit::mbuf &buffer, int errcode) {
FdSourceChannel *channel = reinterpret_cast<FdSourceChannel *>(_channel);
Request *req = static_cast<Request *>(static_cast<
ServerKit::BaseHttpRequest *>(channel->getHooks()->userData));
Client *client = static_cast<Client *>(req->client);
Controller *self = static_cast<Controller *>(getServerFromClient(client));
return self->onAppSourceData(client, req, buffer, errcode);
}
ServerKit::Channel::Result
Controller::onAppSourceData(Client *client, Request *req, const MemoryKit::mbuf &buffer,
int errcode)
{
SKC_LOG_EVENT(Controller, client, "onAppSourceData");
AppResponse *resp = &req->appResponse;
switch (resp->httpState) {
case AppResponse::PARSING_HEADERS:
if (buffer.size() > 0) {
// Data
UPDATE_TRACE_POINT();
size_t ret;
SKC_TRACE(client, 3, "Processing " << buffer.size() <<
" bytes of application data: \"" << cEscapeString(StaticString(
buffer.start, buffer.size())) << "\"");
{
ret = createAppResponseHeaderParser(getContext(), req).
feed(buffer);
}
if (resp->httpState == AppResponse::PARSING_HEADERS) {
// Not yet done parsing.
return Channel::Result(buffer.size(), false);
}
// Done parsing.
UPDATE_TRACE_POINT();
SKC_TRACE(client, 2, "Application response headers received");
getHeaderParserStatePool().destroy(resp->parserState.headerParser);
resp->parserState.headerParser = NULL;
switch (resp->httpState) {
case AppResponse::COMPLETE:
req->appSource.stop();
onAppResponseBegin(client, req);
return Channel::Result(ret, false);
case AppResponse::PARSING_BODY_WITH_LENGTH:
SKC_TRACE(client, 2, "Expecting an app response body with fixed length");
onAppResponseBegin(client, req);
return Channel::Result(ret, false);
case AppResponse::PARSING_BODY_UNTIL_EOF:
SKC_TRACE(client, 2, "Expecting app response body until end of stream");
req->wantKeepAlive = false;
onAppResponseBegin(client, req);
return Channel::Result(ret, false);
case AppResponse::PARSING_CHUNKED_BODY:
SKC_TRACE(client, 2, "Expecting a chunked app response body");
prepareAppResponseChunkedBodyParsing(client, req);
onAppResponseBegin(client, req);
return Channel::Result(ret, false);
case AppResponse::UPGRADED:
SKC_TRACE(client, 2, "Application upgraded connection");
req->wantKeepAlive = false;
onAppResponseBegin(client, req);
return Channel::Result(ret, false);
case AppResponse::ONEHUNDRED_CONTINUE:
SKC_TRACE(client, 2, "Application sent 100-Continue status");
onAppResponse100Continue(client, req);
return Channel::Result(ret, false);
case AppResponse::ERROR:
SKC_ERROR(client, "Error parsing application response header: " <<
ServerKit::getErrorDesc(resp->aux.parseError));
endRequestAsBadGateway(&client, &req);
return Channel::Result(0, true);
default:
P_BUG("Invalid response HTTP state " << (int) resp->httpState);
return Channel::Result(0, true);
}
} else if (errcode == 0 || errcode == ECONNRESET) {
// EOF
UPDATE_TRACE_POINT();
SKC_DEBUG(client, "Application sent EOF before finishing response headers");
endRequestWithAppSocketIncompleteResponse(&client, &req);
return Channel::Result(0, true);
} else {
// Error
UPDATE_TRACE_POINT();
SKC_DEBUG(client, "Application socket read error occurred before finishing response headers");
endRequestWithAppSocketReadError(&client, &req, errcode);
return Channel::Result(0, true);
}
case AppResponse::PARSING_BODY_WITH_LENGTH:
if (buffer.size() > 0) {
// Data
UPDATE_TRACE_POINT();
boost::uint64_t maxRemaining, remaining;
maxRemaining = resp->aux.bodyInfo.contentLength - resp->bodyAlreadyRead;
remaining = std::min<boost::uint64_t>(buffer.size(), maxRemaining);
resp->bodyAlreadyRead += remaining;
SKC_TRACE(client, 3, "Processing " << buffer.size() <<
" bytes of application data: \"" << cEscapeString(StaticString(
buffer.start, buffer.size())) << "\"");
SKC_TRACE(client, 3, "Application response body: " <<
resp->bodyAlreadyRead << " of " <<
resp->aux.bodyInfo.contentLength << " bytes already read");
if (remaining > 0) {
UPDATE_TRACE_POINT();
writeResponseAndMarkForTurboCaching(client, req,
MemoryKit::mbuf(buffer, 0, remaining));
if (!req->ended()) {
if (resp->bodyFullyRead()) {
SKC_TRACE(client, 2, "End of application response body reached");
handleAppResponseBodyEnd(client, req);
endRequest(&client, &req);
} else {
maybeThrottleAppSource(client, req);
}
}
} else {
UPDATE_TRACE_POINT();
SKC_TRACE(client, 2, "End of application response body reached");
handleAppResponseBodyEnd(client, req);
endRequest(&client, &req);
}
return Channel::Result(remaining, false);
} else if (errcode == 0 || errcode == ECONNRESET) {
// EOF
UPDATE_TRACE_POINT();
if (resp->bodyFullyRead()) {
SKC_TRACE(client, 2, "Application sent EOF");
handleAppResponseBodyEnd(client, req);
endRequest(&client, &req);
} else {
SKC_WARN(client, "Application sent EOF before finishing response body: " <<
resp->bodyAlreadyRead << " bytes already read, " <<
resp->aux.bodyInfo.contentLength << " bytes expected");
endRequestWithAppSocketIncompleteResponse(&client, &req);
}
return Channel::Result(0, true);
} else {
// Error
UPDATE_TRACE_POINT();
endRequestWithAppSocketReadError(&client, &req, errcode);
return Channel::Result(0, true);
}
case AppResponse::PARSING_CHUNKED_BODY:
if (!buffer.empty()) {
// Data
UPDATE_TRACE_POINT();
SKC_TRACE(client, 3, "Processing " << buffer.size() <<
" bytes of application data: \"" << cEscapeString(StaticString(
buffer.start, buffer.size())) << "\"");
ServerKit::HttpChunkedEvent event(createAppResponseChunkedBodyParser(req)
.feed(buffer));
resp->bodyAlreadyRead += event.consumed;
if (req->dechunkResponse) {
UPDATE_TRACE_POINT();
switch (event.type) {
case ServerKit::HttpChunkedEvent::NONE:
assert(!event.end);
return Channel::Result(event.consumed, false);
case ServerKit::HttpChunkedEvent::DATA:
assert(!event.end);
writeResponseAndMarkForTurboCaching(client, req, event.data);
maybeThrottleAppSource(client, req);
return Channel::Result(event.consumed, false);
case ServerKit::HttpChunkedEvent::END:
assert(event.end);
SKC_TRACE(client, 2, "End of application response body reached");
resp->aux.bodyInfo.endReached = true;
handleAppResponseBodyEnd(client, req);
endRequest(&client, &req);
return Channel::Result(event.consumed, true);
case ServerKit::HttpChunkedEvent::ERROR:
assert(event.end);
{
string message = "error parsing app response chunked encoding: ";
message.append(ServerKit::getErrorDesc(event.errcode));
disconnectWithError(&client, message);
}
return Channel::Result(event.consumed, true);
}
} else {
UPDATE_TRACE_POINT();
switch (event.type) {
case ServerKit::HttpChunkedEvent::NONE:
case ServerKit::HttpChunkedEvent::DATA:
assert(!event.end);
writeResponse(client, MemoryKit::mbuf(buffer, 0, event.consumed));
markResponsePartForTurboCaching(client, req, event.data);
maybeThrottleAppSource(client, req);
return Channel::Result(event.consumed, false);
case ServerKit::HttpChunkedEvent::END:
assert(event.end);
SKC_TRACE(client, 2, "End of application response body reached");
resp->aux.bodyInfo.endReached = true;
handleAppResponseBodyEnd(client, req);
writeResponse(client, MemoryKit::mbuf(buffer, 0, event.consumed));
if (!req->ended()) {
endRequest(&client, &req);
}
return Channel::Result(event.consumed, true);
case ServerKit::HttpChunkedEvent::ERROR:
assert(event.end);
{
string message = "error parsing app response chunked encoding: ";
message.append(ServerKit::getErrorDesc(event.errcode));
disconnectWithError(&client, message);
}
return Channel::Result(event.consumed, true);
}
}
} else if (errcode == 0 || errcode == ECONNRESET) {
// Premature EOF. This cannot be an expected EOF because
// we end the request upon consuming the end of the chunked body.
UPDATE_TRACE_POINT();
disconnectWithError(&client, "error parsing app response chunked encoding: "
"unexpected end-of-stream");
return Channel::Result(0, false);
} else {
// Error
UPDATE_TRACE_POINT();
endRequestWithAppSocketReadError(&client, &req, errcode);
return Channel::Result(0, true);
}
break; // Never reached, shut up compiler warning.
case AppResponse::PARSING_BODY_UNTIL_EOF:
case AppResponse::UPGRADED:
if (buffer.size() > 0) {
// Data
UPDATE_TRACE_POINT();
SKC_TRACE(client, 3, "Processing " << buffer.size() <<
" bytes of application data: \"" << cEscapeString(StaticString(
buffer.start, buffer.size())) << "\"");
resp->bodyAlreadyRead += buffer.size();
writeResponseAndMarkForTurboCaching(client, req, buffer);
maybeThrottleAppSource(client, req);
return Channel::Result(buffer.size(), false);
} else if (errcode == 0 || errcode == ECONNRESET) {
// EOF
UPDATE_TRACE_POINT();
SKC_TRACE(client, 2, "Application sent EOF");
SKC_TRACE(client, 2, "Not keep-aliving application session connection");
req->session->close(true, false);
endRequest(&client, &req);
return Channel::Result(0, false);
} else {
// Error
UPDATE_TRACE_POINT();
endRequestWithAppSocketReadError(&client, &req, errcode);
return Channel::Result(0, false);
}
break; // Never reached, shut up compiler warning.
default:
P_BUG("Invalid request HTTP state " << (int) resp->httpState);
return Channel::Result(0, false);
}
return Channel::Result(0, false); // Never reached, shut up compiler warning.
}
void
Controller::onAppResponseBegin(Client *client, Request *req) {
TRACE_POINT();
AppResponse *resp = &req->appResponse;
ssize_t bytesWritten;
bool oobw;
#ifdef DEBUG_CC_EVENT_LOOP_BLOCKING
req->timeOnRequestHeaderSent = ev_now(getLoop());
reportLargeTimeDiff(client,
"Headers sent until response begun",
req->timeOnRequestHeaderSent,
ev_now(getLoop()));
#endif
// Localize hash table operations for better CPU caching.
oobw = resp->secureHeaders.lookup(PASSENGER_REQUEST_OOB_WORK) != NULL;
resp->date = resp->headers.lookup(HTTP_DATE);
resp->setCookie = resp->headers.lookup(ServerKit::HTTP_SET_COOKIE);
if (resp->setCookie != NULL) {
// Move the Set-Cookie header from resp->headers to resp->setCookie;
// remove Set-Cookie from resp->headers without deallocating it.
LString *copy;
copy = (LString *) psg_palloc(req->pool, sizeof(LString));
psg_lstr_init(copy);
psg_lstr_move_and_append(resp->setCookie, req->pool, copy);
P_ASSERT_EQ(resp->setCookie->size, 0);
psg_lstr_append(resp->setCookie, req->pool, "x", 1);
resp->headers.erase(ServerKit::HTTP_SET_COOKIE);
resp->setCookie = copy;
}
resp->headers.erase(HTTP_CONNECTION);
resp->headers.erase(HTTP_STATUS);
if (resp->bodyType == AppResponse::RBT_CONTENT_LENGTH) {
resp->headers.erase(HTTP_CONTENT_LENGTH);
}
if (resp->bodyType == AppResponse::RBT_CHUNKED) {
resp->headers.erase(HTTP_TRANSFER_ENCODING);
if (req->dechunkResponse) {
req->wantKeepAlive = false;
}
}
if (resp->headers.lookup(ServerKit::HTTP_X_SENDFILE) != NULL
|| resp->headers.lookup(ServerKit::HTTP_X_ACCEL_REDIRECT) != NULL)
{
// If X-Sendfile or X-Accel-Redirect is set, then HttpHeaderParser
// treats the app response as having no body, and removes the
// Content-Length and Transfer-Encoding headers. Because of this,
// the response that we output also doesn't Content-Length
// or Transfer-Encoding. So we should disable keep-alive.
req->wantKeepAlive = false;
}
prepareAppResponseCaching(client, req);
if (OXT_UNLIKELY(oobw)) {
SKC_TRACE(client, 2, "Response with OOBW detected");
if (req->session != NULL) {
req->session->requestOOBW();
}
}
UPDATE_TRACE_POINT();
if (!sendResponseHeaderWithWritev(client, req, bytesWritten)) {
UPDATE_TRACE_POINT();
if (bytesWritten >= 0 || errno == EAGAIN || errno == EWOULDBLOCK) {
sendResponseHeaderWithBuffering(client, req, bytesWritten);
} else {
int e = errno;
P_ASSERT_EQ(bytesWritten, -1);
disconnectWithClientSocketWriteError(&client, e);
}
}
if (!req->ended() && !resp->hasBody() && !resp->upgraded()) {
UPDATE_TRACE_POINT();
handleAppResponseBodyEnd(client, req);
endRequest(&client, &req);
}
}
void
Controller::prepareAppResponseCaching(Client *client, Request *req) {
if (turboCaching.isEnabled() && !req->cacheKey.empty()) {
TRACE_POINT();
AppResponse *resp = &req->appResponse;
SKC_TRACE(client, 2, "Turbocache: preparing response caching");
if (turboCaching.responseCache.requestAllowsStoring(req)
&& turboCaching.responseCache.prepareRequestForStoring(req))
{
if (resp->bodyType == AppResponse::RBT_CONTENT_LENGTH
&& resp->aux.bodyInfo.contentLength > ResponseCache<Request>::MAX_BODY_SIZE)
{
SKC_DEBUG(client, "Response body larger than " <<
ResponseCache<Request>::MAX_BODY_SIZE <<
" bytes, so response is not eligible for turbocaching");
// Decrease store success ratio.
turboCaching.responseCache.incStores();
req->cacheKey = HashedStaticString();
}
} else if (turboCaching.responseCache.requestAllowsInvalidating(req)) {
SKC_DEBUG(client, "Processing turbocache invalidation based on response");
turboCaching.responseCache.invalidate(req);
req->cacheKey = HashedStaticString();
SKC_TRACE(client, 2, "Turbocache entries:\n" << turboCaching.responseCache.inspect());
} else {
SKC_TRACE(client, 2, "Turbocache: response not eligible for turbocaching");
// Decrease store success ratio.
turboCaching.responseCache.incStores();
req->cacheKey = HashedStaticString();
}
}
}
void
Controller::onAppResponse100Continue(Client *client, Request *req) {
TRACE_POINT();
if (!req->strip100ContinueHeader) {
UPDATE_TRACE_POINT();
const unsigned int BUFSIZE = 32;
char *buf = (char *) psg_pnalloc(req->pool, BUFSIZE);
int size = snprintf(buf, BUFSIZE, "HTTP/%d.%d 100 Continue\r\n",
(int) req->httpMajor, (int) req->httpMinor);
writeResponse(client, buf, size);
}
if (!req->ended()) {
UPDATE_TRACE_POINT();
deinitializeAppResponse(client, req);
reinitializeAppResponse(client, req);
req->appResponse.oneHundredContinueSent = !req->strip100ContinueHeader;
// Allow sending more response headers.
req->responseBegun = false;
}
}
/**
* Construct an array of buffers, which together contain the HTTP response
* data that should be sent to the client. This method does not copy any data:
* it just constructs buffers that point to the data stored inside `req->pool`,
* `req->appResponse.headers`, etc.
*
* The buffers will be stored in the array pointed to by `buffer`. This array must
* have space for at least `maxbuffers` items. The actual number of buffers constructed
* is stored in `nbuffers`, and the total data size of the buffers is stored in `dataSize`.
* Upon success, returns true. If the actual number of buffers necessary exceeds
* `maxbuffers`, then false is returned.
*
* You can also set `buffers` to NULL, in which case this method will not construct any
* buffers, but only count the number of buffers necessary, as well as the total data size.
* In this case, this method always returns true.
*/
bool
Controller::constructHeaderBuffersForResponse(Request *req, struct iovec *buffers,
unsigned int maxbuffers, unsigned int & restrict_ref nbuffers,
unsigned int & restrict_ref dataSize,
unsigned int & restrict_ref nCacheableBuffers)
{
#define BEGIN_PUSH_NEXT_BUFFER() \
do { \
if (buffers != NULL && i >= maxbuffers) { \
return false; \
} \
} while (false)
#define INC_BUFFER_ITER(i) \
do { \
i++; \
} while (false)
#define PUSH_STATIC_BUFFER(str) \
do { \
BEGIN_PUSH_NEXT_BUFFER(); \
if (buffers != NULL) { \
buffers[i].iov_base = (void *) str; \
buffers[i].iov_len = sizeof(str) - 1; \
} \
INC_BUFFER_ITER(i); \
dataSize += sizeof(str) - 1; \
} while (false)
AppResponse *resp = &req->appResponse;
ServerKit::HeaderTable::Iterator it(resp->headers);
const LString::Part *part;
const char *statusAndReason;
unsigned int i = 0;
nbuffers = 0;
dataSize = 0;
PUSH_STATIC_BUFFER("HTTP/");
if (buffers != NULL) {
BEGIN_PUSH_NEXT_BUFFER();
const unsigned int BUFSIZE = 16;
char *buf = (char *) psg_pnalloc(req->pool, BUFSIZE);
const char *end = buf + BUFSIZE;
char *pos = buf;
pos += uintToString(req->httpMajor, pos, end - pos);
pos = appendData(pos, end, ".", 1);
pos += uintToString(req->httpMinor, pos, end - pos);
buffers[i].iov_base = (void *) buf;
buffers[i].iov_len = pos - buf;
dataSize += pos - buf;
} else {
char buf[16];
const char *end = buf + sizeof(buf);
char *pos = buf;
pos += uintToString(req->httpMajor, pos, end - pos);
pos = appendData(pos, end, ".", 1);
pos += uintToString(req->httpMinor, pos, end - pos);
dataSize += pos - buf;
}
INC_BUFFER_ITER(i);
PUSH_STATIC_BUFFER(" ");
statusAndReason = getStatusCodeAndReasonPhrase(resp->statusCode);
if (statusAndReason != NULL) {
size_t len = strlen(statusAndReason);
BEGIN_PUSH_NEXT_BUFFER();
if (buffers != NULL) {
BEGIN_PUSH_NEXT_BUFFER();
buffers[i].iov_base = (void *) statusAndReason;
buffers[i].iov_len = len;
}
INC_BUFFER_ITER(i);
dataSize += len;
PUSH_STATIC_BUFFER("\r\nStatus: ");
if (buffers != NULL) {
BEGIN_PUSH_NEXT_BUFFER();
buffers[i].iov_base = (void *) statusAndReason;
buffers[i].iov_len = len;
}
INC_BUFFER_ITER(i);
dataSize += len;
PUSH_STATIC_BUFFER("\r\n");
} else {
if (buffers != NULL) {
BEGIN_PUSH_NEXT_BUFFER();
const unsigned int BUFSIZE = 8;
char *buf = (char *) psg_pnalloc(req->pool, BUFSIZE);
const char *end = buf + BUFSIZE;
char *pos = buf;
unsigned int size = uintToString(resp->statusCode, pos, end - pos);
buffers[i].iov_base = (void *) buf;
buffers[i].iov_len = size;
INC_BUFFER_ITER(i);
dataSize += size;
PUSH_STATIC_BUFFER(" Unknown Reason-Phrase\r\nStatus: ");
BEGIN_PUSH_NEXT_BUFFER();
buffers[i].iov_base = (void *) buf;
buffers[i].iov_len = size;
INC_BUFFER_ITER(i);
dataSize += size;
PUSH_STATIC_BUFFER("\r\n");
} else {
char buf[8];
const char *end = buf + sizeof(buf);
char *pos = buf;
unsigned int size = uintToString(resp->statusCode, pos, end - pos);
INC_BUFFER_ITER(i);
dataSize += size;
dataSize += sizeof(" Unknown Reason-Phrase\r\nStatus: ") - 1;
INC_BUFFER_ITER(i);
dataSize += size;
INC_BUFFER_ITER(i);
dataSize += sizeof("\r\n");
INC_BUFFER_ITER(i);
}
}
while (*it != NULL) {
dataSize += it->header->origKey.size + sizeof(": ") - 1;
dataSize += it->header->val.size + sizeof("\r\n") - 1;
part = it->header->origKey.start;
while (part != NULL) {
if (buffers != NULL) {
BEGIN_PUSH_NEXT_BUFFER();
buffers[i].iov_base = (void *) part->data;
buffers[i].iov_len = part->size;
}
INC_BUFFER_ITER(i);
part = part->next;
}
if (buffers != NULL) {
BEGIN_PUSH_NEXT_BUFFER();
buffers[i].iov_base = (void *) ": ";
buffers[i].iov_len = sizeof(": ") - 1;
}
INC_BUFFER_ITER(i);
part = it->header->val.start;
while (part != NULL) {
if (buffers != NULL) {
BEGIN_PUSH_NEXT_BUFFER();
buffers[i].iov_base = (void *) part->data;
buffers[i].iov_len = part->size;
}
INC_BUFFER_ITER(i);
part = part->next;
}
if (buffers != NULL) {
BEGIN_PUSH_NEXT_BUFFER();
buffers[i].iov_base = (void *) "\r\n";
buffers[i].iov_len = sizeof("\r\n") - 1;
}
INC_BUFFER_ITER(i);
it.next();
}
// Add Date header. https://code.google.com/p/phusion-passenger/issues/detail?id=485
if (resp->date == NULL) {
unsigned int size;
if (buffers != NULL) {
BEGIN_PUSH_NEXT_BUFFER();
const unsigned int BUFSIZE = 60;
char *dateStr = (char *) psg_pnalloc(req->pool, BUFSIZE);
size = constructDateHeaderBuffersForResponse(dateStr, BUFSIZE);
buffers[i].iov_base = dateStr;
buffers[i].iov_len = size;
} else {
char dateStr[60];
size = constructDateHeaderBuffersForResponse(dateStr, sizeof(dateStr));
}
INC_BUFFER_ITER(i);
dataSize += size;
PUSH_STATIC_BUFFER("\r\n");
}
if (resp->setCookie != NULL) {
PUSH_STATIC_BUFFER("Set-Cookie: ");
part = resp->setCookie->start;
while (part != NULL) {
if (part->size == 1 && part->data[0] == '\n') {
// HeaderTable joins multiple Set-Cookie headers together using \n.
PUSH_STATIC_BUFFER("\r\nSet-Cookie: ");
} else {
if (buffers != NULL) {
BEGIN_PUSH_NEXT_BUFFER();
buffers[i].iov_base = (void *) part->data;
buffers[i].iov_len = part->size;
}
INC_BUFFER_ITER(i);
dataSize += part->size;
}
part = part->next;
}
PUSH_STATIC_BUFFER("\r\n");
}
nCacheableBuffers = i;
if (resp->bodyType == AppResponse::RBT_CONTENT_LENGTH) {
PUSH_STATIC_BUFFER("Content-Length: ");
if (buffers != NULL) {
BEGIN_PUSH_NEXT_BUFFER();
const unsigned int BUFSIZE = 16;
char *buf = (char *) psg_pnalloc(req->pool, BUFSIZE);
unsigned int size = integerToOtherBase<boost::uint64_t, 10>(
resp->aux.bodyInfo.contentLength, buf, BUFSIZE);
buffers[i].iov_base = (void *) buf;
buffers[i].iov_len = size;
dataSize += size;
} else {
dataSize += integerSizeInOtherBase<boost::uint64_t, 10>(
resp->aux.bodyInfo.contentLength);
}
INC_BUFFER_ITER(i);
PUSH_STATIC_BUFFER("\r\n");
} else if (resp->bodyType == AppResponse::RBT_CHUNKED && !req->dechunkResponse) {
PUSH_STATIC_BUFFER("Transfer-Encoding: chunked\r\n");
}
if (resp->bodyType == AppResponse::RBT_UPGRADE) {
PUSH_STATIC_BUFFER("Connection: upgrade\r\n");
} else if (canKeepAlive(req)) {
unsigned int httpVersion = req->httpMajor * 1000 + req->httpMinor * 10;
if (httpVersion < 1010) {
// HTTP < 1.1 defaults to "Connection: close"
PUSH_STATIC_BUFFER("Connection: keep-alive\r\n");
}
} else {
unsigned int httpVersion = req->httpMajor * 1000 + req->httpMinor * 10;
if (httpVersion >= 1010) {
// HTTP 1.1 defaults to "Connection: keep-alive"
PUSH_STATIC_BUFFER("Connection: close\r\n");
}
}
if (req->stickySession) {
StaticString baseURI = req->options.baseURI;
if (baseURI.empty()) {
baseURI = P_STATIC_STRING("/");
}
// Note that we do NOT set HttpOnly. If we set that flag then Chrome
// doesn't send cookies over WebSocket handshakes. Confirmed on Chrome 25.
const LString *cookieName = getStickySessionCookieName(req);
unsigned int stickySessionId;
unsigned int stickySessionIdSize;
char *stickySessionIdStr;
PUSH_STATIC_BUFFER("Set-Cookie: ");
part = cookieName->start;
while (part != NULL) {
if (buffers != NULL) {
BEGIN_PUSH_NEXT_BUFFER();
buffers[i].iov_base = (void *) part->data;
buffers[i].iov_len = part->size;
}
dataSize += part->size;
INC_BUFFER_ITER(i);
part = part->next;
}
stickySessionId = req->session->getStickySessionId();
stickySessionIdSize = uintSizeAsString(stickySessionId);
stickySessionIdStr = (char *) psg_pnalloc(req->pool, stickySessionIdSize + 1);
uintToString(stickySessionId, stickySessionIdStr, stickySessionIdSize + 1);
PUSH_STATIC_BUFFER("=");
if (buffers != NULL) {
BEGIN_PUSH_NEXT_BUFFER();
buffers[i].iov_base = stickySessionIdStr;
buffers[i].iov_len = stickySessionIdSize;
}
dataSize += stickySessionIdSize;
INC_BUFFER_ITER(i);
PUSH_STATIC_BUFFER("; Path=");
if (buffers != NULL) {
BEGIN_PUSH_NEXT_BUFFER();
buffers[i].iov_base = (void *) baseURI.data();
buffers[i].iov_len = baseURI.size();
}
dataSize += baseURI.size();
INC_BUFFER_ITER(i);
StaticString stickyAttributes = req->options.stickySessionsCookieAttributes;
if (stickyAttributes.size() > 0) {
PUSH_STATIC_BUFFER("; ");
if (buffers != NULL) {
BEGIN_PUSH_NEXT_BUFFER();
buffers[i].iov_base = (void *) stickyAttributes.data();
buffers[i].iov_len = stickyAttributes.size();
}
dataSize += stickyAttributes.size();
INC_BUFFER_ITER(i);
}
PUSH_STATIC_BUFFER("\r\n");
}
if (req->config->showVersionInHeader) {
#ifdef PASSENGER_IS_ENTERPRISE
PUSH_STATIC_BUFFER("X-Powered-By: " PROGRAM_NAME " Enterprise " PASSENGER_VERSION "\r\n\r\n");
#else
PUSH_STATIC_BUFFER("X-Powered-By: " PROGRAM_NAME " " PASSENGER_VERSION "\r\n\r\n");
#endif
} else {
#ifdef PASSENGER_IS_ENTERPRISE
PUSH_STATIC_BUFFER("X-Powered-By: " PROGRAM_NAME " Enterprise\r\n\r\n");
#else
PUSH_STATIC_BUFFER("X-Powered-By: " PROGRAM_NAME "\r\n\r\n");
#endif
}
nbuffers = i;
return true;
#undef BEGIN_PUSH_NEXT_BUFFER
#undef INC_BUFFER_ITER
#undef PUSH_STATIC_BUFFER
}
unsigned int
Controller::constructDateHeaderBuffersForResponse(char *dateStr, unsigned int bufsize) {
char *pos = dateStr;
const char *end = dateStr + bufsize - 1;
time_t the_time = (time_t) ev_now(getContext()->libev->getLoop());
struct tm the_tm;
pos = appendData(pos, end, "Date: ");
gmtime_r(&the_time, &the_tm);
pos += strftime(pos, end - pos, "%a, %d %b %Y %H:%M:%S GMT", &the_tm);
return pos - dateStr;
}
bool
Controller::sendResponseHeaderWithWritev(Client *client, Request *req,
ssize_t &bytesWritten)
{
TRACE_POINT();
if (OXT_UNLIKELY(mainConfig.benchmarkMode == BM_RESPONSE_BEGIN)) {
writeBenchmarkResponse(&client, &req, false);
return true;
}
unsigned int maxbuffers = std::min<unsigned int>(
8 + req->appResponse.headers.size() * 4 + 11, IOV_MAX);
struct iovec *buffers = (struct iovec *) psg_palloc(req->pool,
sizeof(struct iovec) * maxbuffers);
unsigned int nbuffers, dataSize, nCacheableBuffers;
if (constructHeaderBuffersForResponse(req, buffers,
maxbuffers, nbuffers, dataSize, nCacheableBuffers))
{
UPDATE_TRACE_POINT();
SKC_TRACE(client, 2, "Sending response headers using writev()");
logResponseHeaders(client, req, buffers, nbuffers, dataSize);
markHeaderBuffersForTurboCaching(client, req, buffers, nCacheableBuffers);
ssize_t ret;
do {
ret = writev(client->getFd(), buffers, nbuffers);
} while (ret == -1 && errno == EINTR);
bytesWritten = ret;
req->responseBegun |= ret > 0;
return ret == (ssize_t) dataSize;
} else {
UPDATE_TRACE_POINT();
bytesWritten = 0;
return false;
}
}
void
Controller::sendResponseHeaderWithBuffering(Client *client, Request *req,
unsigned int offset)
{
TRACE_POINT();
struct iovec *buffers;
unsigned int nbuffers, dataSize, nCacheableBuffers;
bool ok;
ok = constructHeaderBuffersForResponse(req, NULL, 0, nbuffers, dataSize,
nCacheableBuffers);
assert(ok);
buffers = (struct iovec *) psg_palloc(req->pool,
sizeof(struct iovec) * nbuffers);
ok = constructHeaderBuffersForResponse(req, buffers, nbuffers,
nbuffers, dataSize, nCacheableBuffers);
assert(ok);
(void) ok; // Shut up compiler warning
UPDATE_TRACE_POINT();
logResponseHeaders(client, req, buffers, nbuffers, dataSize);
markHeaderBuffersForTurboCaching(client, req, buffers, nCacheableBuffers);
MemoryKit::mbuf_pool &mbuf_pool = getContext()->mbuf_pool;
const unsigned int MBUF_MAX_SIZE = mbuf_pool_data_size(&mbuf_pool);
if (dataSize <= MBUF_MAX_SIZE) {
UPDATE_TRACE_POINT();
SKC_TRACE(client, 2, "Sending response headers using an mbuf");
MemoryKit::mbuf buffer(MemoryKit::mbuf_get(&mbuf_pool));
gatherBuffers(buffer.start, MBUF_MAX_SIZE, buffers, nbuffers);
buffer = MemoryKit::mbuf(buffer, offset, dataSize - offset);
writeResponse(client, buffer);
} else {
UPDATE_TRACE_POINT();
SKC_TRACE(client, 2, "Sending response headers using a psg_pool buffer");
char *buffer = (char *) psg_pnalloc(req->pool, dataSize);
gatherBuffers(buffer, dataSize, buffers, nbuffers);
writeResponse(client, buffer + offset, dataSize - offset);
}
}
void
Controller::logResponseHeaders(Client *client, Request *req, struct iovec *buffers,
unsigned int nbuffers, unsigned int dataSize)
{
if (OXT_UNLIKELY(LoggingKit::getLevel() >= LoggingKit::DEBUG3)) {
TRACE_POINT();
char *buffer = (char *) psg_pnalloc(req->pool, dataSize);
gatherBuffers(buffer, dataSize, buffers, nbuffers);
SKC_TRACE(client, 3, "Sending response headers: \"" <<
cEscapeString(StaticString(buffer, dataSize)) << "\"");
}
}
void
Controller::markHeaderBuffersForTurboCaching(Client *client, Request *req,
struct iovec *buffers, unsigned int nbuffers)
{
if (turboCaching.isEnabled() && !req->cacheKey.empty()) {
unsigned int totalSize = 0;
for (unsigned int i = 0; i < nbuffers; i++) {
totalSize += buffers[i].iov_len;
}
if (totalSize > ResponseCache<Request>::MAX_HEADER_SIZE) {
SKC_DEBUG(client, "Response headers larger than " <<
ResponseCache<Request>::MAX_HEADER_SIZE <<
" bytes, so response is not eligible for turbocaching");
// Decrease store success ratio.
turboCaching.responseCache.incStores();
req->cacheKey = HashedStaticString();
} else {
req->appResponse.headerCacheBuffers = buffers;
req->appResponse.nHeaderCacheBuffers = nbuffers;
}
}
}
ServerKit::HttpHeaderParser<AppResponse, ServerKit::HttpParseResponse>
Controller::createAppResponseHeaderParser(ServerKit::Context *ctx, Request *req) {
return ServerKit::HttpHeaderParser<AppResponse, ServerKit::HttpParseResponse>(
ctx, req->appResponse.parserState.headerParser,
&req->appResponse, req->pool, req->method);
}
ServerKit::HttpChunkedBodyParser
Controller::createAppResponseChunkedBodyParser(Request *req) {
return ServerKit::HttpChunkedBodyParser(
&req->appResponse.parserState.chunkedBodyParser,
formatAppResponseChunkedBodyParserLoggingPrefix,
req);
}
unsigned int
Controller::formatAppResponseChunkedBodyParserLoggingPrefix(char *buf,
unsigned int bufsize, void *userData)
{
Request *req = static_cast<Request *>(userData);
return snprintf(buf, bufsize,
"[Client %u] ChunkedBodyParser: ",
static_cast<Client *>(req->client)->number);
}
void
Controller::prepareAppResponseChunkedBodyParsing(Client *client, Request *req) {
P_ASSERT_EQ(req->appResponse.bodyType, AppResponse::RBT_CHUNKED);
createAppResponseChunkedBodyParser(req).initialize();
}
void
Controller::writeResponseAndMarkForTurboCaching(Client *client, Request *req,
const MemoryKit::mbuf &buffer)
{
if (OXT_LIKELY(mainConfig.benchmarkMode != BM_RESPONSE_BEGIN)) {
writeResponse(client, buffer);
}
markResponsePartForTurboCaching(client, req, buffer);
}
void
Controller::markResponsePartForTurboCaching(Client *client, Request *req,
const MemoryKit::mbuf &buffer)
{
if (!req->ended() && turboCaching.isEnabled() && !req->cacheKey.empty()) {
unsigned int totalSize = req->appResponse.bodyCacheBuffer.size + buffer.size();
if (totalSize > ResponseCache<Request>::MAX_BODY_SIZE) {
SKC_DEBUG(client, "Response body larger than " <<
ResponseCache<Request>::MAX_HEADER_SIZE <<
" bytes, so response is not eligible for turbocaching");
// Decrease store success ratio.
turboCaching.responseCache.incStores();
req->cacheKey = HashedStaticString();
psg_lstr_deinit(&req->appResponse.bodyCacheBuffer);
} else {
psg_lstr_append(&req->appResponse.bodyCacheBuffer, req->pool, buffer,
buffer.start, buffer.size());
}
}
}
void
Controller::maybeThrottleAppSource(Client *client, Request *req) {
if (!req->ended()) {
assert(client->output.getBuffersFlushedCallback() == NULL);
assert(client->output.getDataFlushedCallback() == getClientOutputDataFlushedCallback());
if (mainConfig.responseBufferHighWatermark > 0
&& client->output.getTotalBytesBuffered() >= mainConfig.responseBufferHighWatermark)
{
SKC_TRACE(client, 2, "Application is sending response data quicker than the client "
"can keep up with. Throttling application socket");
client->output.setDataFlushedCallback(_outputDataFlushed);
req->appSource.stop();
} else if (client->output.passedThreshold()) {
SKC_TRACE(client, 2, "Application is sending response data quicker than the on-disk "
"buffer can keep up with (currently buffered " << client->output.getBytesBuffered() <<
" bytes). Throttling application socket");
client->output.setBuffersFlushedCallback(_outputBuffersFlushed);
req->appSource.stop();
}
}
}
void
Controller::_outputBuffersFlushed(FileBufferedChannel *_channel) {
FileBufferedFdSinkChannel *channel = reinterpret_cast<FileBufferedFdSinkChannel *>(_channel);
Client *client = static_cast<Client *>(static_cast<
ServerKit::BaseClient *>(channel->getHooks()->userData));
Request *req = static_cast<Request *>(client->currentRequest);
Controller *self = static_cast<Controller *>(getServerFromClient(client));
if (client->connected() && req != NULL) {
self->outputBuffersFlushed(client, req);
}
}
void
Controller::outputBuffersFlushed(Client *client, Request *req) {
if (!req->ended()) {
assert(!req->appSource.isStarted());
SKC_TRACE(client, 2, "Buffered response data has been written to disk. Resuming application socket");
client->output.clearBuffersFlushedCallback();
req->appSource.start();
}
}
void
Controller::_outputDataFlushed(FileBufferedChannel *_channel) {
FileBufferedFdSinkChannel *channel = reinterpret_cast<FileBufferedFdSinkChannel *>(_channel);
Client *client = static_cast<Client *>(static_cast<
ServerKit::BaseClient *>(channel->getHooks()->userData));
Request *req = static_cast<Request *>(client->currentRequest);
Controller *self = static_cast<Controller *>(getServerFromClient(client));
getClientOutputDataFlushedCallback()(_channel);
if (client->connected() && req != NULL) {
self->outputDataFlushed(client, req);
}
}
void
Controller::outputDataFlushed(Client *client, Request *req) {
if (!req->ended()) {
assert(!req->appSource.isStarted());
SKC_TRACE(client, 2, "The client is ready to receive more data. Resuming application socket");
client->output.setDataFlushedCallback(getClientOutputDataFlushedCallback());
req->appSource.start();
}
}
void
Controller::handleAppResponseBodyEnd(Client *client, Request *req) {
keepAliveAppConnection(client, req);
storeAppResponseInTurboCache(client, req);
assert(!req->ended());
}
OXT_FORCE_INLINE void
Controller::keepAliveAppConnection(Client *client, Request *req) {
if (req->halfClosePolicy == Request::HALF_CLOSE_PERFORMED) {
SKC_TRACE(client, 2, "Not keep-aliving application session connection"
" because it had been half-closed before");
req->session->close(true, false);
} else {
// halfClosePolicy is initialized in sendHeaderToApp(). That method is
// called immediately after checking out a session, before any events
// from the appSource channel can be received.
assert(req->halfClosePolicy != Request::HALF_CLOSE_POLICY_UNINITIALIZED);
if (req->appResponse.wantKeepAlive) {
SKC_TRACE(client, 2, "Keep-aliving application session connection");
req->session->close(true, true);
} else {
SKC_TRACE(client, 2, "Not keep-aliving application session connection"
" because application did not allow it");
req->session->close(true, false);
}
}
}
void
Controller::storeAppResponseInTurboCache(Client *client, Request *req) {
if (turboCaching.isEnabled() && !req->cacheKey.empty()) {
TRACE_POINT();
AppResponse *resp = &req->appResponse;
unsigned int headerSize = 0;
unsigned int i;
for (i = 0; i < resp->nHeaderCacheBuffers; i++) {
headerSize += resp->headerCacheBuffers[i].iov_len;
}
ResponseCache<Request>::Entry entry(
turboCaching.responseCache.store(req, ev_now(getLoop()),
headerSize, resp->bodyCacheBuffer.size));
if (entry.valid()) {
UPDATE_TRACE_POINT();
SKC_DEBUG(client, "Storing app response in turbocache");
SKC_TRACE(client, 2, "Turbocache entries:\n" << turboCaching.responseCache.inspect());
gatherBuffers(entry.body->httpHeaderData,
ResponseCache<Request>::MAX_HEADER_SIZE,
resp->headerCacheBuffers, resp->nHeaderCacheBuffers);
char *pos = entry.body->httpBodyData;
const char *end = entry.body->httpBodyData
+ ResponseCache<Request>::MAX_BODY_SIZE;
const LString::Part *part = resp->bodyCacheBuffer.start;
while (part != NULL) {
pos = appendData(pos, end, part->data, part->size);
part = part->next;
}
} else {
SKC_DEBUG(client, "Could not store app response for turbocaching");
}
}
}
} // namespace Core
} // namespace Passenger