Skip to content

Commit

Permalink
Implement turbocache varying by cookie value
Browse files Browse the repository at this point in the history
  • Loading branch information
FooBarWidget committed Dec 23, 2014
1 parent a6a16c8 commit a760649
Show file tree
Hide file tree
Showing 20 changed files with 670 additions and 18 deletions.
4 changes: 4 additions & 0 deletions build/cxx_tests.rb
Expand Up @@ -123,6 +123,10 @@
ext/common/ServerKit/HttpRequest.h
ext/common/ServerKit/HttpHeaderParser.h
ext/common/ServerKit/HttpChunkedBodyParser.h),
'test/cxx/ServerKit/CookieUtilsTest.o' => %w(
test/cxx/ServerKit/CookieUtilsTest.cpp
ext/common/ServerKit/CookieUtils.h
ext/common/DataStructures/LString.h),
'test/cxx/DataStructures/LStringTest.o' => %w(
test/cxx/DataStructures/LStringTest.cpp
ext/common/DataStructures/LString.h),
Expand Down
10 changes: 10 additions & 0 deletions ext/common/DataStructures/LString.h
Expand Up @@ -117,6 +117,16 @@ psg_lstr_append_part(LString *str, LString::Part *part) {
part->next = NULL;
}

inline void
psg_lstr_append_part_from_another_lstr(LString *str, psg_pool_t *pool, const LString::Part *part) {
LString::Part *copy = (LString::Part *) psg_palloc(pool, sizeof(LString::Part));
*copy = *part;
if (part->mbuf_block != NULL) {
mbuf_block_ref(part->mbuf_block);
}
psg_lstr_append_part(str, copy);
}

inline void
psg_lstr_append(LString *str, psg_pool_t *pool, const MemoryKit::mbuf &buffer,
const char *data, unsigned int size)
Expand Down
246 changes: 246 additions & 0 deletions ext/common/ServerKit/CookieUtils.h
@@ -0,0 +1,246 @@
/*
* Phusion Passenger - https://www.phusionpassenger.com/
* Copyright (c) 2014 Phusion
*
* "Phusion Passenger" is a trademark of Hongli Lai & Ninh Bui.
*
* 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.
*/
#ifndef _PASSENGER_SERVER_KIT_COOKIE_UTILS_H_
#define _PASSENGER_SERVER_KIT_COOKIE_UTILS_H_

#include <cstring>
#include <cassert>
#include <MemoryKit/palloc.h>
#include <DataStructures/LString.h>

