You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@trafficserver.apache.org by jp...@apache.org on 2012/08/21 05:26:51 UTC

[2/2] git commit: TS-1408: Plugin to implement the stale-while-revalidate and stale-if-error features of RFC5861

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 <ph...@omniti.com>
Authored: Mon Aug 20 20:18:07 2012 -0700
Committer: James Peach <jp...@apache.org>
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 <ph...@omniti.com>
+
   *) [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 = "phil@omniti.com";
+
+    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/');