| /** @file |
| |
| Implements RFC 5861 (HTTP Cache-Control Extensions for Stale Content) |
| |
| @section license License |
| |
| 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. |
| */ |
| |
| #include <stdlib.h> |
| #include <stdio.h> |
| #include <time.h> |
| #include <string.h> |
| #include <stdbool.h> |
| #include <search.h> |
| #include <getopt.h> |
| #include <arpa/inet.h> |
| |
| #include "ink_defs.h" |
| #include "ts/ts.h" |
| #include "ts/experimental.h" |
| |
| #define PLUGIN_NAME "rfc5861" |
| |
| 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"; |
| |
| typedef struct |
| { |
| TSTextLogObject object; |
| bool all, stale_if_error, stale_while_revalidate; |
| char* filename; |
| } log_info_t; |
| |
| typedef struct |
| { |
| void *troot; |
| TSMutex troot_mutex; |
| int txn_slot; |
| time_t stale_if_error_override; |
| log_info_t log_info; |
| } config_t; |
| |
| 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; |
| time_t txn_start; |
| config_t *plugin_config; |
| } StateInfo; |
| |
| static ResponseInfo* |
| create_response_info(void) |
| { |
| ResponseInfo *resp_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; |
| |
| return resp_info; |
| } |
| |
| static void |
| free_response_info(ResponseInfo *resp_info) |
| { |
| TSHandleMLocRelease(resp_info->buf, TS_NULL_MLOC, resp_info->http_hdr_loc); |
| TSMBufferDestroy(resp_info->buf); |
| TSHttpParserDestroy(resp_info->parser); |
| TSfree(resp_info); |
| } |
| |
| static RequestInfo* |
| create_request_info(TSHttpTxn txn) |
| { |
| RequestInfo *req_info; |
| char *url; |
| int url_len; |
| TSMBuffer buf; |
| TSMLoc loc; |
| |
| req_info = (RequestInfo *) TSmalloc(sizeof(RequestInfo)); |
| |
| url = TSHttpTxnEffectiveUrlStringGet(txn, &url_len); |
| req_info->effective_url = TSstrndup(url, url_len); |
| TSfree(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)); |
| |
| return req_info; |
| } |
| |
| static void |
| free_request_info(RequestInfo *req_info) |
| { |
| TSfree(req_info->effective_url); |
| TSHandleMLocRelease(req_info->buf, TS_NULL_MLOC, req_info->http_hdr_loc); |
| TSMBufferDestroy(req_info->buf); |
| TSfree(req_info->client_addr); |
| TSfree(req_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; |
| |
| 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(PLUGIN_NAME, "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(PLUGIN_NAME, "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(PLUGIN_NAME, "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(PLUGIN_NAME, "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(PLUGIN_NAME, "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(PLUGIN_NAME, "Found stale-on-error"); |
| ptr += strlen(HTTP_VALUE_STALE_IF_ERROR); |
| if (*ptr == '=') |
| { |
| ptr++; |
| chi->stale_on_error = atol(ptr); |
| } |
| } |
| else |
| { |
| TSDebug(PLUGIN_NAME, "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); |
| } |
| |
| 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; |
| |
| 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(PLUGIN_NAME, "HTTP Status: %d", state->resp_info->status); |
| } |
| } |
| |
| static int |
| consume_resource(TSCont cont, TSEvent event ATS_UNUSED, void *edata ATS_UNUSED) |
| { |
| StateInfo *state; |
| int64_t avail; |
| TSVConn vconn; |
| TSMLoc url_loc; |
| int lookup_count; |
| |
| 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(PLUGIN_NAME, "Write Ready"); |
| case TS_EVENT_VCONN_WRITE_COMPLETE: |
| TSDebug(PLUGIN_NAME, "Write Complete"); |
| //TSDebug(PLUGIN_NAME, "TSVConnShutdown()"); |
| //TSVConnShutdown(state->vconn, 0, 1); |
| //TSVIOReenable(state->w_vio); |
| break; |
| case TS_EVENT_VCONN_READ_READY: |
| TSDebug(PLUGIN_NAME, "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(PLUGIN_NAME, "Inactivity Timeout"); |
| TSVConnAbort(vconn, TS_VC_CLOSE_ABORT); |
| } |
| else |
| { |
| if (event == TS_EVENT_VCONN_READ_COMPLETE) |
| { |
| TSDebug(PLUGIN_NAME, "Read Complete"); |
| } |
| else if (event == TS_EVENT_VCONN_EOS) |
| { |
| TSDebug(PLUGIN_NAME, "EOS"); |
| } |
| 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(PLUGIN_NAME, "Unlock URL"); |
| TSMutexLock(state->plugin_config->troot_mutex); |
| tdelete(state->req_info->effective_url, &(state->plugin_config->troot), xstrcmp); |
| TSMutexUnlock(state->plugin_config->troot_mutex); |
| } |
| else |
| { |
| TSDebug(PLUGIN_NAME, "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(PLUGIN_NAME, "Sending stale data as fresh"); |
| if (state->plugin_config->log_info.object && (state->plugin_config->log_info.all || state->plugin_config->log_info.stale_if_error)) |
| { |
| CachedHeaderInfo *chi = get_cached_header_info(state->txn); |
| TSTextLogObjectWrite(state->plugin_config->log_info.object, "stale-if-error: %d - %d < %d + %d %s", (int) state->txn_start, (int) chi->date, (int) chi->max_age, (int) chi->stale_on_error, state->req_info->effective_url); |
| TSfree(chi); |
| } |
| TSHttpTxnHookAdd(state->txn, TS_HTTP_SEND_RESPONSE_HDR_HOOK, state->main_cont); |
| TSHttpTxnCacheLookupStatusSet(state->txn, TS_CACHE_LOOKUP_HIT_FRESH); |
| } |
| else |
| { |
| TSDebug(PLUGIN_NAME, "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); |
| TSContDestroy(cont); |
| break; |
| default: |
| TSError("Unknown event %d.", event); |
| break; |
| } |
| |
| return 0; |
| } |
| |
| static int |
| fetch_resource(TSCont cont, TSEvent event ATS_UNUSED, void *edata ATS_UNUSED) |
| { |
| StateInfo *state; |
| TSCont consume_cont; |
| //struct sockaddr_in client_addr; |
| TSMLoc connection_hdr_loc, connection_hdr_dup_loc; |
| |
| state = (StateInfo *) TSContDataGet(cont); |
| |
| //li = (RequestInfo *) edata; |
| if (state->async_req) |
| { |
| TSMutexLock(state->plugin_config->troot_mutex); |
| // If already doing async lookup lets just close shop and go home |
| if (tfind(state->req_info->effective_url, &(state->plugin_config->troot), xstrcmp) != NULL) |
| { |
| TSDebug(PLUGIN_NAME, "Looks like an async is already in progress"); |
| TSMutexUnlock(state->plugin_config->troot_mutex); |
| free_request_info(state->req_info); |
| TSfree(state); |
| } |
| // Otherwise lets do the lookup! |
| else |
| { |
| // Lock in tree |
| TSDebug(PLUGIN_NAME, "Locking URL"); |
| tsearch(state->req_info->effective_url, &(state->plugin_config->troot), xstrcmp); |
| TSMutexUnlock(state->plugin_config->troot_mutex); |
| } |
| } |
| |
| if (state) |
| { |
| TSDebug(PLUGIN_NAME, "Lets do the lookup"); |
| 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(PLUGIN_NAME, "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(PLUGIN_NAME, "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(PLUGIN_NAME, "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(PLUGIN_NAME, "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(PLUGIN_NAME, "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); |
| |
| TSHttpHdrPrint(state->req_info->buf, state->req_info->http_hdr_loc, state->req_io_buf); |
| TSIOBufferWrite(state->req_io_buf, "\r\n", 2); |
| |
| //memmove((void *) &client_addr, (void *) state->req_info->client_addr, sizeof(struct sockaddr)); |
| //TSDebug(PLUGIN_NAME, "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); |
| |
| state->r_vio = TSVConnRead(state->vconn, consume_cont, state->resp_io_buf, INT64_MAX); |
| state->w_vio = TSVConnWrite(state->vconn, consume_cont, state->req_io_buf_reader, TSIOBufferReaderAvail(state->req_io_buf_reader)); |
| } |
| |
| TSContDestroy(cont); |
| |
| return 0; |
| } |
| |
| static int |
| rfc5861_plugin(TSCont cont, TSEvent event, void *edata) |
| { |
| TSHttpTxn txn = (TSHttpTxn) edata; |
| int status, lookup_count; |
| CachedHeaderInfo *chi; |
| TSCont fetch_cont; |
| StateInfo *state; |
| TSMBuffer buf; |
| TSMLoc loc,warn_loc; |
| TSHttpStatus http_status; |
| config_t *plugin_config; |
| |
| switch (event) |
| { |
| // Is this the proper event? |
| case TS_EVENT_HTTP_READ_REQUEST_HDR: |
| |
| if (TSHttpIsInternalRequest(txn) != TS_SUCCESS) |
| { |
| TSDebug(PLUGIN_NAME, "External Request"); |
| plugin_config = (config_t *) TSContDataGet(cont); |
| state = TSmalloc(sizeof(StateInfo)); |
| state->plugin_config = plugin_config; |
| time(&state->txn_start); |
| state->req_info = create_request_info(txn); |
| TSHttpTxnArgSet(txn, state->plugin_config->txn_slot, (void *) state); |
| TSHttpTxnHookAdd(txn, TS_HTTP_CACHE_LOOKUP_COMPLETE_HOOK, cont); |
| } |
| else |
| { |
| TSDebug(PLUGIN_NAME, "Internal Request"); // This is insufficient if there are other plugins using TSHttpConnect |
| TSHttpTxnConfigIntSet(txn, TS_CONFIG_HTTP_SHARE_SERVER_SESSIONS, 1); |
| //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); |
| break; |
| case TS_EVENT_HTTP_CACHE_LOOKUP_COMPLETE: |
| plugin_config = (config_t *) TSContDataGet(cont); |
| state = (StateInfo *) TSHttpTxnArgGet(txn, plugin_config->txn_slot); |
| TSHttpTxnCacheLookupCountGet(txn, &lookup_count); |
| if (TSHttpTxnCacheLookupStatusGet(txn, &status) == TS_SUCCESS) |
| { |
| // Are we stale? |
| if (status == TS_CACHE_LOOKUP_HIT_STALE) |
| { |
| TSDebug(PLUGIN_NAME, "CacheLookupStatus is STALE"); |
| // Get headers |
| chi = get_cached_header_info(txn); |
| |
| if (state->plugin_config->stale_if_error_override > chi->stale_on_error) |
| chi->stale_on_error = state->plugin_config->stale_if_error_override; |
| |
| if ((state->txn_start - chi->date) < (chi->max_age + chi->stale_while_revalidate)) |
| { |
| TSDebug(PLUGIN_NAME, "Looks like we can return fresh info and validate in the background"); |
| if (state->plugin_config->log_info.object && (state->plugin_config->log_info.all || state->plugin_config->log_info.stale_while_revalidate)) |
| TSTextLogObjectWrite(state->plugin_config->log_info.object, "stale-while-revalidate: %d - %d < %d + %d %s", (int) state->txn_start, (int) chi->date, (int) chi->max_age, (int) chi->stale_while_revalidate, state->req_info->effective_url); |
| // lookup async |
| |
| #if (TS_VERSION_NUMBER >= 3003000) |
| TSHttpTxnConfigIntSet(txn, TS_CONFIG_HTTP_INSERT_AGE_IN_RESPONSE, 1); |
| #endif |
| // Set warning header |
| TSHttpTxnHookAdd(txn, TS_HTTP_SEND_RESPONSE_HDR_HOOK, cont); |
| |
| TSDebug(PLUGIN_NAME, "set state as async"); |
| state->async_req = true; |
| TSHttpTxnCacheLookupStatusSet(txn, TS_CACHE_LOOKUP_HIT_FRESH); |
| //TSHttpTxnReenable(txn, TS_EVENT_HTTP_CONTINUE); |
| fetch_cont = TSContCreate(fetch_resource, NULL); |
| TSContDataSet(fetch_cont, (void *) state); |
| TSContSchedule(fetch_cont, 0, TS_THREAD_POOL_TASK); |
| TSHttpTxnReenable(txn, TS_EVENT_HTTP_CONTINUE); |
| } |
| else if ((state->txn_start - chi->date) < (chi->max_age + chi->stale_on_error)) |
| { |
| TSDebug(PLUGIN_NAME, "Looks like we can return fresh data on 500 error"); |
| #if (TS_VERSION_NUMBER >= 3003000) |
| TSHttpTxnConfigIntSet(txn, TS_CONFIG_HTTP_INSERT_AGE_IN_RESPONSE, 1); |
| #endif |
| //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_NET); |
| } |
| else |
| { |
| TSDebug(PLUGIN_NAME, "No love? now: %d date: %d max-age: %d swr: %d soe: %d", (int) state->txn_start, (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(PLUGIN_NAME, "Not Stale!"); |
| if (lookup_count == 1) |
| { |
| free_request_info(state->req_info); |
| TSfree(state); |
| } |
| TSHttpTxnReenable(txn, TS_EVENT_HTTP_CONTINUE); |
| } |
| } |
| else |
| { |
| TSDebug(PLUGIN_NAME, "Could not get CacheLookupStatus"); |
| if (lookup_count == 1) |
| { |
| free_request_info(state->req_info); |
| TSfree(state); |
| } |
| TSHttpTxnReenable(txn, TS_EVENT_HTTP_CONTINUE); |
| } |
| break; |
| case 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(PLUGIN_NAME, "Set non-cachable"); |
| #if (TS_VERSION_NUMBER >= 3003000) |
| TSHttpTxnServerRespNoStoreSet(txn,1); |
| #else |
| TSHttpTxnServerRespNoStore(txn); |
| #endif |
| } |
| TSHandleMLocRelease(buf, TS_NULL_MLOC, loc); |
| TSHttpTxnReenable(txn, TS_EVENT_HTTP_CONTINUE); |
| break; |
| case TS_EVENT_HTTP_SEND_RESPONSE_HDR: |
| TSDebug(PLUGIN_NAME, "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); |
| break; |
| default: |
| TSHttpTxnReenable(txn, TS_EVENT_HTTP_CONTINUE); |
| break; |
| } |
| |
| return 0; |
| } |
| |
| void |
| TSPluginInit (int argc, const char *argv[]) |
| { |
| config_t *plugin_config; |
| TSPluginRegistrationInfo info; |
| TSCont main_cont; |
| |
| info.plugin_name = PLUGIN_NAME; |
| info.vendor_name = "Apache Software Foundation"; |
| info.support_email = "dev@trafficserver.apache.org"; |
| |
| if (TSPluginRegister(TS_SDK_VERSION_3_0 , &info) != TS_SUCCESS) |
| { |
| TSError("Plugin registration failed.\n"); |
| return; |
| } |
| else |
| { |
| TSDebug(PLUGIN_NAME, "Plugin registration succeeded.\n"); |
| } |
| |
| plugin_config = TSmalloc(sizeof(config_t)); |
| |
| plugin_config->troot = NULL; |
| plugin_config->troot_mutex = TSMutexCreate(); |
| plugin_config->stale_if_error_override = 0; |
| plugin_config->log_info.object = NULL; |
| plugin_config->log_info.all = false; |
| plugin_config->log_info.stale_if_error = false; |
| plugin_config->log_info.stale_while_revalidate = false; |
| plugin_config->log_info.filename = PLUGIN_NAME; |
| |
| if (argc > 1) |
| { |
| int c; |
| optind = 1; |
| static const struct option longopts[] = { |
| { "log-all", no_argument, NULL, 'a' }, |
| { "log-stale-while-revalidate", no_argument, NULL, 'r' }, |
| { "log-stale-if-error", no_argument, NULL, 'e' }, |
| { "log-filename", required_argument, NULL, 'f' }, |
| { "force-stale-if-error", required_argument, NULL, 'E' }, |
| { NULL, 0, NULL, 0 } |
| }; |
| |
| while ((c = getopt_long(argc, (char * const*) argv, "aref:E:", longopts, NULL)) != -1) |
| { |
| switch (c) |
| { |
| case 'a': |
| plugin_config->log_info.all = true; |
| break; |
| case 'r': |
| plugin_config->log_info.stale_while_revalidate = true; |
| break; |
| case 'e': |
| plugin_config->log_info.stale_if_error = true; |
| break; |
| case 'f': |
| plugin_config->log_info.filename = strdup(optarg); |
| break; |
| case 'E': |
| plugin_config->stale_if_error_override = atoi(optarg); |
| break; |
| default: |
| break; |
| } |
| } |
| |
| if (plugin_config->log_info.all || plugin_config->log_info.stale_while_revalidate || plugin_config->log_info.stale_if_error) |
| TSTextLogObjectCreate(plugin_config->log_info.filename, TS_LOG_MODE_ADD_TIMESTAMP, &(plugin_config->log_info.object)); |
| } |
| |
| // proxy.config.http.insert_age_in_response |
| TSHttpArgIndexReserve(PLUGIN_NAME, "txn state info", &(plugin_config->txn_slot)); |
| main_cont = TSContCreate(rfc5861_plugin, NULL); |
| TSContDataSet(main_cont, (void *) plugin_config); |
| TSHttpHookAdd(TS_HTTP_READ_REQUEST_HDR_HOOK, main_cont); |
| |
| TSDebug(PLUGIN_NAME, "Plugin Init Complete.\n"); |
| } |