namespace Passenger {
namespace ServerKit {


inline bool findCookieNameValueSeparator(const LString::Part *part, size_t index,
const LString::Part **separatorPart, size_t *separatorIndex);
inline bool findCookieEnd(const LString::Part *separatorPart, size_t separatorIndex,
const LString::Part **endPart, size_t *endIndex);
inline bool matchCookieName(psg_pool_t *pool, const LString::Part *part, size_t index,
const LString::Part *separatorPart, size_t separatorIndex,
const LString *name);
inline LString *extractCookieValue(psg_pool_t *pool,
const LString::Part *separatorPart, size_t separatorIndex,
const LString::Part *endPart, size_t endIndex);


/**
* Given the value of an HTTP cookie header, returns the value of the cookie
* of the given name, or NULL if not found.
*/
inline LString *
findCookie(psg_pool_t *pool, const LString *cookieHeaderValue, const LString *name) {
const LString::Part *part = cookieHeaderValue->start;
const LString::Part *separatorPart, *endPart;
size_t index = 0, separatorIndex, endIndex;
bool done = part == NULL;
LString *result = NULL;

while (!done) {
if (findCookieNameValueSeparator(part, index, &separatorPart, &separatorIndex)) {
if (!findCookieEnd(separatorPart, separatorIndex, &endPart, &endIndex)) {
done = true;
} else if (matchCookieName(pool, part, index, separatorPart, separatorIndex, name)) {
result = extractCookieValue(pool, separatorPart, separatorIndex, endPart, endIndex);
done = true;
} else {
part = endPart;
index = endIndex;
done = endIndex >= endPart->size;
}
} else {
done = true;
}
}

return result;
}

inline bool
findCookieNameValueSeparator(const LString::Part *part, size_t index,
const LString::Part **separatorPart, size_t *separatorIndex)
{
const char *pos;
bool result = false;
bool done = part == NULL;

while (!done) {
pos = (const char *) memchr(part->data + index, '=', part->size - index);
if (pos == NULL) {
part = part->next;
index = 0;
done = part == NULL;
} else {
*separatorPart = part;
*separatorIndex = pos - part->data;
result = true;
done = true;
}
}

return result;
}

inline bool
findCookieEnd(const LString::Part *separatorPart, size_t separatorIndex,
const LString::Part **endPart, size_t *endIndex)
{
const LString::Part *part = separatorPart;
size_t index = separatorIndex;
const char *pos;
bool result = false;
bool done = part == NULL;

while (!done) {
pos = (const char *) memchr(part->data + index, ';', part->size - index);
if (pos == NULL) {
if (part->next == NULL) {
// Semicolon not found in entire LString. Return end-of-LString
// as cookie end.
*endPart = part;
*endIndex = part->size;
result = true;
done = true;
} else {
part = part->next;
index = 0;
done = part == NULL;
}
} else {
// Semicolon found.
*endPart = part;
*endIndex = pos - part->data;
result = true;
done = true;
}
}

return result;
}

inline void
_matchCookieName_skipWhitespace(LString *str) {
LString::Part *part = str->start;
size_t pos = 0;
bool done = false;

while (!done) {
while (part->data[pos] == ' ' || part->data[pos] == ';') {
pos++;
}

if (pos == part->size) {
str->start = part->next;
str->size -= part->size;
part = part->next;
if (part == NULL) {
assert(str->size == 0);
done = true;
str->end = NULL;
}
} else {
part->data += pos;
part->size -= pos;
str->size -= pos;
done = true;
}
}
}

inline bool
matchCookieName(psg_pool_t *pool, const LString::Part *part, size_t index,
const LString::Part *separatorPart, size_t separatorIndex,
const LString *name)
{
LString *str = (LString *) psg_palloc(pool, sizeof(LString));
psg_lstr_init(str);

if (part == separatorPart) {
assert(index < separatorIndex);
psg_lstr_append(str, pool,
part->data + index,
separatorIndex - index);
} else {
psg_lstr_append(str, pool,
part->data + index,
part->size - index);

part = part->next;
while (part != separatorPart) {
psg_lstr_append(str, pool, part->data, part->size);
part = part->next;
}

if (separatorIndex != 0) {
psg_lstr_append(str, pool, separatorPart->data, separatorIndex);
}
}

_matchCookieName_skipWhitespace(str);

bool result = psg_lstr_cmp(str, name);
psg_lstr_deinit(str);
return result;
}

inline LString *
extractCookieValue(psg_pool_t *pool,
const LString::Part *separatorPart, size_t separatorIndex,
const LString::Part *endPart, size_t endIndex)
{
LString *str = (LString *) psg_palloc(pool, sizeof(LString));
psg_lstr_init(str);

if (separatorPart == endPart) {
assert(separatorIndex < endIndex);
psg_lstr_append(str, pool,
separatorPart->data + separatorIndex + 1,
endIndex - separatorIndex - 1);
} else {
if (separatorIndex < separatorPart->size - 1) {
psg_lstr_append(str, pool,
separatorPart->data + separatorIndex + 1,
separatorPart->size - separatorIndex - 1);
}

const LString::Part *part = separatorPart->next;
while (part != endPart) {
psg_lstr_append(str, pool, part->data, part->size);
part = part->next;
}

if (endIndex != 0) {
psg_lstr_append(str, pool, endPart->data, endIndex);
}
}

return str;
}


} // namespace ServerKit
} // namespace Passenger

