TS-1408: Plugin to implement the stale-while-revalidate and stale-if-error features of RFC5861
Project: http://git-wip-us.apache.org/repos/asf/trafficserver/repo Commit: http://git-wip-us.apache.org/repos/asf/trafficserver/commit/a506b902 Tree: http://git-wip-us.apache.org/repos/asf/trafficserver/tree/a506b902 Diff: http://git-wip-us.apache.org/repos/asf/trafficserver/diff/a506b902 Branch: refs/heads/maste Commit: a506b902ce55312e348382e3642b574c4c490bae Parents: 938279e Author: Phil Sorber <[email protected]> Authored: Mon Aug 20 20:18:07 2012 -0700 Committer: James Peach <[email protected]> Committed: Mon Aug 20 20:21:20 2012 -0700 ---------------------------------------------------------------------- CHANGES | 4 + configure.ac | 1 + plugins/Makefile.am | 1 + plugins/experimental/rfc5861/Makefile.am | 22 + plugins/experimental/rfc5861/Makefile.tsxs | 10 + plugins/experimental/rfc5861/README | 14 + plugins/experimental/rfc5861/rfc5861.c | 746 ++++++++++++++++++++++ plugins/experimental/rfc5861/test_server.js | 17 + 8 files changed, 815 insertions(+), 0 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/trafficserver/blob/a506b902/CHANGES ---------------------------------------------------------------------- diff --git a/CHANGES b/CHANGES index 74e7ccc..62fc68f 100644 --- a/CHANGES +++ b/CHANGES @@ -1,6 +1,10 @@ -*- coding: utf-8 -*- Changes with Apache Traffic Server 3.3.0 + *) [TS-1408] Plugin to implement the stale-while-revalidate and + stale-if-error features of RFC5861 + Author: Phil Sorber <[email protected]> + *) [TS-1406] add ESI to experimental plugins build *) [TS-1387] Allow proxy.config.http.insert_age_in_response to be overridden http://git-wip-us.apache.org/repos/asf/trafficserver/blob/a506b902/configure.ac ---------------------------------------------------------------------- diff --git a/configure.ac b/configure.ac index 7ef05c2..5a6b07b 100644 --- a/configure.ac +++ b/configure.ac @@ -1558,6 +1558,7 @@ AC_CONFIG_FILES([plugins/stats_over_http/Makefile]) # experimental plugins AC_CONFIG_FILES([plugins/experimental/esi/Makefile]) AC_CONFIG_FILES([plugins/experimental/lua/Makefile]) +AC_CONFIG_FILES([plugins/experimental/rfc5861/Makefile]) AC_CONFIG_FILES([plugins/experimental/tcp_info/Makefile]) AC_CONFIG_FILES([plugins/experimental/custom_redirect/Makefile]) AC_CONFIG_FILES([plugins/experimental/header_rewrite/Makefile]) http://git-wip-us.apache.org/repos/asf/trafficserver/blob/a506b902/plugins/Makefile.am ---------------------------------------------------------------------- diff --git a/plugins/Makefile.am b/plugins/Makefile.am index 361a7b1..d5bbcf0 100644 --- a/plugins/Makefile.am +++ b/plugins/Makefile.am @@ -20,6 +20,7 @@ if BUILD_EXPERIMENTAL_PLUGINS SUBDIRS += \ experimental/lua \ experimental/esi \ + experimental/rfc5861 \ experimental/tcp_info \ experimental/custom_redirect \ experimental/header_rewrite http://git-wip-us.apache.org/repos/asf/trafficserver/blob/a506b902/plugins/experimental/rfc5861/Makefile.am ---------------------------------------------------------------------- diff --git a/plugins/experimental/rfc5861/Makefile.am b/plugins/experimental/rfc5861/Makefile.am new file mode 100644 index 0000000..723d592 --- /dev/null +++ b/plugins/experimental/rfc5861/Makefile.am @@ -0,0 +1,22 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +AM_CPPFLAGS = -I$(top_builddir)/proxy/api -I$(top_srcdir)/proxy/api + +pkglibdir = ${pkglibexecdir} +pkglib_LTLIBRARIES = rfc5861.la +rfc5861_la_SOURCES = rfc5861.c +rfc5861_la_LDFLAGS = -module -avoid-version -shared http://git-wip-us.apache.org/repos/asf/trafficserver/blob/a506b902/plugins/experimental/rfc5861/Makefile.tsxs ---------------------------------------------------------------------- diff --git a/plugins/experimental/rfc5861/Makefile.tsxs b/plugins/experimental/rfc5861/Makefile.tsxs new file mode 100644 index 0000000..292b05f --- /dev/null +++ b/plugins/experimental/rfc5861/Makefile.tsxs @@ -0,0 +1,10 @@ +TSXS?=tsxs + +all: rfc5861.c + $(TSXS) -v -c $? -o rfc5861.so + +install: + $(TSXS) -i -o rfc5861.so + +clean: + rm -f *.lo *.so http://git-wip-us.apache.org/repos/asf/trafficserver/blob/a506b902/plugins/experimental/rfc5861/README ---------------------------------------------------------------------- diff --git a/plugins/experimental/rfc5861/README b/plugins/experimental/rfc5861/README new file mode 100644 index 0000000..2c0fd36 --- /dev/null +++ b/plugins/experimental/rfc5861/README @@ -0,0 +1,14 @@ +Quick install: + +Make sure devel packages for traffic-server are installed. +Make sure that 'tsxs' is in your path. + + make -f Makefile.tsxs + make -f Makefile.tsxs install + +Add 'rfc5861.so' to plugin.config. + +Restart traffic-server. + +test_server.js is a test server written in JavaScript and +meant to be run under node.js. http://git-wip-us.apache.org/repos/asf/trafficserver/blob/a506b902/plugins/experimental/rfc5861/rfc5861.c ---------------------------------------------------------------------- diff --git a/plugins/experimental/rfc5861/rfc5861.c b/plugins/experimental/rfc5861/rfc5861.c new file mode 100644 index 0000000..d77ed14 --- /dev/null +++ b/plugins/experimental/rfc5861/rfc5861.c @@ -0,0 +1,746 @@ +#include <stdlib.h> +#include <stdio.h> +#include <time.h> +#include <string.h> +#include <stdbool.h> +#include <search.h> +#include <ts/ts.h> + +//#include <sys/socket.h> +//#include <netinet/in.h> +#include <arpa/inet.h> + +#include <ts/experimental.h> + +#ifdef TS_VERSION_NUMBER + #if ((TS_VERSION_NUMBER >= 3000000) && (TS_VERSION_NUMBER < 3001000)) + #define TS_API_30X + #elif ((TS_VERSION_NUMBER >= 3001000) && (TS_VERSION_NUMBER < 3003000)) + #define TS_API_32X + #else + #error Cannot determine API version! + #endif +#else + #error TS_VERSION_NUMBER not defined! +#endif + +#define LOG_PREFIX "rfc5861" + +//#define ENABLE_SAVE_ORIGINAL_REQUEST + +static const char HTTP_VALUE_STALE_WHILE_REVALIDATE[] = "stale-while-revalidate"; +static const char HTTP_VALUE_STALE_IF_ERROR[] = "stale-if-error"; +static const char HTTP_VALUE_STALE_WARNING[] = "110 Response is stale"; + +void *troot = NULL; +TSMutex troot_mutex; +int txn_slot; + +typedef struct +{ + time_t date, stale_while_revalidate, stale_on_error, max_age; +} CachedHeaderInfo; + +typedef struct +{ + char *effective_url; + TSMBuffer buf; + TSMLoc http_hdr_loc; + struct sockaddr *client_addr; +} RequestInfo; + +typedef struct +{ + TSMBuffer buf; + TSMLoc http_hdr_loc; + TSHttpParser parser; + bool parsed; + TSHttpStatus status; +} ResponseInfo; + +typedef struct +{ + TSHttpTxn txn; + TSCont main_cont; + bool async_req; + TSIOBuffer req_io_buf, resp_io_buf; + TSIOBufferReader req_io_buf_reader, resp_io_buf_reader; + TSVIO r_vio, w_vio; + TSVConn vconn; + RequestInfo *req_info; + ResponseInfo *resp_info; +} StateInfo; + +static ResponseInfo* +create_response_info(void) +{ + ResponseInfo *resp_info; + + TSDebug(LOG_PREFIX, "Entering create_response_info"); + + resp_info = (ResponseInfo *) TSmalloc(sizeof(ResponseInfo)); + + resp_info->buf = TSMBufferCreate(); + resp_info->http_hdr_loc = TSHttpHdrCreate(resp_info->buf); + resp_info->parser = TSHttpParserCreate(); + resp_info->parsed = false; + + TSDebug(LOG_PREFIX, "Leaving create_reseponse_info"); + + return resp_info; +} + +static void +free_response_info(ResponseInfo *resp_info) +{ + TSDebug(LOG_PREFIX, "Entering free_response_info"); + + TSHandleMLocRelease(resp_info->buf, TS_NULL_MLOC, resp_info->http_hdr_loc); + TSMBufferDestroy(resp_info->buf); + TSHttpParserDestroy(resp_info->parser); + TSfree(resp_info); + + TSDebug(LOG_PREFIX, "Leaving free_response_info"); +} + +static RequestInfo* +create_request_info(TSHttpTxn txn) +{ + RequestInfo *req_info; + char *url; + int url_len; + TSMBuffer buf; + TSMLoc loc; + + TSDebug(LOG_PREFIX, "Entering create_request_info"); + + req_info = (RequestInfo *) TSmalloc(sizeof(RequestInfo)); + + url = TSHttpTxnEffectiveUrlStringGet(txn, &url_len); + req_info->effective_url = TSstrndup(url, url_len); + TSfree(url); + //TSDebug(LOG_PREFIX, "URL: %s", req_info->effective_url); + + TSHttpTxnClientReqGet(txn, &buf, &loc); + req_info->buf = TSMBufferCreate(); + TSHttpHdrClone(req_info->buf, buf, loc, &(req_info->http_hdr_loc)); + TSHandleMLocRelease(buf, TS_NULL_MLOC, loc); + + req_info->client_addr = TSmalloc(sizeof(struct sockaddr)); + memmove((void *) req_info->client_addr, (void *) TSHttpTxnClientAddrGet(txn), sizeof(struct sockaddr)); + + TSDebug(LOG_PREFIX, "Leaving create_request_info"); + + return req_info; +} + +static void +free_request_info(RequestInfo *req_info) +{ + TSDebug(LOG_PREFIX, "Entering free_request_info"); + TSDebug(LOG_PREFIX, "Free effective URL"); + //TSDebug(LOG_PREFIX, "URL: %s", req_info->effective_url); + TSfree(req_info->effective_url); + TSDebug(LOG_PREFIX, "Release Http Header"); + TSHandleMLocRelease(req_info->buf, TS_NULL_MLOC, req_info->http_hdr_loc); + TSDebug(LOG_PREFIX, "Destroy Buffer"); + TSMBufferDestroy(req_info->buf); + TSDebug(LOG_PREFIX, "Free Client Addr"); + TSfree(req_info->client_addr); + TSDebug(LOG_PREFIX, "Free Request Info"); + TSfree(req_info); + + TSDebug(LOG_PREFIX, "Leaving free_request_info"); +} + +static CachedHeaderInfo* +get_cached_header_info(TSHttpTxn txn) +{ + CachedHeaderInfo* chi; + TSMBuffer cr_buf; + TSMLoc cr_hdr_loc, cr_date_loc, cr_cache_control_loc, cr_cache_control_dup_loc; + int cr_cache_control_count, val_len, i; + char *value, *ptr; + + chi = (CachedHeaderInfo *) TSmalloc(sizeof(CachedHeaderInfo)); + chi->date = 0; + chi->max_age = 0; + chi->stale_while_revalidate = 0; + chi->stale_on_error = 0; + + TSDebug(LOG_PREFIX, "Inside get_cached_header_info"); + + if (TSHttpTxnCachedRespGet(txn, &cr_buf, &cr_hdr_loc) == TS_SUCCESS) + { + cr_date_loc = TSMimeHdrFieldFind(cr_buf, cr_hdr_loc, TS_MIME_FIELD_DATE, TS_MIME_LEN_DATE); + if (cr_date_loc != TS_NULL_MLOC) + { + TSDebug(LOG_PREFIX, "Found a date"); + chi->date = TSMimeHdrFieldValueDateGet(cr_buf, cr_hdr_loc, cr_date_loc); + TSHandleMLocRelease(cr_buf, cr_hdr_loc, cr_date_loc); + } + + cr_cache_control_loc = TSMimeHdrFieldFind(cr_buf, cr_hdr_loc, TS_MIME_FIELD_CACHE_CONTROL, TS_MIME_LEN_CACHE_CONTROL); + + while(cr_cache_control_loc != TS_NULL_MLOC) + { + TSDebug(LOG_PREFIX, "Found cache-control"); + cr_cache_control_count = TSMimeHdrFieldValuesCount(cr_buf, cr_hdr_loc, cr_cache_control_loc); + + for (i = 0; i < cr_cache_control_count; i++) + { + value = (char *) TSMimeHdrFieldValueStringGet(cr_buf, cr_hdr_loc, cr_cache_control_loc, i, &val_len); + ptr = value; + + if (strncmp(value, TS_HTTP_VALUE_MAX_AGE, TS_HTTP_LEN_MAX_AGE) == 0) + { + TSDebug(LOG_PREFIX, "Found max-age"); + ptr += TS_HTTP_LEN_MAX_AGE; + if (*ptr == '=') + { + ptr++; + chi->max_age = atol(ptr); + } + else + { + ptr = TSstrndup(value, TS_HTTP_LEN_MAX_AGE + 2); + TSDebug(LOG_PREFIX, "This is what I found: %s", ptr); + TSfree(ptr); + } + } + else if (strncmp(value, HTTP_VALUE_STALE_WHILE_REVALIDATE, strlen(HTTP_VALUE_STALE_WHILE_REVALIDATE)) == 0) + { + TSDebug(LOG_PREFIX, "Found stale-while-revalidate"); + ptr += strlen(HTTP_VALUE_STALE_WHILE_REVALIDATE); + if (*ptr == '=') + { + ptr++; + chi->stale_while_revalidate = atol(ptr); + } + } + else if (strncmp(value, HTTP_VALUE_STALE_IF_ERROR, strlen(HTTP_VALUE_STALE_IF_ERROR)) == 0) + { + TSDebug(LOG_PREFIX, "Found stale-on-error"); + ptr += strlen(HTTP_VALUE_STALE_IF_ERROR); + if (*ptr == '=') + { + ptr++; + chi->stale_on_error = atol(ptr); + } + } + else + { + TSDebug(LOG_PREFIX, "Unknown field value"); + } + } + + cr_cache_control_dup_loc = TSMimeHdrFieldNextDup(cr_buf, cr_hdr_loc, cr_cache_control_loc); + TSHandleMLocRelease(cr_buf, cr_hdr_loc, cr_cache_control_loc); + cr_cache_control_loc = cr_cache_control_dup_loc; + } + TSHandleMLocRelease(cr_buf, TS_NULL_MLOC, cr_hdr_loc); + } + + TSDebug(LOG_PREFIX, "Leaving get_cached_header_info"); + return chi; +} + +static int +xstrcmp(const void *a, const void *b) +{ + return strcmp((const char *) a, (const char *) b); +} + +static void +parse_response(StateInfo *state) +{ + TSIOBufferBlock block; + TSParseResult pr = TS_PARSE_CONT; + int64_t avail; + char *start; + + TSDebug(LOG_PREFIX, "Entering parse_response"); + + block = TSIOBufferReaderStart(state->resp_io_buf_reader); + + while ((pr == TS_PARSE_CONT) && (block != NULL)) + { + start = (char *) TSIOBufferBlockReadStart(block, state->resp_io_buf_reader, &avail); + if (avail > 0) + { + pr = TSHttpHdrParseResp(state->resp_info->parser, state->resp_info->buf, state->resp_info->http_hdr_loc, (const char **) &start, (const char *) (start + avail)); + } + block = TSIOBufferBlockNext(block); + } + + if (pr != TS_PARSE_CONT) + { + state->resp_info->status = TSHttpHdrStatusGet(state->resp_info->buf, state->resp_info->http_hdr_loc); + state->resp_info->parsed = true; + TSDebug(LOG_PREFIX, "HTTP Status: %d", state->resp_info->status); + } + + TSDebug(LOG_PREFIX, "Leaving parse_response"); +} + +static int +consume_resource(TSCont cont, TSEvent event, void *edata) +{ + StateInfo *state; + int64_t avail; + TSVConn vconn; + TSMLoc url_loc; + int lookup_count; + + TSDebug(LOG_PREFIX, "Entering consume_resource"); + + vconn = (TSVConn) edata; + state = (StateInfo *) TSContDataGet(cont); + + switch (event) + { + case TS_EVENT_VCONN_WRITE_READY: + // We shouldn't get here because we specify the exact size of the buffer. + TSDebug(LOG_PREFIX, "Write Ready"); + case TS_EVENT_VCONN_WRITE_COMPLETE: + TSDebug(LOG_PREFIX, "Write Complete"); + //TSDebug(LOG_PREFIX, "TSVConnShutdown()"); + //TSVConnShutdown(state->vconn, 0, 1); + //TSVIOReenable(state->w_vio); + break; + case TS_EVENT_VCONN_READ_READY: + TSDebug(LOG_PREFIX, "Read Ready"); + + avail = TSIOBufferReaderAvail(state->resp_io_buf_reader); + + if ((state->resp_info) && !state->resp_info->parsed) + { + parse_response(state); + } + + // Consume data + avail = TSIOBufferReaderAvail(state->resp_io_buf_reader); + TSIOBufferReaderConsume(state->resp_io_buf_reader, avail); + TSVIONDoneSet(state->r_vio, TSVIONDoneGet(state->r_vio) + avail); + TSVIOReenable(state->r_vio); + break; + case TS_EVENT_VCONN_READ_COMPLETE: + case TS_EVENT_VCONN_EOS: + case TS_EVENT_VCONN_INACTIVITY_TIMEOUT: + if (event == TS_EVENT_VCONN_INACTIVITY_TIMEOUT) + { + TSDebug(LOG_PREFIX, "Inactivity Timeout"); + TSDebug(LOG_PREFIX, "TSVConnAbort()"); + TSVConnAbort(vconn, TS_VC_CLOSE_ABORT); + } + else + { + if (event == TS_EVENT_VCONN_READ_COMPLETE) + { + TSDebug(LOG_PREFIX, "Read Complete"); + } + else if (event == TS_EVENT_VCONN_EOS) + { + TSDebug(LOG_PREFIX, "EOS"); + } + TSDebug(LOG_PREFIX, "TSVConnClose()"); + TSVConnClose(state->vconn); + } + + avail = TSIOBufferReaderAvail(state->resp_io_buf_reader); + + if ((state->resp_info) && !state->resp_info->parsed) + { + parse_response(state); + } + + // Consume data + avail = TSIOBufferReaderAvail(state->resp_io_buf_reader); + TSIOBufferReaderConsume(state->resp_io_buf_reader, avail); + TSVIONDoneSet(state->r_vio, TSVIONDoneGet(state->r_vio) + avail); + if (state->async_req) + { + TSDebug(LOG_PREFIX, "Unlock URL"); + TSMutexLock(troot_mutex); + tdelete(state->req_info->effective_url, &troot, xstrcmp); + TSMutexUnlock(troot_mutex); + } + else + { + TSDebug(LOG_PREFIX, "In sync path. setting fresh and re-enabling"); + TSHttpTxnCacheLookupCountGet(state->txn, &lookup_count); + if ((state->resp_info->status == 500) || ((state->resp_info->status >= 502) && (state->resp_info->status <= 504)) || lookup_count > 2) + { + TSDebug(LOG_PREFIX, "Sending stale data as fresh"); + TSHttpTxnHookAdd(state->txn, TS_HTTP_SEND_RESPONSE_HDR_HOOK, state->main_cont); + TSHttpTxnCacheLookupStatusSet(state->txn, TS_CACHE_LOOKUP_HIT_FRESH); + } + else + { + TSDebug(LOG_PREFIX, "Attempting new cache lookup"); + TSHttpHdrUrlGet(state->req_info->buf, state->req_info->http_hdr_loc, &url_loc); + TSHttpTxnNewCacheLookupDo(state->txn, state->req_info->buf, url_loc); + TSHandleMLocRelease(state->req_info->buf, state->req_info->http_hdr_loc, url_loc); + // TODO add txn translation hook and pass result along, maybe inside continuation? + //TSHttpTxnHookAdd(state->txn, TS_HTTP_RESPONSE_TRANSFORM_HOOK, TSTransformCreate(replace_transform, state->txn)); + } + TSHttpTxnReenable(state->txn, TS_EVENT_HTTP_CONTINUE); + } + free_request_info(state->req_info); + if (state->resp_info) + { + free_response_info(state->resp_info); + } + TSIOBufferReaderFree(state->req_io_buf_reader); + TSIOBufferDestroy(state->req_io_buf); + TSIOBufferReaderFree(state->resp_io_buf_reader); + TSIOBufferDestroy(state->resp_io_buf); + TSfree(state); + TSDebug(LOG_PREFIX, "Destroying Cont"); + TSContDestroy(cont); + break; + default: + TSError("Unknown event %d.", event); + break; + } + + TSDebug(LOG_PREFIX, "Leaving consume_resource"); + return 0; +} + +static int +fetch_resource(TSCont cont, TSEvent event, void *edata) +{ + StateInfo *state; + TSCont consume_cont; + //struct sockaddr_in client_addr; + TSMLoc connection_hdr_loc, connection_hdr_dup_loc; + + TSDebug(LOG_PREFIX, "Entering fetch_resource"); + + state = (StateInfo *) TSContDataGet(cont); + + TSDebug(LOG_PREFIX, "state: %p", state); + + //li = (RequestInfo *) edata; + TSMutexLock(troot_mutex); + // If already doing async lookup lets just close shop and go home + if (state->async_req && (tfind(state->req_info->effective_url, &troot, xstrcmp) != NULL)) + { + TSDebug(LOG_PREFIX, "Looks like an async is already in progress"); + TSMutexUnlock(troot_mutex); + free_request_info(state->req_info); + TSfree(state); + } + // Otherwise lets do the lookup! + else + { + TSDebug(LOG_PREFIX, "Lets do the lookup"); + if (state->async_req) + { + // Lock in tree + TSDebug(LOG_PREFIX, "Locking URL"); + tsearch(state->req_info->effective_url, &troot, xstrcmp); + } + TSMutexUnlock(troot_mutex); + consume_cont = TSContCreate(consume_resource, NULL); + TSContDataSet(consume_cont, (void *) state); + + if (state->async_req) + { + state->resp_info = NULL; + } + else + { + state->resp_info = create_response_info(); + } + + TSDebug(LOG_PREFIX, "Set Connection: close"); + connection_hdr_loc = TSMimeHdrFieldFind(state->req_info->buf, state->req_info->http_hdr_loc, TS_MIME_FIELD_CONNECTION, TS_MIME_LEN_CONNECTION); + + while(connection_hdr_loc != TS_NULL_MLOC) + { + TSDebug(LOG_PREFIX, "Found old Connection hdr"); + + connection_hdr_dup_loc = TSMimeHdrFieldNextDup(state->req_info->buf, state->req_info->http_hdr_loc, connection_hdr_loc); + TSMimeHdrFieldRemove(state->req_info->buf, state->req_info->http_hdr_loc, connection_hdr_loc); + TSMimeHdrFieldDestroy(state->req_info->buf, state->req_info->http_hdr_loc, connection_hdr_loc); + TSHandleMLocRelease(state->req_info->buf, state->req_info->http_hdr_loc, connection_hdr_loc); + connection_hdr_loc = connection_hdr_dup_loc; + } + + // This seems to have little effect + TSDebug(LOG_PREFIX, "Creating Connection hdr"); + TSMimeHdrFieldCreateNamed(state->req_info->buf, state->req_info->http_hdr_loc, TS_MIME_FIELD_CONNECTION, TS_MIME_LEN_CONNECTION, &connection_hdr_loc); + TSMimeHdrFieldValueStringInsert(state->req_info->buf, state->req_info->http_hdr_loc, connection_hdr_loc, -1, TS_HTTP_VALUE_CLOSE, TS_HTTP_LEN_CLOSE); + TSMimeHdrFieldAppend(state->req_info->buf, state->req_info->http_hdr_loc, connection_hdr_loc); + TSHandleMLocRelease(state->req_info->buf, state->req_info->http_hdr_loc, connection_hdr_loc); + + /* + TSDebug(LOG_PREFIX, "Creating @RFC5861 header"); + TSMimeHdrFieldCreateNamed(state->req_info->buf, state->req_info->http_hdr_loc, TS_MIME_FIELD_CONNECTION, TS_MIME_LEN_CONNECTION, &connection_hdr_loc); + TSMimeHdrFieldValueStringInsert(state->req_info->buf, state->req_info->http_hdr_loc, connection_hdr_loc, -1, TS_HTTP_VALUE_CLOSE, TS_HTTP_LEN_CLOSE); + TSMimeHdrFieldAppend(state->req_info->buf, state->req_info->http_hdr_loc, connection_hdr_loc); + TSHandleMLocRelease(state->req_info->buf, state->req_info->http_hdr_loc, connection_hdr_loc); + */ + + TSDebug(LOG_PREFIX, "Create Buffers"); + state->req_io_buf = TSIOBufferCreate(); + state->req_io_buf_reader = TSIOBufferReaderAlloc(state->req_io_buf); + state->resp_io_buf = TSIOBufferCreate(); + state->resp_io_buf_reader = TSIOBufferReaderAlloc(state->resp_io_buf); + + TSDebug(LOG_PREFIX, "HdrPrint()"); + TSHttpHdrPrint(state->req_info->buf, state->req_info->http_hdr_loc, state->req_io_buf); + TSIOBufferWrite(state->req_io_buf, "\r\n", 2); + + TSDebug(LOG_PREFIX, "TSHttpConnect()"); + //memmove((void *) &client_addr, (void *) state->req_info->client_addr, sizeof(struct sockaddr)); + //TSDebug(LOG_PREFIX, "client_addr: %s:%d", inet_ntoa(client_addr.sin_addr), client_addr.sin_port); + state->vconn = TSHttpConnect((struct sockaddr const *) state->req_info->client_addr); + + TSDebug(LOG_PREFIX, "TSVConnRead()"); + state->r_vio = TSVConnRead(state->vconn, consume_cont, state->resp_io_buf, INT64_MAX); + TSDebug(LOG_PREFIX, "TSVConnWrite()"); + state->w_vio = TSVConnWrite(state->vconn, consume_cont, state->req_io_buf_reader, TSIOBufferReaderAvail(state->req_io_buf_reader)); + } + + TSContDestroy(cont); + TSDebug(LOG_PREFIX, "Leaving fetch_resource"); + + return 0; +} + +static int +rfc5861_plugin(TSCont cont, TSEvent event, void *edata) +{ + TSHttpTxn txn = (TSHttpTxn) edata; + int status, lookup_count; + CachedHeaderInfo *chi; + time_t now; + TSCont fetch_cont; + StateInfo *state; + TSMBuffer buf; + TSMLoc loc,warn_loc; + TSHttpStatus http_status; + + time(&now); + + TSDebug(LOG_PREFIX, "Entering rfc5861_plugin"); + switch (event) + { + // Is this the proper event? + case TS_EVENT_HTTP_READ_REQUEST_HDR: + TSDebug(LOG_PREFIX, "Event: TS_EVENT_HTTP_READ_REQUEST_HDR"); + + if (TSHttpIsInternalRequest(txn) != TS_SUCCESS) + { + TSDebug(LOG_PREFIX, "External Request"); + state = TSmalloc(sizeof(StateInfo)); + state->req_info = create_request_info(txn); + TSDebug(LOG_PREFIX, "state after TSmalloc: %p", state); + TSHttpTxnArgSet(txn, txn_slot, (void *) state); + TSHttpTxnHookAdd(txn, TS_HTTP_CACHE_LOOKUP_COMPLETE_HOOK, cont); + } + else + { + TSDebug(LOG_PREFIX, "Internal Request"); // This is insufficient if there are other plugins using TSHttpConnect + //TSHttpTxnHookAdd(txn, TS_HTTP_CACHE_LOOKUP_COMPLETE_HOOK, cont); + TSHttpTxnHookAdd(txn, TS_HTTP_READ_RESPONSE_HDR_HOOK, cont); + // This might be needed in 3.2.0 to fix a timeout issue + //TSHttpTxnConfigIntSet(txn, TS_CONFIG_HTTP_TRANSACTION_NO_ACTIVITY_TIMEOUT_IN, 5); + //TSHttpTxnConfigIntSet(txn, TS_CONFIG_HTTP_TRANSACTION_NO_ACTIVITY_TIMEOUT_OUT, 5); + } + + TSHttpTxnReenable(txn, TS_EVENT_HTTP_CONTINUE); + TSDebug(LOG_PREFIX, "TS_EVENT_HTTP_READ_REQUEST_HDR Event Handler End"); + break; + case TS_EVENT_HTTP_CACHE_LOOKUP_COMPLETE: + TSDebug(LOG_PREFIX, "Event: TS_EVENT_HTTP_CACHE_LOOKUP_COMPLETE"); + state = (StateInfo *) TSHttpTxnArgGet(txn, txn_slot); + TSHttpTxnCacheLookupCountGet(txn, &lookup_count); + TSDebug(LOG_PREFIX, "state after arg get: %p", state); + if (TSHttpTxnCacheLookupStatusGet(txn, &status) == TS_SUCCESS) + { + // Are we stale? + if (status == TS_CACHE_LOOKUP_HIT_STALE) + { + TSDebug(LOG_PREFIX, "CacheLookupStatus is STALE"); + // Get headers + chi = get_cached_header_info(txn); + + if ((now - chi->date) < (chi->max_age + chi->stale_while_revalidate)) + { + TSDebug(LOG_PREFIX, "Looks like we can return fresh info and validate in the background"); + // lookup async + + // Set warning header + TSHttpTxnHookAdd(txn, TS_HTTP_SEND_RESPONSE_HDR_HOOK, cont); + + TSDebug(LOG_PREFIX, "set state as async"); + state->async_req = true; + TSDebug(LOG_PREFIX, "TSHttpTxnCacheLookupStatusSet()"); + TSHttpTxnCacheLookupStatusSet(txn, TS_CACHE_LOOKUP_HIT_FRESH); + //TSHttpTxnReenable(txn, TS_EVENT_HTTP_CONTINUE); + TSDebug(LOG_PREFIX, "TSContCreate()"); + fetch_cont = TSContCreate(fetch_resource, NULL); + TSDebug(LOG_PREFIX, "TSContDataSet()"); + TSContDataSet(fetch_cont, (void *) state); + TSDebug(LOG_PREFIX, "state: %p", state); + TSContSchedule(fetch_cont, 0, TS_THREAD_POOL_TASK); + TSDebug(LOG_PREFIX, "TSHttpTxnReenable()"); + TSHttpTxnReenable(txn, TS_EVENT_HTTP_CONTINUE); + } + else if ((now - chi->date) < (chi->max_age + chi->stale_on_error)) + { + TSDebug(LOG_PREFIX, "Looks like we can return fresh data on 500 error"); + //lookup sync + state->async_req = false; + state->txn = txn; + state->main_cont = cont; // we need this for the warning header callback. not sure i like it, but it works. + fetch_cont = TSContCreate(fetch_resource, NULL); + TSContDataSet(fetch_cont, (void *) state); + TSContSchedule(fetch_cont, 0, TS_THREAD_POOL_TASK); + } + else + { + TSDebug(LOG_PREFIX, "No love? now: %d date: %d max-age: %d swr: %d soe: %d", (int) now, (int) chi->date, (int) chi->max_age, (int) chi->stale_while_revalidate, (int) chi->stale_on_error); + if (lookup_count == 1) + { + free_request_info(state->req_info); + TSfree(state); + } + TSHttpTxnReenable(txn, TS_EVENT_HTTP_CONTINUE); + } + + TSfree(chi); + } + else + { + TSDebug(LOG_PREFIX, "Not Stale!"); + if (lookup_count == 1) + { + free_request_info(state->req_info); + TSfree(state); + } + TSHttpTxnReenable(txn, TS_EVENT_HTTP_CONTINUE); + } + } + else + { + TSDebug(LOG_PREFIX, "Could not get CacheLookupStatus"); + if (lookup_count == 1) + { + free_request_info(state->req_info); + TSfree(state); + } + TSHttpTxnReenable(txn, TS_EVENT_HTTP_CONTINUE); + } + TSDebug(LOG_PREFIX, "TS_EVENT_HTTP_CACHE_LOOKUP_COMPLETE Event Handler End"); + break; + case TS_EVENT_HTTP_READ_RESPONSE_HDR: + TSDebug(LOG_PREFIX, "Event: TS_EVENT_HTTP_READ_RESPONSE_HDR"); + TSHttpTxnServerRespGet(txn, &buf, &loc); + http_status = TSHttpHdrStatusGet(buf, loc); + if ((http_status == 500) || ((http_status >= 502) && (http_status <= 504))) // 500, 502, 503, or 504 + { + TSDebug(LOG_PREFIX, "Set non-cachable"); + //TSHttpTxnRespCacheableSet(txn, 0); + TSHttpTxnServerRespNoStore(txn); + } + TSHandleMLocRelease(buf, TS_NULL_MLOC, loc); + TSHttpTxnReenable(txn, TS_EVENT_HTTP_CONTINUE); + TSDebug(LOG_PREFIX, "TS_EVENT_HTTP_READ_RESPONSE_HDR Event Handler End"); + break; + case TS_EVENT_HTTP_SEND_RESPONSE_HDR: + TSDebug(LOG_PREFIX, "Event: TS_EVENT_HTTP_SEND_RESPONSE_HDR"); + TSDebug(LOG_PREFIX, "set warning header"); + TSHttpTxnClientRespGet(txn, &buf, &loc); + TSMimeHdrFieldCreateNamed(buf, loc, TS_MIME_FIELD_WARNING, TS_MIME_LEN_WARNING, &warn_loc); + TSMimeHdrFieldValueStringInsert(buf, loc, warn_loc, -1, HTTP_VALUE_STALE_WARNING, strlen(HTTP_VALUE_STALE_WARNING)); + TSMimeHdrFieldAppend(buf, loc, warn_loc); + TSHandleMLocRelease(buf, loc, warn_loc); + TSHandleMLocRelease(buf, TS_NULL_MLOC, loc); + TSHttpTxnReenable(txn, TS_EVENT_HTTP_CONTINUE); + TSDebug(LOG_PREFIX, "TS_EVENT_HTTP_SEND_RESPONSE_HDR Event Handler End"); + break; + default: + TSHttpTxnReenable(txn, TS_EVENT_HTTP_CONTINUE); + break; + } + + TSDebug(LOG_PREFIX, "Leaving rfc5861_plugin"); + + return 0; +} + +static int +check_ts_version() +{ + const char *ts_version = TSTrafficServerVersionGet(); + int result = 0; + + if (ts_version) + { + int major_ts_version = 0; + int minor_ts_version = 0; + int patch_ts_version = 0; + + if (sscanf(ts_version, "%d.%d.%d", &major_ts_version, &minor_ts_version, &patch_ts_version) != 3) + { + return 0; + } + +#ifdef TS_API_32X + /* We need Traffic Server 3.2.x */ + if ((major_ts_version == 3) && (minor_ts_version >= 1) && (minor_ts_version <= 2)) +#elif defined TS_API_30X + /* We need Traffic Server 3.0.x */ + if ((major_ts_version >= 3) && (minor_ts_version == 0)) +#endif + { + result = 1; + } + } + + return result; +} + +void +TSPluginInit (int argc, const char *argv[]) +{ + TSPluginRegistrationInfo info; + TSCont main_cont; + + info.plugin_name = "rfc5861"; + info.vendor_name = "OmniTI Computer Consulting on behalf of Oregon Health & Science University"; + info.support_email = "[email protected]"; + + if (TSPluginRegister(TS_SDK_VERSION_3_0 , &info) != TS_SUCCESS) + { + TSError("Plugin registration failed.\n"); + return; + } + else + { + TSDebug(LOG_PREFIX, "Plugin registration succeeded.\n"); + } + + if (!check_ts_version()) + { +#ifdef TS_API_32X + TSError("Plugin requires Traffic Server 3.2\n"); +#elif defined TS_API_30X + TSError("Plugin requires Traffic Server 3.0\n"); +#endif + return; + } + + // proxy.config.http.insert_age_in_response + TSHttpArgIndexReserve("rfc5861_state", "txn state info for rfc5861", &txn_slot); + troot_mutex = TSMutexCreate(); + main_cont = TSContCreate(rfc5861_plugin, NULL); + TSHttpHookAdd(TS_HTTP_READ_REQUEST_HDR_HOOK, main_cont); + + TSDebug(LOG_PREFIX, "Plugin Init Complete.\n"); +} http://git-wip-us.apache.org/repos/asf/trafficserver/blob/a506b902/plugins/experimental/rfc5861/test_server.js ---------------------------------------------------------------------- diff --git a/plugins/experimental/rfc5861/test_server.js b/plugins/experimental/rfc5861/test_server.js new file mode 100644 index 0000000..5bb731f --- /dev/null +++ b/plugins/experimental/rfc5861/test_server.js @@ -0,0 +1,17 @@ +var http = require('http'); +var url = require('url'); + +http.createServer(function (request, response) { + console.log(Date()); + setTimeout( function (req, res) { + console.log(req.headers); + console.log(url.parse(req.url)); + res.writeHead(200, {'Content-Type': 'text/plain', 'Cache-Control': 'max-age=5, stale-while-revalidate=55'}); + //res.writeHead(500, {'Content-Type': 'text/plain', 'Cache-Control': 'max-age=5, stale-if-error=555'}); + res.end(Date() + '\n'); + console.log(Date() + '\n'); + }, 50, request, response); +}).listen(8081, '127.0.0.1'); + +console.log('Proxy running at http://127.0.0.1:8080/'); +console.log('Server running at http://127.0.0.1:8081/');