#endif /* _PASSENGER_SERVER_KIT_COOKIE_UTILS_H_ */
1 change: 1 addition & 0 deletions ext/common/agents/HelperAgent/Main.cpp
Expand Up @@ -975,6 +975,7 @@ setAgentsOptionsDefaults() {
options.setDefaultInt("stat_throttle_rate", DEFAULT_STAT_THROTTLE_RATE);
options.setDefault("server_software", SERVER_TOKEN_NAME "/" PASSENGER_VERSION);
options.setDefaultBool("show_version_in_header", true);
options.setDefault("sticky_sessions_cookie_name", DEFAULT_STICKY_SESSIONS_COOKIE_NAME);
options.setDefaultBool("turbocaching", true);
options.setDefault("data_buffer_dir", getSystemTempDir());
options.setDefaultBool("selfchecks", false);
Expand Down
16 changes: 14 additions & 2 deletions ext/common/agents/HelperAgent/OptionParser.h
Expand Up @@ -108,8 +108,6 @@ serverUsage() {
printf(" Force friendly error pages to be always on\n");
printf(" --disable-friendly-error-pages\n");
printf(" Force friendly error pages to be always off\n");
printf(" --disable-turbocaching\n");
printf(" Disable turbocaching\n");
printf("\n");
printf(" --ruby PATH Default Ruby interpreter to use.\n");
printf("\n");
Expand All @@ -123,6 +121,14 @@ serverUsage() {
printf(" may be idle. Default: %d\n", DEFAULT_POOL_IDLE_TIME);
printf(" --min-instances N Minimum number of application processes. Default: 1\n");
printf("\n");
printf("Request handling options (optional):\n");
printf(" --sticky-sessions-cookie-name NAME\n");
printf(" Cookie name to use for sticky sessions\n");
printf(" --vary-turbocache-by-cookie NAME\n");
printf(" Vary the turbocache by the cookie of the given name\n");
printf(" --disable-turbocaching\n");
printf(" Disable turbocaching\n");
printf("\n");
printf("Other options (optional):\n");
printf(" --log-file PATH Log to the given file.\n");
printf(" --log-level LEVEL Logging level. Default: %d\n", DEFAULT_LOG_LEVEL);
Expand Down Expand Up @@ -262,6 +268,12 @@ parseServerOption(int argc, const char *argv[], int &i, VariantMap &options) {
} else if (p.isFlag(argv[i], '\0', "--disable-friendly-error-pages")) {
options.set("friendly_error_pages", "false");
i++;
} else if (p.isValueFlag(argc, i, argv[i], '\0', "--sticky-sessions-cookie-name")) {
options.set("sticky_sessions_cookie_name", argv[i + 1]);
i += 2;
} else if (p.isValueFlag(argc, i, argv[i], '\0', "--vary-turbocache-by-cookie")) {
options.set("vary_turbocache_by_cookie", argv[i + 1]);
i += 2;
} else if (p.isFlag(argv[i], '\0', "--disable-turbocaching")) {
options.setBool("turbocaching", false);
i++;
Expand Down
10 changes: 10 additions & 0 deletions ext/common/agents/HelperAgent/RequestHandler.h
Expand Up @@ -206,6 +206,8 @@ class RequestHandler: public ServerKit::HttpServer<RequestHandler, Client> {
StaticString defaultServerName;
StaticString defaultServerPort;
StaticString serverSoftware;
StaticString defaultStickySessionsCookieName;
StaticString defaultVaryTurbocacheByCookie;

HashedStaticString PASSENGER_APP_GROUP_NAME;
HashedStaticString PASSENGER_MAX_REQUESTS;
Expand All @@ -231,6 +233,7 @@ class RequestHandler: public ServerKit::HttpServer<RequestHandler, Client> {
StaticString serverLogName;

friend class TurboCaching<Request>;
friend class ResponseCache<Request>;
struct ev_check checkWatcher;
TurboCaching<Request> turboCaching;

Expand Down Expand Up @@ -307,6 +310,13 @@ class RequestHandler: public ServerKit::HttpServer<RequestHandler, Client> {
agentsOptions->get("default_server_port"));
serverSoftware = psg_pstrdup(stringPool,
agentsOptions->get("server_software"));
defaultStickySessionsCookieName = psg_pstrdup(stringPool,
agentsOptions->get("sticky_sessions_cookie_name"));

if (agentsOptions->has("vary_turbocache_by_cookie")) {
defaultVaryTurbocacheByCookie = psg_pstrdup(stringPool,
agentsOptions->get("vary_turbocache_by_cookie"));
}

generateServerLogName(_threadNumber);

Expand Down
1 change: 1 addition & 0 deletions ext/common/agents/HelperAgent/RequestHandler/Hooks.cpp
Expand Up @@ -90,6 +90,7 @@ virtual void reinitializeRequest(Client *client, Request *req) {
req->bodyBytesBuffered = 0;
req->cacheKey = HashedStaticString();
req->cacheControl = NULL;
req->varyCookie = NULL;

#ifdef DEBUG_RH_EVENT_LOOP_BLOCKING
req->timedAppPoolGet = false;
Expand Down

0 comments on commit a760649

Please sign in to comment.