You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@trafficserver.apache.org by zw...@apache.org on 2012/06/13 22:18:14 UTC
[5/11] Moved these plugins from the separate git repo
http://git-wip-us.apache.org/repos/asf/trafficserver/blob/bc13c5e0/plugins/experimental/esi/plugin.cc
----------------------------------------------------------------------
diff --git a/plugins/experimental/esi/plugin.cc b/plugins/experimental/esi/plugin.cc
new file mode 100644
index 0000000..e68c771
--- /dev/null
+++ b/plugins/experimental/esi/plugin.cc
@@ -0,0 +1,1289 @@
+/** @file
+
+ A brief file description
+
+ @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 <stdio.h>
+#include <stdlib.h>
+#include <limits.h>
+#include <string.h>
+#include <string>
+#include <list>
+#include <arpa/inet.h>
+#include <pthread.h>
+#include "ts/ts.h"
+
+#include "EsiProcessor.h"
+#include "HttpDataFetcher.h"
+#include "Utils.h"
+#include "HandlerManager.h"
+#include "serverIntercept.h"
+#include "Stats.h"
+#include "gzip.h"
+#include "HttpDataFetcherImpl.h"
+#include "FailureInfo.h"
+using std::string;
+using std::list;
+using namespace EsiLib;
+using namespace Stats;
+
+static HandlerManager *gHandlerManager;
+
+#define DEBUG_TAG "plugin_esi"
+#define PROCESSOR_DEBUG_TAG "plugin_esi_processor"
+#define PARSER_DEBUG_TAG "plugin_esi_parser"
+#define FETCHER_DEBUG_TAG "plugin_esi_fetcher"
+#define VARS_DEBUG_TAG "plugin_esi_vars"
+#define HANDLER_MGR_DEBUG_TAG "plugin_esi_handler_mgr"
+#define EXPR_DEBUG_TAG VARS_DEBUG_TAG
+
+#define MIME_FIELD_XESI "X-Esi"
+#define MIME_FIELD_XESI_LEN 5
+
+enum DataType { DATA_TYPE_RAW_ESI = 0, DATA_TYPE_GZIPPED_ESI = 1, DATA_TYPE_PACKED_ESI = 2 };
+static const char *DATA_TYPE_NAMES_[] = { "RAW_ESI",
+ "GZIPPED_ESI",
+ "PACKED_ESI" };
+
+static const char *HEADER_MASK_PREFIX = "Mask-";
+static const int HEADER_MASK_PREFIX_SIZE = 5;
+
+struct ContData
+{
+ enum STATE { READING_ESI_DOC, FETCHING_DATA, PROCESSING_COMPLETE };
+ STATE curr_state;
+ TSVIO input_vio;
+ TSIOBufferReader input_reader;
+ TSVIO output_vio;
+ TSIOBuffer output_buffer;
+ TSIOBufferReader output_reader;
+ Variables *esi_vars;
+ HttpDataFetcherImpl *data_fetcher;
+ EsiProcessor *esi_proc;
+ string debug_tag;
+ bool initialized;
+ bool xform_closed;
+ TSCont contp;
+ DataType input_type;
+ DocNodeList node_list;
+ string packed_node_list;
+ char *request_url;
+ bool os_response_cacheable;
+ list<string> post_headers;
+ TSHttpTxn txnp;
+ bool gzip_output;
+ string gzipped_data;
+ sockaddr const* client_addr;
+ bool got_server_state;
+
+ ContData(TSCont contptr, TSHttpTxn tx)
+ : curr_state(READING_ESI_DOC), input_vio(NULL), output_vio(NULL), output_buffer(NULL), output_reader(NULL),
+ esi_vars(NULL), data_fetcher(NULL), esi_proc(NULL), initialized(false),
+ xform_closed(false), contp(contptr), input_type(DATA_TYPE_RAW_ESI),
+ packed_node_list(""), request_url(NULL), os_response_cacheable(true), txnp(tx), gzip_output(false),
+ gzipped_data(""), got_server_state(false) {
+ client_addr = TSHttpTxnClientAddrGet(txnp);
+ }
+
+ void getClientState();
+
+ void getServerState();
+
+ void checkXformStatus();
+
+ bool init();
+
+ ~ContData();
+};
+
+class TSStatSystem : public StatSystem {
+public:
+ void create(int handle) {
+ g_stat_indices[handle]=TSStatCreate(Stats::STAT_NAMES[handle], TS_RECORDDATATYPE_INT, TS_STAT_PERSISTENT, TS_STAT_SYNC_COUNT);
+ }
+// void increment(int handle, TSMgmtInt step = 1) {
+ void increment(int handle, int step = 1) {
+ TSStatIntIncrement(g_stat_indices[handle], step);
+ }
+};
+
+
+static const char *
+createDebugTag(const char *prefix, TSCont contp, string &dest)
+{
+ char buf[1024];
+ snprintf(buf, 1024, "%s_%p", prefix, contp);
+ dest.assign(buf);
+ return dest.c_str();
+}
+
+static bool
+checkHeaderValue(TSMBuffer bufp, TSMLoc hdr_loc, const char *name, int name_len,
+ const char *exp_value = 0, int exp_value_len = 0, bool prefix = false); // forward decl
+
+static bool
+checkForCacheHeader(const char *name, int name_len, const char *value, int value_len, bool &cacheable);
+
+void
+ContData::checkXformStatus() {
+ if (!xform_closed) {
+ int retval = TSVConnClosedGet(contp);
+ if ((retval == TS_ERROR) || retval) {
+ if (retval == TS_ERROR) {
+ TSDebug(debug_tag.c_str(), "[%s] Error while getting close status of transformation at state %d",
+ __FUNCTION__, curr_state);
+ } else {
+ TSDebug(debug_tag.c_str(), "[%s] Vconn closed", __FUNCTION__);
+ }
+ xform_closed = true;
+ }
+ }
+}
+
+bool
+ContData::init()
+{
+ if (initialized) {
+ TSError("[%s] ContData already initialized!", __FUNCTION__);
+ return false;
+ }
+
+ createDebugTag(DEBUG_TAG, contp, debug_tag);
+ checkXformStatus();
+
+ bool retval = false;
+
+ if (!xform_closed) {
+ // Get upstream VIO
+ input_vio = TSVConnWriteVIOGet(contp);
+ if (!input_vio) {
+ TSError("[%s] Error while getting input vio", __FUNCTION__);
+ goto lReturn;
+ }
+ input_reader = TSVIOReaderGet(input_vio);
+
+ // Get downstream VIO
+ TSVConn output_conn;
+ output_conn = TSTransformOutputVConnGet(contp);
+ if (!output_conn) {
+ TSError("[%s] Error while getting transform VC", __FUNCTION__);
+ goto lReturn;
+ }
+ output_buffer = TSIOBufferCreate();
+ output_reader = TSIOBufferReaderAlloc(output_buffer);
+
+ // we don't know how much data we are going to write, so INT_MAX
+ output_vio = TSVConnWrite(output_conn, contp, output_reader, INT_MAX);
+
+ string fetcher_tag, vars_tag, expr_tag, parser_tag, proc_tag;
+ if (!data_fetcher) {
+ data_fetcher = new HttpDataFetcherImpl(contp, client_addr,
+ createDebugTag(FETCHER_DEBUG_TAG, contp, fetcher_tag));
+ }
+ if (!esi_vars) {
+ esi_vars = new Variables(createDebugTag(VARS_DEBUG_TAG, contp, vars_tag), &TSDebug, &TSError);
+ }
+ esi_proc = new EsiProcessor(createDebugTag(PROCESSOR_DEBUG_TAG, contp, proc_tag),
+ createDebugTag(PARSER_DEBUG_TAG, contp, fetcher_tag),
+ createDebugTag(EXPR_DEBUG_TAG, contp, expr_tag),
+ &TSDebug, &TSError, *data_fetcher, *esi_vars, *gHandlerManager);
+
+ if (!got_server_state) {
+ getServerState();
+ }
+ TSDebug(debug_tag.c_str(), "[%s] Set input data type to [%s]", __FUNCTION__,
+ DATA_TYPE_NAMES_[input_type]);
+
+ retval = true;
+ } else {
+ TSDebug(debug_tag.c_str(), "[%s] Transformation closed during initialization; Returning false",
+ __FUNCTION__);
+ }
+
+lReturn:
+ initialized = true;
+ return retval;
+}
+
+void
+ContData::getClientState() {
+ TSMBuffer req_bufp;
+ TSMLoc req_hdr_loc;
+ if (TSHttpTxnClientReqGet(txnp, &req_bufp, &req_hdr_loc) != TS_SUCCESS) {
+ TSError("[%s] Error while retrieving client request", __FUNCTION__);
+ return;
+ }
+
+ if (!esi_vars) {
+ string vars_tag;
+ esi_vars = new Variables(createDebugTag(VARS_DEBUG_TAG, contp, vars_tag), &TSDebug, &TSError);
+ }
+ if (!data_fetcher) {
+ string fetcher_tag;
+ data_fetcher = new HttpDataFetcherImpl(contp, client_addr,
+ createDebugTag(FETCHER_DEBUG_TAG, contp, fetcher_tag));
+ }
+ if (req_bufp && req_hdr_loc) {
+ TSMLoc url_loc;
+ if(TSHttpHdrUrlGet(req_bufp, req_hdr_loc, &url_loc) != TS_SUCCESS) {
+ TSError("[%s] Error while retrieving hdr url", __FUNCTION__);
+/*FIXME Does this leak?*/
+ return;
+ }
+ if (url_loc) {
+ if (request_url) {
+ TSfree(request_url);
+ }
+//FIXME TSUrlStringGet says it can accept a null length but it lies.
+ int length;
+ request_url = TSUrlStringGet(req_bufp, url_loc, &length);
+ TSDebug(DEBUG_TAG, "[%s] Got request URL [%s]", __FUNCTION__, request_url ? request_url : "(null)");
+ int query_len;
+ const char *query = TSUrlHttpQueryGet(req_bufp, url_loc, &query_len);
+ if (query) {
+ esi_vars->populate(query, query_len);
+ }
+ TSHandleMLocRelease(req_bufp, req_hdr_loc, url_loc);
+ }
+ TSMLoc field_loc = TSMimeHdrFieldGet(req_bufp, req_hdr_loc, 0);
+ while (field_loc) {
+ TSMLoc next_field_loc;
+ const char *name;
+ int name_len;
+
+ name = TSMimeHdrFieldNameGet(req_bufp, req_hdr_loc, field_loc, &name_len);
+ if (name) {
+ int n_values;
+ n_values = TSMimeHdrFieldValuesCount(req_bufp, req_hdr_loc, field_loc);
+ if (n_values && (n_values != TS_ERROR)) {
+ const char *value;
+ int value_len;
+ for (int i = 0; i < n_values; ++i) {
+ value = TSMimeHdrFieldValueStringGet(req_bufp, req_hdr_loc, field_loc, i, &value_len);
+ if ( NULL != value || value_len ) {
+ HttpHeader header(name, name_len, value, value_len);
+ esi_vars->populate(header);
+ data_fetcher->useHeader(header);
+ if (Utils::areEqual(name, name_len, TS_MIME_FIELD_ACCEPT_ENCODING,
+ TS_MIME_LEN_ACCEPT_ENCODING) &&
+ Utils::areEqual(value, value_len, TS_HTTP_VALUE_GZIP,
+ TS_HTTP_LEN_GZIP)) {
+ TSDebug(DEBUG_TAG, "[%s] Client accepts gzip encoding; will compress output", __FUNCTION__);
+ gzip_output = true;
+ }
+ }
+ }
+ }
+ }
+
+ next_field_loc = TSMimeHdrFieldNext(req_bufp, req_hdr_loc, field_loc);
+ TSHandleMLocRelease(req_bufp, req_hdr_loc, field_loc);
+ field_loc = next_field_loc;
+ }
+ }
+ TSHandleMLocRelease(req_bufp, TS_NULL_MLOC, req_hdr_loc);
+}
+
+void
+ContData::getServerState() {
+ got_server_state = true;
+ TSMBuffer bufp;
+ TSMLoc hdr_loc;
+ if (!TSHttpTxnServerRespGet(txnp, &bufp, &hdr_loc)) {
+ TSDebug(DEBUG_TAG, "[%s] Could not get server response; Assuming cache object", __FUNCTION__);
+//FIXME In theory it should be DATA_TYPE_PACKED_ESI but that doesn't work. Forcing to RAW_ESI for now.
+ input_type = DATA_TYPE_RAW_ESI;
+ return;
+ }
+ if (checkHeaderValue(bufp, hdr_loc, TS_MIME_FIELD_CONTENT_ENCODING,
+ TS_MIME_LEN_CONTENT_ENCODING, TS_HTTP_VALUE_GZIP, TS_HTTP_LEN_GZIP)) {
+ input_type = DATA_TYPE_GZIPPED_ESI;
+ } else {
+ input_type = DATA_TYPE_RAW_ESI;
+ }
+ int n_mime_headers = TSMimeHdrFieldsCount(bufp, hdr_loc);
+ TSMLoc field_loc;
+ const char *name, *act_name, *value;
+ int name_len, act_name_len, value_len;
+ string header;
+ for (int i = 0; i < n_mime_headers; ++i) {
+ field_loc = TSMimeHdrFieldGet(bufp, hdr_loc, i);
+ if (!field_loc) {
+ TSDebug(DEBUG_TAG, "[%s] Error while obtaining header field #%d", __FUNCTION__, i);
+ continue;
+ }
+ name = TSMimeHdrFieldNameGet(bufp, hdr_loc, field_loc, &name_len);
+ if (name) {
+ if (Utils::areEqual(name, name_len, TS_MIME_FIELD_TRANSFER_ENCODING, TS_MIME_LEN_TRANSFER_ENCODING)) {
+ TSDebug(DEBUG_TAG, "[%s] Not retaining transfer encoding header", __FUNCTION__);
+ } else if (Utils::areEqual(name, name_len, MIME_FIELD_XESI, MIME_FIELD_XESI_LEN)) {
+ TSDebug(DEBUG_TAG, "[%s] Not retaining 'X-Esi' header", __FUNCTION__);
+ } else if (Utils::areEqual(name, name_len, TS_MIME_FIELD_CONTENT_LENGTH, TS_MIME_LEN_CONTENT_LENGTH)) {
+ TSDebug(DEBUG_TAG, "[%s] Not retaining 'Content-length' header", __FUNCTION__);
+ } else {
+ if ((name_len > HEADER_MASK_PREFIX_SIZE) &&
+ (strncmp(name, HEADER_MASK_PREFIX, HEADER_MASK_PREFIX_SIZE) == 0)) {
+ act_name = name + HEADER_MASK_PREFIX_SIZE;
+ act_name_len = name_len - HEADER_MASK_PREFIX_SIZE;
+ } else {
+ act_name = name;
+ act_name_len = name_len;
+ }
+ header.assign(act_name, act_name_len);
+ header.append(": ");
+ int n_field_values = TSMimeHdrFieldValuesCount(bufp, hdr_loc, field_loc);
+ for (int j = 0; j < n_field_values; ++j) {
+ value = TSMimeHdrFieldValueStringGet(bufp, hdr_loc, field_loc, j, &value_len);
+ if ( NULL == value || !value_len ) {
+ TSDebug(DEBUG_TAG, "[%s] Error while getting value #%d of header [%.*s]",
+ __FUNCTION__, j, act_name_len, act_name);
+ } else {
+ if (Utils::areEqual(act_name, act_name_len, TS_MIME_FIELD_VARY, TS_MIME_LEN_VARY) &&
+ Utils::areEqual(value, value_len, TS_MIME_FIELD_ACCEPT_ENCODING,
+ TS_MIME_LEN_ACCEPT_ENCODING)) {
+ TSDebug(DEBUG_TAG, "[%s] Not retaining 'vary: accept-encoding' header", __FUNCTION__);
+ } else if (Utils::areEqual(act_name, act_name_len, TS_MIME_FIELD_CONTENT_ENCODING,
+ TS_MIME_LEN_CONTENT_ENCODING) &&
+ Utils::areEqual(value, value_len, TS_HTTP_VALUE_GZIP, TS_HTTP_LEN_GZIP)) {
+ TSDebug(DEBUG_TAG, "[%s] Not retaining 'content-encoding: gzip' header", __FUNCTION__);
+ } else {
+ if (header[header.size() - 2] != ':') {
+ header.append(", ");
+ }
+ header.append(value, value_len);
+ checkForCacheHeader(act_name, act_name_len, value, value_len,
+ os_response_cacheable);
+ if (!os_response_cacheable) {
+ TSDebug(DEBUG_TAG, "[%s] Header [%.*s] with value [%.*s] is a no-cache header",
+ __FUNCTION__, act_name_len, act_name, value_len, value);
+ break;
+ }
+ }
+ } // end if got value string
+ } // end value iteration
+ if (static_cast<int>(header.size()) > (act_name_len + 2 /* for ': ' */ )) {
+ header += "\r\n";
+ post_headers.push_back(header);
+ }
+ } // end if processable header
+ } // end if got header name
+ TSHandleMLocRelease(bufp, hdr_loc, field_loc);
+ if (!os_response_cacheable) {
+ post_headers.clear();
+ break;
+ }
+ } // end header iteration
+ TSHandleMLocRelease(bufp, TS_NULL_MLOC, hdr_loc);
+}
+
+ContData::~ContData()
+{
+ TSDebug(debug_tag.c_str(), "[%s] Destroying continuation data", __FUNCTION__);
+ if (output_reader) {
+ TSIOBufferReaderFree(output_reader);
+ }
+ if (output_buffer) {
+ TSIOBufferDestroy(output_buffer);
+ }
+ if (request_url) {
+ TSfree(request_url);
+ }
+ if (esi_vars) {
+ delete esi_vars;
+ }
+ if (data_fetcher) {
+ delete data_fetcher;
+ }
+ if (esi_proc) {
+ delete esi_proc;
+ }
+}
+
+static void
+cacheNodeList(ContData *cont_data) {
+ if (TSHttpTxnAborted(cont_data->txnp)) {
+ TSDebug(cont_data->debug_tag.c_str(), "[%s] Not caching node list as txn has been aborted", __FUNCTION__);
+ return;
+ }
+ string post_request("");
+ post_request.append(TS_HTTP_METHOD_POST);
+ post_request += ' ';
+ post_request.append(cont_data->request_url);
+ post_request.append(" HTTP/1.0\r\n");
+ post_request.append(SERVER_INTERCEPT_HEADER);
+ post_request.append(": cache=1\r\n");
+ for (list<string>::iterator list_iter = cont_data->post_headers.begin();
+ list_iter != cont_data->post_headers.end(); ++list_iter) {
+ post_request.append(ECHO_HEADER_PREFIX);
+ post_request.append(*list_iter);
+ }
+ post_request.append(TS_MIME_FIELD_ACCEPT_ENCODING, TS_MIME_LEN_ACCEPT_ENCODING);
+ post_request.append(": ");
+ post_request.append(TS_HTTP_VALUE_GZIP, TS_HTTP_LEN_GZIP);
+ post_request.append("\r\n");
+
+ string body;
+ cont_data->esi_proc->packNodeList(body, false);
+ char buf[64];
+ snprintf(buf, 64, "%s: %ld\r\n\r\n", TS_MIME_FIELD_CONTENT_LENGTH, body.size());
+
+ post_request.append(buf);
+ post_request.append(body);
+
+ TSFetchEvent event_ids;
+ TSFetchUrl(post_request.data(), post_request.size(), cont_data->client_addr,
+ cont_data->contp, NO_CALLBACK, event_ids);
+}
+
+static int
+transformData(TSCont contp)
+{
+ ContData *cont_data;
+ int64_t toread, consumed = 0, avail;
+ bool input_vio_buf_null = false;
+ bool process_input_complete = false;
+
+ // Get the output (downstream) vconnection where we'll write data to.
+ cont_data = static_cast<ContData *>(TSContDataGet(contp));
+
+ // If the input VIO's buffer is NULL, we need to terminate the transformation
+ if (!TSVIOBufferGet(cont_data->input_vio)) {
+ input_vio_buf_null = true;
+ if (cont_data->curr_state == ContData::PROCESSING_COMPLETE) {
+ TSDebug((cont_data->debug_tag).c_str(), "[%s] input_vio NULL, marking transformation to be terminated",
+ __FUNCTION__);
+ return 1;
+ } else if (cont_data->curr_state == ContData::READING_ESI_DOC) {
+ TSDebug((cont_data->debug_tag).c_str(), "[%s] input_vio NULL while in read state. Assuming end of input",
+ __FUNCTION__);
+ process_input_complete = true;
+ } else {
+ if (!cont_data->data_fetcher->isFetchComplete()) {
+ TSDebug((cont_data->debug_tag).c_str(),
+ "[%s] input_vio NULL, but data needs to be fetched. Returning control", __FUNCTION__);
+ return 1;
+ } else {
+ TSDebug((cont_data->debug_tag).c_str(),
+ "[%s] input_vio NULL, but processing needs to (and can) be completed", __FUNCTION__);
+ }
+ }
+ }
+
+ if (!process_input_complete && (cont_data->curr_state == ContData::READING_ESI_DOC)) {
+ // Determine how much data we have left to read.
+ toread = TSVIONTodoGet(cont_data->input_vio);
+ TSDebug((cont_data->debug_tag).c_str(), "[%s] upstream VC has %ld bytes available to read",
+ __FUNCTION__, toread);
+
+ if (toread > 0) {
+ avail = TSIOBufferReaderAvail(cont_data->input_reader);
+ if (avail == TS_ERROR) {
+ TSError("[%s] Error while getting number of bytes available", __FUNCTION__);
+ return 0;
+ }
+
+ // There are some data available for reading. Let's parse it
+ if (avail > 0) {
+ int64_t data_len;
+ const char *data;
+ TSIOBufferBlock block = TSIOBufferReaderStart(cont_data->input_reader);
+ // Now start extraction
+ while (block != NULL) {
+ data = TSIOBufferBlockReadStart(block, cont_data->input_reader, &data_len);
+ if (cont_data->input_type == DATA_TYPE_RAW_ESI) {
+ cont_data->esi_proc->addParseData(data, data_len);
+ } else if (cont_data->input_type == DATA_TYPE_GZIPPED_ESI) {
+ cont_data->gzipped_data.append(data, data_len);
+ } else {
+ cont_data->packed_node_list.append(data, data_len);
+ }
+ TSDebug((cont_data->debug_tag).c_str(),
+ "[%s] Added chunk of %lu bytes starting with [%.10s] to parse list",
+ __FUNCTION__, data_len, (data_len ? data : "(null)"));
+ consumed += data_len;
+
+ block = TSIOBufferBlockNext(block);
+/*FIXME this chunk of code looks to be in error. Commenting out.
+ if (!block) {
+ TSError("[%s] Error while getting block from ioreader", __FUNCTION__);
+ return 0;
+ }
+*/
+ }
+ }
+ TSDebug((cont_data->debug_tag).c_str(), "[%s] Consumed %ld bytes from upstream VC",
+ __FUNCTION__, consumed);
+
+ TSIOBufferReaderConsume(cont_data->input_reader, consumed);
+
+ // Modify the input VIO to reflect how much data we've completed.
+ TSVIONDoneSet(cont_data->input_vio, TSVIONDoneGet(cont_data->input_vio) + consumed);
+
+ toread = TSVIONTodoGet(cont_data->input_vio); // set this for the test after this if block
+ }
+
+ if (toread > 0) { // testing this again because it might have changed in previous if block
+ // let upstream know we are ready to read new data
+ TSContCall(TSVIOContGet(cont_data->input_vio), TS_EVENT_VCONN_WRITE_READY, cont_data->input_vio);
+ } else {
+ // we have consumed everything that there was to read
+ process_input_complete = true;
+ }
+ }
+ if (process_input_complete) {
+ TSDebug((cont_data->debug_tag).c_str(), "[%s] Completed reading input...", __FUNCTION__);
+ if (cont_data->input_type == DATA_TYPE_PACKED_ESI) {
+ TSDebug(DEBUG_TAG, "[%s] Going to use packed node list of size %d",
+ __FUNCTION__, (int) cont_data->packed_node_list.size());
+ cont_data->esi_proc->usePackedNodeList(cont_data->packed_node_list);
+ } else {
+ if (cont_data->input_type == DATA_TYPE_GZIPPED_ESI) {
+ BufferList buf_list;
+ if (gunzip(cont_data->gzipped_data.data(), cont_data->gzipped_data.size(), buf_list)) {
+ for (BufferList::iterator iter = buf_list.begin(); iter != buf_list.end(); ++iter) {
+ cont_data->esi_proc->addParseData(iter->data(), iter->size());
+ }
+ } else {
+ TSError("[%s] Error while gunzipping data", __FUNCTION__);
+ }
+ }
+ if (cont_data->esi_proc->completeParse()) {
+ if (cont_data->os_response_cacheable) {
+ cacheNodeList(cont_data);
+ }
+ }
+ }
+ cont_data->curr_state = ContData::FETCHING_DATA;
+ if (!input_vio_buf_null) {
+ TSContCall(TSVIOContGet(cont_data->input_vio), TS_EVENT_VCONN_WRITE_COMPLETE,
+ cont_data->input_vio);
+ }
+ }
+
+ if (cont_data->curr_state == ContData::FETCHING_DATA) { // retest as state may have changed in previous block
+ if (cont_data->data_fetcher->isFetchComplete()) {
+ TSDebug((cont_data->debug_tag).c_str(), "[%s] data ready; going to process doc", __FUNCTION__);
+ const char *out_data;
+ int out_data_len;
+ EsiProcessor::ReturnCode retval = cont_data->esi_proc->process(out_data, out_data_len);
+ if (retval == EsiProcessor::NEED_MORE_DATA) {
+ TSDebug((cont_data->debug_tag).c_str(), "[%s] ESI processor needs more data; "
+ "will wait for all data to be fetched", __FUNCTION__);
+ return 1;
+ }
+ cont_data->curr_state = ContData::PROCESSING_COMPLETE;
+ if (retval == EsiProcessor::SUCCESS) {
+ TSDebug((cont_data->debug_tag).c_str(),
+ "[%s] ESI processor output document of size %d starting with [%.10s]",
+ __FUNCTION__, out_data_len, (out_data_len ? out_data : "(null)"));
+ } else {
+ TSError("[%s] ESI processor failed to process document; will return empty document", __FUNCTION__);
+ out_data = "";
+ out_data_len = 0;
+ }
+
+ // make sure transformation has not been prematurely terminated
+ if (!cont_data->xform_closed) {
+ string cdata;
+ if (cont_data->gzip_output) {
+ if (!gzip(out_data, out_data_len, cdata)) {
+ TSError("[%s] Error while gzipping content", __FUNCTION__);
+ out_data_len = 0;
+ out_data = "";
+ } else {
+ TSDebug((cont_data->debug_tag).c_str(), "[%s] Compressed document from size %d to %d bytes",
+ __FUNCTION__, out_data_len, (int) cdata.size());
+ out_data_len = cdata.size();
+ out_data = cdata.data();
+ }
+ }
+
+ if (TSIOBufferWrite(TSVIOBufferGet(cont_data->output_vio), out_data, out_data_len) == TS_ERROR) {
+ TSError("[%s] Error while writing bytes to downstream VC", __FUNCTION__);
+ return 0;
+ }
+
+ TSVIONBytesSet(cont_data->output_vio, out_data_len);
+
+ // Reenable the output connection so it can read the data we've produced.
+ TSVIOReenable(cont_data->output_vio);
+ }
+ } else {
+ TSDebug((cont_data->debug_tag).c_str(), "[%s] Data not available yet; cannot process document",
+ __FUNCTION__);
+ }
+ }
+
+ return 1;
+}
+
+static int
+transformHandler(TSCont contp, TSEvent event, void *edata)
+{
+ TSVIO input_vio;
+ ContData *cont_data;
+
+ cont_data = static_cast<ContData *>(TSContDataGet(contp));
+
+ // we need these later, but declaring now avoid compiler warning w.r.t. goto
+ bool process_event = true;
+ const char *cont_debug_tag;
+ bool shutdown, is_fetch_event;
+
+ if (!cont_data->initialized) {
+ if (!cont_data->init()) {
+ TSError("[%s] Could not initialize continuation data; shutting down transformation", __FUNCTION__);
+ goto lShutdown;
+ }
+ TSDebug((cont_data->debug_tag).c_str(), "[%s] initialized continuation data", __FUNCTION__);
+ }
+
+ cont_debug_tag = (cont_data->debug_tag).c_str(); // just a handy reference
+
+ cont_data->checkXformStatus();
+
+ is_fetch_event = cont_data->data_fetcher->isFetchEvent(event);
+
+ if (cont_data->xform_closed) {
+ TSDebug(cont_debug_tag, "[%s] Transformation closed. Post-processing...", __FUNCTION__);
+ if (cont_data->curr_state == ContData::PROCESSING_COMPLETE) {
+ TSDebug(cont_debug_tag, "[%s] Processing is complete, not processing current event %d",
+ __FUNCTION__, event);
+ process_event = false;
+ } else if (cont_data->curr_state == ContData::READING_ESI_DOC) {
+ TSDebug(cont_debug_tag, "[%s] Parsing is incomplete, will force end of input",
+ __FUNCTION__);
+ cont_data->curr_state = ContData::FETCHING_DATA;
+ }
+ if (cont_data->curr_state == ContData::FETCHING_DATA) { // retest as it may be modified in prev. if block
+ if (cont_data->data_fetcher->isFetchComplete()) {
+ TSDebug(cont_debug_tag,
+ "[%s] Requested data has been fetched; will skip event and marking processing as complete ",
+ __FUNCTION__);
+ cont_data->curr_state = ContData::PROCESSING_COMPLETE;
+ process_event = false;
+ } else {
+ if (is_fetch_event) {
+ TSDebug(cont_debug_tag, "[%s] Going to process received data",
+ __FUNCTION__);
+ } else {
+ TSDebug(cont_debug_tag, "[%s] Ignoring event %d; Will wait for pending data",
+ __FUNCTION__, event);
+ // transformation is over, but data hasn't been fetched;
+ // let's wait for data to be fetched - we will be called
+ // by Fetch API and go through this loop again
+ process_event = false;
+ }
+ }
+ }
+ }
+
+ if (process_event) {
+ switch (event) {
+ case TS_EVENT_ERROR:
+ // doubt: what is this code doing?
+ input_vio = TSVConnWriteVIOGet(contp);
+ if (!input_vio) {
+ TSError("[%s] Error while getting upstream vio", __FUNCTION__);
+ } else {
+ TSContCall(TSVIOContGet(input_vio), TS_EVENT_ERROR, input_vio);
+ }
+ // FetchSM also might send this; let's just output whatever we have
+ cont_data->curr_state = ContData::FETCHING_DATA;
+ transformData(contp);
+ break;
+
+ case TS_EVENT_VCONN_WRITE_COMPLETE:
+ case TS_EVENT_VCONN_WRITE_READY: // we write only once to downstream VC
+ TSDebug(cont_debug_tag, "[%s] shutting down transformation", __FUNCTION__);
+ TSVConnShutdown(TSTransformOutputVConnGet(contp), 0, 1);
+ break;
+
+ case TS_EVENT_IMMEDIATE:
+ TSDebug(cont_debug_tag, "[%s] handling TS_EVENT_IMMEDIATE...", __FUNCTION__);
+ transformData(contp);
+ break;
+
+ default:
+ if (is_fetch_event) {
+ TSDebug(cont_debug_tag, "[%s] Handling fetch event %d...", __FUNCTION__, event);
+ if (cont_data->data_fetcher->handleFetchEvent(event, edata)) {
+ if ((cont_data->curr_state == ContData::FETCHING_DATA) &&
+ cont_data->data_fetcher->isFetchComplete()) {
+ // there's a small chance that fetcher is ready even before
+ // parsing is complete; hence we need to check the state too
+ TSDebug(cont_debug_tag, "[%s] fetcher is ready with data, going into process stage",
+ __FUNCTION__);
+ transformData(contp);
+ }
+ } else {
+ TSError("[%s] Could not handle fetch event!", __FUNCTION__);
+ }
+ } else {
+ TSAssert(!"Unexpected event");
+ }
+ break;
+ }
+ }
+
+ shutdown = (cont_data->xform_closed && (cont_data->curr_state == ContData::PROCESSING_COMPLETE));
+
+ if (shutdown) {
+ if (process_event && is_fetch_event) {
+ // we need to return control to the fetch API to give up it's
+ // lock on our continuation which will fail if we destroy
+ // ourselves right now
+ TSDebug(cont_debug_tag, "[%s] Deferring shutdown as data event was just processed", __FUNCTION__);
+ TSContSchedule(contp, 10, TS_THREAD_POOL_TASK);
+ } else {
+ goto lShutdown;
+ }
+ }
+
+ return 1;
+
+lShutdown:
+ TSDebug((cont_data->debug_tag).c_str(), "[%s] transformation closed; cleaning up data...", __FUNCTION__);
+ delete cont_data;
+ TSContDestroy(contp);
+ return 1;
+
+}
+
+struct RespHdrModData {
+ bool cache_txn;
+ bool gzip_encoding;
+};
+
+static void
+addMimeHeaderField(TSMBuffer bufp, TSMLoc hdr_loc, const char *name, int name_len,
+ const char *value, int value_len) {
+ TSMLoc field_loc = (TSMLoc)NULL;
+ TSMimeHdrFieldCreate(bufp, hdr_loc, &field_loc);
+ if (!field_loc) {
+ TSError("[%s] Error while creating mime field", __FUNCTION__);
+ } else {
+ if (TSMimeHdrFieldNameSet(bufp, hdr_loc, field_loc, name, name_len) != TS_SUCCESS) {
+ TSError("[%s] Error while setting name [%.*s] for MIME header field", __FUNCTION__, name_len, name);
+ } else {
+ if (TSMimeHdrFieldValueStringInsert(bufp, hdr_loc, field_loc, 0, value, value_len) != TS_SUCCESS) {
+ TSError("[%s] Error while inserting value [%.*s] string to MIME field [%.*s]", __FUNCTION__,
+ value_len, value, name_len, name);
+ } else {
+ if (TSMimeHdrFieldAppend(bufp, hdr_loc, field_loc) != TS_SUCCESS) {
+ TSError("[%s] Error while appending MIME field with name [%.*s] and value [%.*s]", __FUNCTION__,
+ name_len, name, value_len, value);
+ }
+ }
+ }
+ TSHandleMLocRelease(bufp, hdr_loc, field_loc);
+ }
+}
+
+static int
+modifyResponseHeader(TSCont contp, TSEvent event, void *edata) {
+ int retval = 0;
+ RespHdrModData *mod_data = static_cast<RespHdrModData *>(TSContDataGet(contp));
+ TSHttpTxn txnp = static_cast<TSHttpTxn>(edata);
+ if (event != TS_EVENT_HTTP_SEND_RESPONSE_HDR) {
+ TSError("[%s] Unexpected event (%d)", __FUNCTION__, event);
+ goto lReturn;
+ }
+ TSMBuffer bufp;
+ TSMLoc hdr_loc;
+ if (TSHttpTxnClientRespGet(txnp, &bufp, &hdr_loc) == TS_SUCCESS) {
+ int n_mime_headers = TSMimeHdrFieldsCount(bufp, hdr_loc);
+ TSMLoc field_loc;
+ const char *name, *value;
+ int name_len, value_len;
+ for (int i = 0; i < n_mime_headers; ++i) {
+ field_loc = TSMimeHdrFieldGet(bufp, hdr_loc, i);
+ if (!field_loc) {
+ TSDebug(DEBUG_TAG, "[%s] Error while obtaining header field #%d", __FUNCTION__, i);
+ continue;
+ }
+ name = TSMimeHdrFieldNameGet(bufp, hdr_loc, field_loc, &name_len);
+ if (name) {
+ bool destroy_header = false;
+ if (Utils::areEqual(name, name_len, SERVER_INTERCEPT_HEADER, SERVER_INTERCEPT_HEADER_LEN)) {
+ destroy_header = true;
+ } else if (Utils::areEqual(name, name_len, TS_MIME_FIELD_AGE, TS_MIME_LEN_AGE)) {
+ destroy_header = true;
+ } else if (!mod_data->cache_txn &&
+ Utils::areEqual(name, name_len, MIME_FIELD_XESI, MIME_FIELD_XESI_LEN)) {
+ destroy_header = true;
+ } else if ((name_len > HEADER_MASK_PREFIX_SIZE) &&
+ (strncmp(name, HEADER_MASK_PREFIX, HEADER_MASK_PREFIX_SIZE) == 0)) {
+ destroy_header = true;
+ } else {
+ int n_field_values = TSMimeHdrFieldValuesCount(bufp, hdr_loc, field_loc);
+ for (int j = 0; j < n_field_values; ++j) {
+ value = TSMimeHdrFieldValueStringGet(bufp, hdr_loc, field_loc, j, &value_len);
+ if ( NULL == value || !value_len ) {
+ TSDebug(DEBUG_TAG, "[%s] Error while getting value #%d of header [%.*s]",
+ __FUNCTION__, j, name_len, name);
+ } else {
+ if (mod_data->cache_txn) {
+ bool response_cacheable, is_cache_header;
+ is_cache_header = checkForCacheHeader(name, name_len, value, value_len, response_cacheable);
+ if (is_cache_header && response_cacheable) {
+ destroy_header = true;
+ }
+ }
+ } // if got valid value for header
+ } // end for
+ }
+ if (destroy_header) {
+ TSDebug(DEBUG_TAG, "[%s] Removing header with name [%.*s]", __FUNCTION__, name_len, name);
+ TSMimeHdrFieldDestroy(bufp, hdr_loc, field_loc);
+ --n_mime_headers;
+ --i;
+ }
+ }
+ TSHandleMLocRelease(bufp, hdr_loc, field_loc);
+ }
+ if (mod_data->gzip_encoding &&
+ !checkHeaderValue(bufp, hdr_loc, TS_MIME_FIELD_CONTENT_ENCODING, TS_MIME_LEN_CONTENT_ENCODING,
+ TS_HTTP_VALUE_GZIP, TS_HTTP_LEN_GZIP)) {
+ addMimeHeaderField(bufp, hdr_loc, TS_MIME_FIELD_CONTENT_ENCODING, TS_MIME_LEN_CONTENT_ENCODING,
+ TS_HTTP_VALUE_GZIP, TS_HTTP_LEN_GZIP);
+ }
+ if (mod_data->cache_txn) {
+ addMimeHeaderField(bufp, hdr_loc, TS_MIME_FIELD_VARY, TS_MIME_LEN_VARY, TS_MIME_FIELD_ACCEPT_ENCODING,
+ TS_MIME_LEN_ACCEPT_ENCODING);
+ }
+ TSHandleMLocRelease(bufp, TS_NULL_MLOC, hdr_loc);
+ TSDebug(DEBUG_TAG, "[%s] Inspected client-bound headers", __FUNCTION__);
+ retval = 1;
+ } else {
+ TSError("[%s] Error while getting response from txn", __FUNCTION__);
+ }
+
+lReturn:
+ delete mod_data;
+ TSContDestroy(contp);
+ TSHttpTxnReenable(txnp, TS_EVENT_HTTP_CONTINUE);
+ return retval;
+}
+
+static bool
+checkHeaderValue(TSMBuffer bufp, TSMLoc hdr_loc, const char *name, int name_len,
+ const char *exp_value, int exp_value_len, bool prefix) {
+ TSMLoc field_loc = TSMimeHdrFieldFind(bufp, hdr_loc, name, name_len);
+ if (!field_loc) {
+ return false;
+ }
+ bool retval = false;
+ if (exp_value && exp_value_len) {
+ const char *value;
+ int value_len;
+ int n_values = TSMimeHdrFieldValuesCount(bufp, hdr_loc, field_loc);
+ for (int i = 0; i < n_values; ++i) {
+ value = TSMimeHdrFieldValueStringGet(bufp, hdr_loc, field_loc, i, &value_len);
+ if ( NULL != value || value_len ) {
+ if (prefix) {
+ if ((value_len >= exp_value_len) &&
+ (strncasecmp(value, exp_value, exp_value_len) == 0)) {
+ retval = true;
+ }
+ } else if (Utils::areEqual(value, value_len, exp_value, exp_value_len)) {
+ retval = true;
+ }
+ } else {
+ TSDebug(DEBUG_TAG, "[%s] Error while getting value # %d of header [%.*s]", __FUNCTION__,
+ i, name_len, name);
+ }
+ if (retval) {
+ break;
+ }
+ }
+ } else { // only presence required
+ retval = true;
+ }
+ TSHandleMLocRelease(bufp, hdr_loc, field_loc);
+ return retval;
+}
+
+static void
+maskOsCacheHeaders(TSHttpTxn txnp) {
+ TSMBuffer bufp;
+ TSMLoc hdr_loc;
+ if (TSHttpTxnServerRespGet(txnp, &bufp, &hdr_loc) != TS_SUCCESS) {
+ TSError("[%s] Couldn't get server response from txn", __FUNCTION__);
+ return;
+ }
+ int n_mime_headers = TSMimeHdrFieldsCount(bufp, hdr_loc);
+ TSMLoc field_loc;
+ const char *name, *value;
+ int name_len, value_len, n_field_values;
+ bool os_response_cacheable, is_cache_header, mask_header;
+ string masked_name;
+ for (int i = 0; i < n_mime_headers; ++i) {
+ os_response_cacheable = true;
+ field_loc = TSMimeHdrFieldGet(bufp, hdr_loc, i);
+ if (!field_loc) {
+ TSDebug(DEBUG_TAG, "[%s] Error while obtaining header field #%d", __FUNCTION__, i);
+ continue;
+ }
+ name = TSMimeHdrFieldNameGet(bufp, hdr_loc, field_loc, &name_len);
+ if (name) {
+ mask_header = is_cache_header = false;
+ n_field_values = TSMimeHdrFieldValuesCount(bufp, hdr_loc, field_loc);
+ for (int j = 0; j < n_field_values; ++j) {
+ value = TSMimeHdrFieldValueStringGet(bufp, hdr_loc, field_loc, j, &value_len);
+ if ( NULL == value || !value_len ) {
+ TSDebug(DEBUG_TAG, "[%s] Error while getting value #%d of header [%.*s]",
+ __FUNCTION__, j, name_len, name);
+ } else {
+ is_cache_header = checkForCacheHeader(name, name_len, value, value_len, os_response_cacheable);
+ if (!os_response_cacheable) {
+ break;
+ }
+ if (is_cache_header) {
+ TSDebug(DEBUG_TAG, "[%s] Masking OS cache header [%.*s] with value [%.*s]. ",
+ __FUNCTION__, name_len, name, value_len, value);
+ mask_header = true;
+ }
+ } // end if got value string
+ } // end value iteration
+ if (mask_header) {
+ masked_name.assign(HEADER_MASK_PREFIX);
+ masked_name.append(name, name_len);
+ if (TSMimeHdrFieldNameSet(bufp, hdr_loc, field_loc, masked_name.data(),
+ masked_name.size()) != TS_SUCCESS) {
+ TSError("[%s] Couldn't rename header [%.*s]", __FUNCTION__, name_len, name);
+ }
+ }
+ } // end if got header name
+ TSHandleMLocRelease(bufp, hdr_loc, field_loc);
+ if (!os_response_cacheable) {
+ break;
+ }
+ } // end header iteration
+ TSHandleMLocRelease(bufp, TS_NULL_MLOC, hdr_loc);
+}
+
+static bool
+isTxnTransformable(TSHttpTxn txnp, bool is_cache_txn) {
+ // We are only interested in transforming "200 OK" responses with a
+ // Content-Type: text/ header and with X-Esi header
+
+ TSMBuffer bufp;
+ TSMLoc hdr_loc;
+ TSHttpStatus resp_status;
+ bool header_obtained = false, intercept_header;
+ bool retval = false;
+
+ header_obtained = is_cache_txn ? TSHttpTxnCachedRespGet(txnp, &bufp, &hdr_loc) :
+ TSHttpTxnServerRespGet(txnp, &bufp, &hdr_loc);
+ if (header_obtained != TS_SUCCESS) {
+ TSError("[%s] Couldn't get txn header", __FUNCTION__);
+ goto lReturn;
+ }
+
+ intercept_header = checkHeaderValue(bufp, hdr_loc, SERVER_INTERCEPT_HEADER, SERVER_INTERCEPT_HEADER_LEN);
+ if (intercept_header) {
+ if (is_cache_txn) {
+ TSDebug(DEBUG_TAG, "[%s] Packed ESI document found in cache; will process", __FUNCTION__);
+ retval = true;
+ } else {
+ TSDebug(DEBUG_TAG, "[%s] Found Intercept header in server response; document not processable",
+ __FUNCTION__);
+ }
+ goto lReturn; // found internal header; no other detection required
+ }
+
+ resp_status = TSHttpHdrStatusGet(bufp, hdr_loc);
+ if (static_cast<int>(resp_status) == static_cast<int>(TS_ERROR)) {
+ TSError("[%s] Error while getting http status", __FUNCTION__);
+ goto lReturn;
+ }
+ if (resp_status != TS_HTTP_STATUS_OK) {
+ TSDebug(DEBUG_TAG, "[%s] Not handling non-OK response status %d", __FUNCTION__, resp_status);
+ goto lReturn;
+ }
+
+ if (!checkHeaderValue(bufp, hdr_loc, TS_MIME_FIELD_CONTENT_TYPE, TS_MIME_LEN_CONTENT_TYPE,
+ "text/", 5, true)) {
+ TSDebug(DEBUG_TAG, "[%s] Not text content", __FUNCTION__);
+ goto lReturn;
+ }
+ if (!checkHeaderValue(bufp, hdr_loc, MIME_FIELD_XESI, MIME_FIELD_XESI_LEN)) {
+ TSDebug(DEBUG_TAG, "[%s] ESI header [%s] not found", __FUNCTION__, MIME_FIELD_XESI);
+ goto lReturn;
+ }
+
+ retval = true;
+
+lReturn:
+ if (header_obtained) {
+ TSHandleMLocRelease(bufp, TS_NULL_MLOC, hdr_loc);
+ }
+ return retval;
+}
+
+static bool
+isCacheObjTransformable(TSHttpTxn txnp) {
+ int obj_status;
+ if (TSHttpTxnCacheLookupStatusGet(txnp, &obj_status) == TS_ERROR) {
+ TSError("[%s] Couldn't get cache status of object", __FUNCTION__);
+ return false;
+ }
+ if ((obj_status == TS_CACHE_LOOKUP_HIT_FRESH) || (obj_status == TS_CACHE_LOOKUP_HIT_STALE)) {
+ TSDebug(DEBUG_TAG, "[%s] doc found in cache, will add transformation", __FUNCTION__);
+ return isTxnTransformable(txnp, true);
+ }
+ TSDebug(DEBUG_TAG, "[%s] cache object's status is %d; not transformable",
+ __FUNCTION__, obj_status);
+ return false;
+}
+
+static bool
+isInterceptRequest(TSHttpTxn txnp) {
+ if (!TSHttpIsInternalRequest(txnp)) {
+ TSDebug(DEBUG_TAG, "[%s] Skipping external request", __FUNCTION__);
+ return false;
+ }
+
+ TSMBuffer bufp;
+ TSMLoc hdr_loc;
+ if (TSHttpTxnClientReqGet(txnp, &bufp, &hdr_loc) != TS_SUCCESS) {
+ TSError("[%s] Could not get client request", __FUNCTION__);
+ return false;
+ }
+
+ bool valid_request = false;
+ bool retval = false;
+ int method_len;
+ const char *method = TSHttpHdrMethodGet(bufp, hdr_loc, &method_len);
+ if (!method) {
+ TSError("[%s] Could not obtain method!", __FUNCTION__);
+ } else {
+ if ((method_len != TS_HTTP_LEN_POST) ||
+ (strncasecmp(method, TS_HTTP_METHOD_POST, TS_HTTP_LEN_POST))) {
+ TSDebug(DEBUG_TAG, "[%s] Method [%.*s] invalid, [%s] expected", __FUNCTION__, method_len, method,
+ TS_HTTP_METHOD_POST);
+ } else {
+ TSDebug(DEBUG_TAG, "[%s] Valid server intercept method found", __FUNCTION__);
+ valid_request = true;
+ }
+ }
+
+ if (valid_request) {
+ retval = checkHeaderValue(bufp, hdr_loc, SERVER_INTERCEPT_HEADER, SERVER_INTERCEPT_HEADER_LEN);
+ }
+ TSHandleMLocRelease(bufp, TS_NULL_MLOC, hdr_loc);
+ return retval;
+}
+
+static bool
+checkForCacheHeader(const char *name, int name_len, const char *value, int value_len, bool &cacheable) {
+ cacheable = true;
+ if (Utils::areEqual(name, name_len, TS_MIME_FIELD_EXPIRES, TS_MIME_LEN_EXPIRES)) {
+ if ((value_len == 1) && (*value == '0')) {
+ cacheable = false;
+ }
+ return true;
+ }
+ if (Utils::areEqual(name, name_len, TS_MIME_FIELD_CACHE_CONTROL, TS_MIME_LEN_CACHE_CONTROL)) {
+ if (Utils::areEqual(value, value_len, TS_HTTP_VALUE_PRIVATE, TS_HTTP_LEN_PRIVATE)) {
+ cacheable = false;
+ }
+ return true;
+ }
+ return false;
+}
+
+static bool
+addSendResponseHeaderHook(TSHttpTxn txnp, bool cache_txn, bool gzip_encoding) {
+ TSCont contp = TSContCreate(modifyResponseHeader, NULL);
+ if (!contp) {
+ TSError("[%s] Could not create continuation", __FUNCTION__);
+ return false;
+ }
+ TSHttpTxnHookAdd(txnp, TS_HTTP_SEND_RESPONSE_HDR_HOOK, contp);
+ RespHdrModData *cont_data = new RespHdrModData();
+ cont_data->cache_txn = cache_txn;
+ cont_data->gzip_encoding = gzip_encoding;
+ TSContDataSet(contp, cont_data);
+ return true;
+}
+
+static bool
+addTransform(TSHttpTxn txnp, bool processing_os_response) {
+ TSCont contp = 0;
+ ContData *cont_data = 0;
+
+ contp = TSTransformCreate(transformHandler, txnp);
+ if (!contp) {
+ TSError("[%s] Error while creating a new transformation", __FUNCTION__);
+ goto lFail;
+ }
+
+ cont_data = new ContData(contp, txnp);
+ TSContDataSet(contp, cont_data);
+
+ cont_data->getClientState();
+ if (processing_os_response) {
+ cont_data->getServerState();
+ }
+
+ TSHttpTxnHookAdd(txnp, TS_HTTP_RESPONSE_TRANSFORM_HOOK, contp);
+
+ if (!addSendResponseHeaderHook(txnp, !processing_os_response, cont_data->gzip_output)) {
+ TSError("[%s] Couldn't add send response header hook", __FUNCTION__);
+ goto lFail;
+ }
+
+ TSHttpTxnTransformedRespCache(txnp, 0);
+ TSHttpTxnUntransformedRespCache(txnp, 0);
+
+ TSDebug(DEBUG_TAG, "[%s] Added transformation (0x%p)", __FUNCTION__, contp);
+ return true;
+
+lFail:
+ if (contp) {
+ TSContDestroy(contp);
+ }
+ if (cont_data) {
+ delete cont_data;
+ }
+ return false;
+}
+
+pthread_key_t threadKey;
+static int
+globalHookHandler(TSCont contp, TSEvent event, void *edata) {
+ TSHttpTxn txnp = (TSHttpTxn) edata;
+ bool intercept_req = isInterceptRequest(txnp);
+
+
+
+
+ switch (event) {
+ case TS_EVENT_HTTP_READ_REQUEST_HDR:
+ TSDebug(DEBUG_TAG, "[%s] handling read request header event...", __FUNCTION__);
+ if (intercept_req) {
+ if (!setupServerIntercept(txnp)) {
+ TSError("[%s] Could not setup server intercept", __FUNCTION__);
+ } else {
+ TSDebug(DEBUG_TAG, "[%s] Setup server intercept", __FUNCTION__);
+ }
+ } else {
+ TSDebug(DEBUG_TAG, "[%s] Not setting up intercept", __FUNCTION__);
+ }
+ break;
+
+ case TS_EVENT_HTTP_READ_RESPONSE_HDR:
+ case TS_EVENT_HTTP_CACHE_LOOKUP_COMPLETE:
+ if (!intercept_req) {
+ if (event == TS_EVENT_HTTP_READ_RESPONSE_HDR) {
+ bool mask_cache_headers = false;
+ TSDebug(DEBUG_TAG, "[%s] handling read response header event...", __FUNCTION__);
+ if (isCacheObjTransformable(txnp)) {
+ // transformable cache object will definitely have a
+ // transformation already as cache_lookup_complete would
+ // have been processed before this
+ TSDebug(DEBUG_TAG, "[%s] xform should already have been added on cache lookup. Not adding now",
+ __FUNCTION__);
+ mask_cache_headers = true;
+ } else if (isTxnTransformable(txnp, false)) {
+ addTransform(txnp, true);
+ Stats::increment(Stats::N_OS_DOCS);
+ mask_cache_headers = true;
+ }
+ if (mask_cache_headers) {
+ // we'll 'mask' OS cache headers so that traffic server will
+ // not try to cache this. We cannot outright delete them
+ // because we need them in our POST request; hence the 'masking'
+ maskOsCacheHeaders(txnp);
+ }
+ } else {
+ TSDebug(DEBUG_TAG, "[%s] handling cache lookup complete event...", __FUNCTION__);
+ if (isCacheObjTransformable(txnp)) {
+ // we make the assumption above that a transformable cache
+ // object would already have a tranformation. We should revisit
+ // that assumption in case we change the statement below
+ addTransform(txnp, false);
+ Stats::increment(Stats::N_CACHE_DOCS);
+ }
+ }
+ }
+ break;
+
+ default:
+ TSDebug(DEBUG_TAG, "[%s] Don't know how to handle event type %d", __FUNCTION__, event);
+ break;
+ }
+
+ TSHttpTxnReenable(txnp, TS_EVENT_HTTP_CONTINUE);
+ return 0;
+}
+
+static void
+loadHandlerConf(const char *file_name, Utils::KeyValueMap &handler_conf) {
+ std::list<string> conf_lines;
+ TSFile conf_file = TSfopen(file_name, "r");
+ if (conf_file != NULL) {
+ char buf[1024];
+ while (TSfgets(conf_file, buf, sizeof(buf) - 1) != NULL) {
+ conf_lines.push_back(string(buf));
+ }
+ TSfclose(conf_file);
+ Utils::parseKeyValueConfig(conf_lines, handler_conf);
+ TSDebug(DEBUG_TAG, "[%s] Loaded handler conf file [%s]", __FUNCTION__, file_name);
+ } else {
+ TSError("[%s] Failed to open handler config file [%s]", __FUNCTION__, file_name);
+ }
+}
+
+
+void
+TSPluginInit(int argc, const char *argv[]) {
+ Utils::init(&TSDebug, &TSError);
+ Stats::init(new TSStatSystem());
+
+ gHandlerManager = new HandlerManager(HANDLER_MGR_DEBUG_TAG, &TSDebug, &TSError);
+
+ if ((argc > 1) && (strcmp(argv[1], "-") != 0)) {
+ Utils::KeyValueMap handler_conf;
+ loadHandlerConf(argv[1], handler_conf);
+ gHandlerManager->loadObjects(handler_conf);
+ }
+
+ if(pthread_key_create(&threadKey,NULL)){
+ TSError("[%s] Could not create key", __FUNCTION__);
+ return;
+ }
+
+ TSCont global_contp = TSContCreate(globalHookHandler, NULL);
+ if (!global_contp) {
+ TSError("[%s] Could not create global continuation", __FUNCTION__);
+ return;
+ }
+ TSHttpHookAdd(TS_HTTP_READ_RESPONSE_HDR_HOOK, global_contp);
+
+ TSHttpHookAdd(TS_HTTP_CACHE_LOOKUP_COMPLETE_HOOK, global_contp);
+
+ TSHttpHookAdd(TS_HTTP_READ_REQUEST_HDR_HOOK, global_contp);
+
+ TSDebug(DEBUG_TAG, "[%s] Plugin started and key is set", __FUNCTION__);
+}
http://git-wip-us.apache.org/repos/asf/trafficserver/blob/bc13c5e0/plugins/experimental/esi/serverIntercept.cc
----------------------------------------------------------------------
diff --git a/plugins/experimental/esi/serverIntercept.cc b/plugins/experimental/esi/serverIntercept.cc
new file mode 100644
index 0000000..b5e0fda
--- /dev/null
+++ b/plugins/experimental/esi/serverIntercept.cc
@@ -0,0 +1,350 @@
+/** @file
+
+ A brief file description
+
+ @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 "serverIntercept.h"
+
+#include <string>
+#include <limits.h>
+#include <strings.h>
+#include <stdio.h>
+
+const char *ECHO_HEADER_PREFIX = "Echo-";
+const int ECHO_HEADER_PREFIX_LEN = 5;
+const char *SERVER_INTERCEPT_HEADER = "Esi-Internal";
+const int SERVER_INTERCEPT_HEADER_LEN = 12;
+
+using std::string;
+
+#define DEBUG_TAG "plugin_esi_intercept"
+
+struct ContData {
+ TSVConn net_vc;
+ TSCont contp;
+
+ struct IoHandle {
+ TSVIO vio;
+ TSIOBuffer buffer;
+ TSIOBufferReader reader;
+ IoHandle()
+ : vio(0), buffer(0), reader(0) { };
+ ~IoHandle() {
+ if (reader) {
+ TSIOBufferReaderFree(reader);
+ }
+ if (buffer) {
+ TSIOBufferDestroy(buffer);
+ }
+ };
+ };
+
+ IoHandle input;
+ IoHandle output;
+
+ TSHttpParser http_parser;
+ string body;
+ int req_content_len;
+ TSMBuffer req_hdr_bufp;
+ TSMLoc req_hdr_loc;
+ bool req_hdr_parsed;
+ bool initialized;
+
+ ContData(TSCont cont)
+ : net_vc(0), contp(cont), input(), output(), body(""), req_content_len(0), req_hdr_bufp(0), req_hdr_loc(0),
+ req_hdr_parsed(false), initialized(false) {
+ http_parser = TSHttpParserCreate();
+ }
+
+ bool init(TSVConn vconn);
+
+ void setupWrite();
+
+ ~ContData() {
+ TSDebug(DEBUG_TAG, "[%s] Destroying continuation data", __FUNCTION__);
+ TSHttpParserDestroy(http_parser);
+ if (req_hdr_loc) {
+ TSHandleMLocRelease(req_hdr_bufp, TS_NULL_MLOC, req_hdr_loc);
+ }
+ if (req_hdr_bufp) {
+ TSMBufferDestroy(req_hdr_bufp);
+ }
+ };
+};
+
+bool
+ContData::init(TSVConn vconn)
+{
+ if (initialized) {
+ TSError("[%s] ContData already initialized!", __FUNCTION__);
+ return false;
+ }
+
+ net_vc = vconn;
+
+ input.buffer = TSIOBufferCreate();
+ input.reader = TSIOBufferReaderAlloc(input.buffer);
+ input.vio = TSVConnRead(net_vc, contp, input.buffer, INT_MAX);
+
+ req_hdr_bufp = TSMBufferCreate();
+ req_hdr_loc = TSHttpHdrCreate(req_hdr_bufp);
+ TSHttpHdrTypeSet(req_hdr_bufp, req_hdr_loc, TS_HTTP_TYPE_REQUEST);
+
+ initialized = true;
+ TSDebug(DEBUG_TAG, "[%s] ContData initialized!", __FUNCTION__);
+ return true;
+}
+
+void
+ContData::setupWrite() {
+ TSAssert(output.buffer == 0);
+ output.buffer = TSIOBufferCreate();
+ output.reader = TSIOBufferReaderAlloc(output.buffer);
+ output.vio = TSVConnWrite(net_vc, contp, output.reader, INT_MAX);
+}
+
+static bool
+handleRead(ContData *cont_data, bool &read_complete) {
+ int avail = TSIOBufferReaderAvail(cont_data->input.reader);
+ if (avail == TS_ERROR) {
+ TSError("[%s] Error while getting number of bytes available", __FUNCTION__);
+ return false;
+ }
+
+ int consumed = 0;
+ if (avail > 0) {
+ int64_t data_len;
+ const char *data;
+ TSIOBufferBlock block = TSIOBufferReaderStart(cont_data->input.reader);
+ while (block != NULL) {
+ data = TSIOBufferBlockReadStart(block, cont_data->input.reader, &data_len);
+ if (!cont_data->req_hdr_parsed) {
+ const char *endptr = data + data_len;
+ if (TSHttpHdrParseReq(cont_data->http_parser, cont_data->req_hdr_bufp, cont_data->req_hdr_loc,
+ &data, endptr) == TS_PARSE_DONE) {
+ TSDebug(DEBUG_TAG, "[%s] Parsed header", __FUNCTION__);
+ TSMLoc content_len_loc = TSMimeHdrFieldFind(cont_data->req_hdr_bufp, cont_data->req_hdr_loc,
+ TS_MIME_FIELD_CONTENT_LENGTH, -1);
+ if (!content_len_loc) {
+ TSError("[%s] Error while searching content length header [%s]",
+ __FUNCTION__, TS_MIME_FIELD_CONTENT_LENGTH);
+ return false;
+ }
+ if (!content_len_loc) {
+ TSError("[%s] request doesn't contain content length header [%s]",
+ __FUNCTION__, TS_MIME_FIELD_CONTENT_TYPE);
+ return false;
+ }
+ cont_data->req_content_len=TSMimeHdrFieldValueIntGet(cont_data->req_hdr_bufp, cont_data->req_hdr_loc,
+ content_len_loc, 0);
+ TSHandleMLocRelease(cont_data->req_hdr_bufp, cont_data->req_hdr_loc, content_len_loc);
+ TSDebug(DEBUG_TAG, "[%s] Got content length as %d", __FUNCTION__, cont_data->req_content_len);
+ if (cont_data->req_content_len <= 0) {
+ TSError("[%s] Invalid content length [%d]", __FUNCTION__, cont_data->req_content_len);
+ return false;
+ }
+ if (endptr - data) {
+ TSDebug(DEBUG_TAG, "[%s] Appending %ld bytes to body", __FUNCTION__, endptr - data);
+ cont_data->body.append(data, endptr - data);
+ }
+ cont_data->req_hdr_parsed = true;
+ }
+ } else {
+ TSDebug(DEBUG_TAG, "[%s] Appending %ld bytes to body", __FUNCTION__, data_len);
+ cont_data->body.append(data, data_len);
+ }
+ consumed += data_len;
+ block = TSIOBufferBlockNext(block);
+ if (!block) {
+ TSError("[%s] Error while getting block from ioreader", __FUNCTION__);
+ return false;
+ }
+ }
+ }
+ TSDebug(DEBUG_TAG, "[%s] Consumed %d bytes from input vio", __FUNCTION__, consumed);
+
+ TSIOBufferReaderConsume(cont_data->input.reader, consumed);
+
+ // Modify the input VIO to reflect how much data we've completed.
+ TSVIONDoneSet(cont_data->input.vio, TSVIONDoneGet(cont_data->input.vio) + consumed);
+ if (static_cast<int>(cont_data->body.size()) == cont_data->req_content_len) {
+ TSDebug(DEBUG_TAG, "[%s] Completely read body of size %d", __FUNCTION__, cont_data->req_content_len);
+ read_complete = true;
+ } else {
+ read_complete = false;
+ TSDebug(DEBUG_TAG, "[%s] Reenabling input vio as %ld bytes still need to be read",
+ __FUNCTION__, cont_data->req_content_len - cont_data->body.size());
+ TSVIOReenable(cont_data->input.vio);
+ }
+ return true;
+}
+
+static bool
+processRequest(ContData *cont_data) {
+ string reply_header("HTTP/1.0 200 OK\r\n");
+
+ TSMLoc field_loc = TSMimeHdrFieldGet(cont_data->req_hdr_bufp, cont_data->req_hdr_loc, 0);
+ while (field_loc) {
+ TSMLoc next_field_loc;
+ const char *name;
+ int name_len;
+ name = TSMimeHdrFieldNameGet(cont_data->req_hdr_bufp, cont_data->req_hdr_loc, field_loc, &name_len);
+ if (name) {
+ bool echo_header = false;
+ if ((name_len > ECHO_HEADER_PREFIX_LEN) &&
+ (strncasecmp(name, ECHO_HEADER_PREFIX, ECHO_HEADER_PREFIX_LEN) == 0)) {
+ echo_header = true;
+ reply_header.append(name + ECHO_HEADER_PREFIX_LEN, name_len - ECHO_HEADER_PREFIX_LEN);
+ } else if ((name_len == SERVER_INTERCEPT_HEADER_LEN) &&
+ (strncasecmp(name, SERVER_INTERCEPT_HEADER, name_len) == 0)) {
+ echo_header = true;
+ reply_header.append(name, name_len);
+ }
+ if (echo_header) {
+ reply_header.append(": ");
+ int n_field_values = TSMimeHdrFieldValuesCount(cont_data->req_hdr_bufp, cont_data->req_hdr_loc,
+ field_loc);
+ for (int i = 0; i < n_field_values; ++i) {
+ const char *value;
+ int value_len;
+ value=TSMimeHdrFieldValueStringGet(cont_data->req_hdr_bufp, cont_data->req_hdr_loc, field_loc,
+ i, &value_len);
+ if (!value_len) {
+ TSDebug(DEBUG_TAG, "[%s] Error while getting value #%d of header [%.*s]",
+ __FUNCTION__, i, name_len, name);
+ } else {
+ if (reply_header[reply_header.size() - 2] != ':') {
+ reply_header.append(", ");
+ }
+ reply_header.append(value, value_len);
+ }
+ }
+ reply_header += "\r\n";
+ }
+ }
+ next_field_loc = TSMimeHdrFieldNext(cont_data->req_hdr_bufp, cont_data->req_hdr_loc, field_loc);
+ TSHandleMLocRelease(cont_data->req_hdr_bufp, cont_data->req_hdr_loc, field_loc);
+ field_loc = next_field_loc;
+ }
+
+ int body_size = static_cast<int>(cont_data->body.size());
+ if (cont_data->req_content_len != body_size) {
+ TSError("[%s] Read only %d bytes of body; expecting %d bytes", __FUNCTION__, body_size,
+ cont_data->req_content_len);
+ }
+
+ char buf[64];
+ snprintf(buf, 64, "%s: %d\r\n\r\n", TS_MIME_FIELD_CONTENT_LENGTH, body_size);
+ reply_header.append(buf);
+
+ cont_data->setupWrite();
+ if (TSIOBufferWrite(cont_data->output.buffer, reply_header.data(), reply_header.size()) == TS_ERROR) {
+ TSError("[%s] Error while writing reply header", __FUNCTION__);
+ return false;
+ }
+ if (TSIOBufferWrite(cont_data->output.buffer, cont_data->body.data(), body_size) == TS_ERROR) {
+ TSError("[%s] Error while writing content", __FUNCTION__);
+ return false;
+ }
+ int total_bytes_written = reply_header.size() + body_size;
+ TSDebug(DEBUG_TAG, "[%s] Wrote reply of size %d", __FUNCTION__, total_bytes_written);
+ TSVIONBytesSet(cont_data->output.vio, total_bytes_written);
+
+ TSVIOReenable(cont_data->output.vio);
+ return true;
+}
+
+static int
+serverIntercept(TSCont contp, TSEvent event, void *edata) {
+ ContData *cont_data = static_cast<ContData *>(TSContDataGet(contp));
+ bool read_complete = false;
+ bool shutdown = false;
+ switch (event) {
+ case TS_EVENT_NET_ACCEPT:
+ TSDebug(DEBUG_TAG, "[%s] Received net accept event", __FUNCTION__);
+ TSAssert(cont_data->initialized == false);
+ if (!cont_data->init(static_cast<TSVConn>(edata))) {
+ TSError("[%s] Could not initialize continuation data!", __FUNCTION__);
+ return 1;
+ }
+ break;
+ case TS_EVENT_VCONN_READ_READY:
+ TSDebug(DEBUG_TAG, "[%s] Received read ready event", __FUNCTION__);
+ if (!handleRead(cont_data, read_complete)) {
+ TSError("[%s] Error while reading from input vio", __FUNCTION__);
+ return 0;
+ }
+ break;
+ case TS_EVENT_VCONN_READ_COMPLETE:
+ case TS_EVENT_VCONN_EOS:
+ // intentional fall-through
+ TSDebug(DEBUG_TAG, "[%s] Received read complete/eos event %d", __FUNCTION__, event);
+ read_complete = true;
+ break;
+ case TS_EVENT_VCONN_WRITE_READY:
+ TSDebug(DEBUG_TAG, "[%s] Received write ready event", __FUNCTION__);
+ break;
+ case TS_EVENT_VCONN_WRITE_COMPLETE:
+ TSDebug(DEBUG_TAG, "[%s] Received write complete event", __FUNCTION__);
+ shutdown = true;
+ break;
+ case TS_EVENT_ERROR:
+ // todo: do some error handling here
+ TSError("[%s] Received error event; going to shutdown", __FUNCTION__);
+ shutdown = true;
+ break;
+ default:
+ break;
+ }
+
+ if (read_complete) {
+ if (!processRequest(cont_data)) {
+ TSError("[%s] Failed to process process", __FUNCTION__);
+ } else {
+ TSDebug(DEBUG_TAG, "[%s] Processed request successfully", __FUNCTION__);
+ }
+ }
+
+ if (shutdown) {
+ TSDebug(DEBUG_TAG, "[%s] Completed request processing. Shutting down...", __FUNCTION__);
+ TSVConnClose(cont_data->net_vc);
+ delete cont_data;
+ TSContDestroy(contp);
+ }
+
+ return 1;
+}
+
+bool
+setupServerIntercept(TSHttpTxn txnp) {
+ TSCont contp = TSContCreate(serverIntercept, TSMutexCreate());
+ if (!contp) {
+ TSError("[%s] Could not create intercept request", __FUNCTION__);
+ return false;
+ }
+ ContData *cont_data = new ContData(contp);
+ TSContDataSet(contp, cont_data);
+ TSHttpTxnServerIntercept(contp, txnp);
+ TSHttpTxnReqCacheableSet(txnp, 1);
+ TSHttpTxnRespCacheableSet(txnp, 1);
+ TSDebug(DEBUG_TAG, "[%s] Setup server intercept successfully", __FUNCTION__);
+ return true;
+}
http://git-wip-us.apache.org/repos/asf/trafficserver/blob/bc13c5e0/plugins/experimental/esi/serverIntercept.h
----------------------------------------------------------------------
diff --git a/plugins/experimental/esi/serverIntercept.h b/plugins/experimental/esi/serverIntercept.h
new file mode 100644
index 0000000..0e4145f
--- /dev/null
+++ b/plugins/experimental/esi/serverIntercept.h
@@ -0,0 +1,38 @@
+/** @file
+
+ A brief file description
+
+ @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.
+ */
+
+#ifndef _ESI_SERVER_INTERCEPT_H
+
+#define _ESI_SERVER_INTERCEPT_H
+
+#include "ts/ts.h"
+
+bool setupServerIntercept(TSHttpTxn txnp);
+
+extern const char *ECHO_HEADER_PREFIX;
+extern const char *SERVER_INTERCEPT_HEADER;
+
+extern const int ECHO_HEADER_PREFIX_LEN;
+extern const int SERVER_INTERCEPT_HEADER_LEN;
+
+#endif
http://git-wip-us.apache.org/repos/asf/trafficserver/blob/bc13c5e0/plugins/experimental/esi/test/HandlerManager.cc
----------------------------------------------------------------------
diff --git a/plugins/experimental/esi/test/HandlerManager.cc b/plugins/experimental/esi/test/HandlerManager.cc
new file mode 100644
index 0000000..548a35e
--- /dev/null
+++ b/plugins/experimental/esi/test/HandlerManager.cc
@@ -0,0 +1,42 @@
+/** @file
+
+ A brief file description
+
+ @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 "HandlerManager.h"
+#include "HandlerMap.h"
+
+using namespace EsiLib;
+
+void
+HandlerManager::loadObjects(const Utils::KeyValueMap &handlers) {
+}
+
+SpecialIncludeHandler *
+HandlerManager::getHandler(Variables &esi_vars, Expression &esi_expr, HttpDataFetcher &fetcher,
+ const std::string &id) const {
+ StubIncludeHandler *handler = new StubIncludeHandler(esi_vars, esi_expr, fetcher);
+ gHandlerMap[id] = handler;
+ return handler;
+}
+
+HandlerManager::~HandlerManager() {
+}
http://git-wip-us.apache.org/repos/asf/trafficserver/blob/bc13c5e0/plugins/experimental/esi/test/HandlerMap.cc
----------------------------------------------------------------------
diff --git a/plugins/experimental/esi/test/HandlerMap.cc b/plugins/experimental/esi/test/HandlerMap.cc
new file mode 100644
index 0000000..9f2bad0
--- /dev/null
+++ b/plugins/experimental/esi/test/HandlerMap.cc
@@ -0,0 +1,26 @@
+/** @file
+
+ A brief file description
+
+ @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 "HandlerMap.h"
+
+HandlerMap gHandlerMap;
http://git-wip-us.apache.org/repos/asf/trafficserver/blob/bc13c5e0/plugins/experimental/esi/test/HandlerMap.h
----------------------------------------------------------------------
diff --git a/plugins/experimental/esi/test/HandlerMap.h b/plugins/experimental/esi/test/HandlerMap.h
new file mode 100644
index 0000000..faaf669
--- /dev/null
+++ b/plugins/experimental/esi/test/HandlerMap.h
@@ -0,0 +1,37 @@
+/** @file
+
+ A brief file description
+
+ @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.
+ */
+
+#ifndef _HANDLER_MAP_H
+
+#define _HANDLER_MAP_H
+
+#include <string>
+#include <map>
+
+#include "StubIncludeHandler.h"
+
+typedef std::map<std::string, StubIncludeHandler *> HandlerMap;
+
+extern HandlerMap gHandlerMap;
+
+#endif
http://git-wip-us.apache.org/repos/asf/trafficserver/blob/bc13c5e0/plugins/experimental/esi/test/StubIncludeHandler.cc
----------------------------------------------------------------------
diff --git a/plugins/experimental/esi/test/StubIncludeHandler.cc b/plugins/experimental/esi/test/StubIncludeHandler.cc
new file mode 100644
index 0000000..0b99a28
--- /dev/null
+++ b/plugins/experimental/esi/test/StubIncludeHandler.cc
@@ -0,0 +1,73 @@
+/** @file
+
+ A brief file description
+
+ @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 "StubIncludeHandler.h"
+#include "TestHttpDataFetcher.h"
+
+using namespace EsiLib;
+
+bool StubIncludeHandler::includeResult = true;
+const char *const StubIncludeHandler::DATA_PREFIX = "Special data for include id ";
+const int StubIncludeHandler::DATA_PREFIX_SIZE = strlen(StubIncludeHandler::DATA_PREFIX);
+
+const char *StubIncludeHandler::FOOTER = 0;
+int StubIncludeHandler::FOOTER_SIZE = 0;
+
+int
+StubIncludeHandler::handleInclude(const char *data, int data_len) {
+ if (includeResult) {
+ _http_fetcher.addFetchRequest(data, data_len);
+ return ++n_includes;
+ }
+ return -1;
+}
+
+void
+StubIncludeHandler::handleParseComplete() {
+ parseCompleteCalled = true;
+}
+
+bool
+StubIncludeHandler::getData(int include_id, const char *&data, int &data_len) {
+ TestHttpDataFetcher &test_fetcher = dynamic_cast<TestHttpDataFetcher &>(_http_fetcher);
+ if (test_fetcher.getReturnData()) {
+ char *buf = new char[1024];
+ data_len = snprintf(buf, 1024, "%s%d", DATA_PREFIX, include_id);
+ data = buf;
+ heap_strings.push_back(buf);
+ return true;
+ }
+ return false;
+}
+
+void
+StubIncludeHandler::getFooter(const char *&footer, int &footer_len) {
+ footer = FOOTER;
+ footer_len = FOOTER_SIZE;
+}
+
+StubIncludeHandler::~StubIncludeHandler() {
+ for (std::list<char *>::iterator iter = heap_strings.begin(); iter != heap_strings.end(); ++iter) {
+ delete[] *iter;
+ }
+}
http://git-wip-us.apache.org/repos/asf/trafficserver/blob/bc13c5e0/plugins/experimental/esi/test/StubIncludeHandler.h
----------------------------------------------------------------------
diff --git a/plugins/experimental/esi/test/StubIncludeHandler.h b/plugins/experimental/esi/test/StubIncludeHandler.h
new file mode 100644
index 0000000..576834b
--- /dev/null
+++ b/plugins/experimental/esi/test/StubIncludeHandler.h
@@ -0,0 +1,66 @@
+/** @file
+
+ A brief file description
+
+ @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.
+ */
+
+#ifndef _STUB_INCLUDE_HANDLER_H
+
+#define _STUB_INCLUDE_HANDLER_H
+
+#include <list>
+#include "SpecialIncludeHandler.h"
+
+class StubIncludeHandler : public EsiLib::SpecialIncludeHandler {
+
+public:
+
+ StubIncludeHandler(EsiLib::Variables &esi_vars, EsiLib::Expression &esi_expr,
+ HttpDataFetcher &http_fetcher)
+ : EsiLib::SpecialIncludeHandler(esi_vars, esi_expr, http_fetcher),
+ parseCompleteCalled(false), n_includes(0) {
+ }
+
+ int handleInclude(const char *data, int data_len);
+
+ bool parseCompleteCalled;
+ void handleParseComplete();
+
+ bool getData(int include_id, const char *&data, int &data_len);
+
+ void getFooter(const char *&footer, int &footer_len);
+
+ ~StubIncludeHandler();
+
+ static bool includeResult;
+ static const char *const DATA_PREFIX;
+ static const int DATA_PREFIX_SIZE;
+
+ static const char *FOOTER;
+ static int FOOTER_SIZE;
+
+private:
+
+ int n_includes;
+ std::list<char *> heap_strings;
+
+};
+
+#endif
http://git-wip-us.apache.org/repos/asf/trafficserver/blob/bc13c5e0/plugins/experimental/esi/test/TestHttpDataFetcher.h
----------------------------------------------------------------------
diff --git a/plugins/experimental/esi/test/TestHttpDataFetcher.h b/plugins/experimental/esi/test/TestHttpDataFetcher.h
new file mode 100644
index 0000000..1df387e
--- /dev/null
+++ b/plugins/experimental/esi/test/TestHttpDataFetcher.h
@@ -0,0 +1,79 @@
+/** @file
+
+ A brief file description
+
+ @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.
+ */
+
+#ifndef _TEST_HTTP_DATA_FETCHER_H
+#define _TEST_HTTP_DATA_FETCHER_H
+
+#include <string>
+
+#include "HttpDataFetcher.h"
+
+class TestHttpDataFetcher : public HttpDataFetcher
+{
+
+public:
+
+ TestHttpDataFetcher() : _n_pending_requests(0), _return_data(true) { }
+
+ bool addFetchRequest(const std::string &url, FetchedDataProcessor *callback_obj = 0) {
+ ++_n_pending_requests;
+ return true;
+ }
+
+ DataStatus getRequestStatus(const std::string &url) const {
+ if (_return_data) {
+ return STATUS_DATA_AVAILABLE;
+ }
+ --(const_cast<int &>(_n_pending_requests));
+ return STATUS_ERROR;
+ }
+
+ int getNumPendingRequests() const { return _n_pending_requests; };
+
+ bool getContent(const std::string &url, const char *&content, int &content_len) const {
+ TestHttpDataFetcher &curr_obj = const_cast<TestHttpDataFetcher &>(*this);
+ --curr_obj._n_pending_requests;
+ if (_return_data) {
+ curr_obj._data.clear();
+ curr_obj._data.append(">>>>> Content for URL [");
+ curr_obj._data.append(url);
+ curr_obj._data.append("] <<<<<");
+ content = curr_obj._data.data();
+ content_len = curr_obj._data.size();
+ return true;
+ }
+ return false;
+ }
+
+ void setReturnData(bool rd) { _return_data = rd; };
+
+ bool getReturnData() const { return _return_data; };
+
+private:
+ int _n_pending_requests;
+ std::string _data;
+ bool _return_data;
+
+};
+
+#endif
http://git-wip-us.apache.org/repos/asf/trafficserver/blob/bc13c5e0/plugins/experimental/esi/test/docnode_test.cc
----------------------------------------------------------------------
diff --git a/plugins/experimental/esi/test/docnode_test.cc b/plugins/experimental/esi/test/docnode_test.cc
new file mode 100644
index 0000000..319eb19
--- /dev/null
+++ b/plugins/experimental/esi/test/docnode_test.cc
@@ -0,0 +1,246 @@
+/** @file
+
+ A brief file description
+
+ @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 <iostream>
+#include <assert.h>
+#include <string>
+
+#include "EsiParser.h"
+#include "print_funcs.h"
+#include "Utils.h"
+
+using std::cout;
+using std::cerr;
+using std::endl;
+using std::string;
+using namespace EsiLib;
+
+void check_node_attr(const Attribute &attr, const char *name, const char *value) {
+ int name_len = strlen(name);
+ int value_len = strlen(value);
+ assert(attr.name_len == name_len);
+ assert(attr.value_len == value_len);
+ assert(strncmp(attr.name, name, name_len) == 0);
+ assert(strncmp(attr.value, value, value_len) == 0);
+}
+
+void checkNodeList1(const DocNodeList &node_list) {
+ DocNodeList::const_iterator list_iter = node_list.begin();
+ assert(list_iter->type == DocNode::TYPE_PRE);
+ assert(list_iter->data_len == 4);
+ assert(strncmp(list_iter->data, "foo ", list_iter->data_len) == 0);
+ ++list_iter;
+ assert(list_iter->type == DocNode::TYPE_INCLUDE);
+ assert(list_iter->data_len == 0);
+ assert(list_iter->attr_list.size() == 1);
+ check_node_attr(list_iter->attr_list.front(), "src", "blah");
+ ++list_iter;
+ assert(list_iter->type == DocNode::TYPE_PRE);
+ assert(list_iter->data_len == 4);
+ assert(strncmp(list_iter->data, " bar", list_iter->data_len) == 0);
+ assert((list_iter->child_nodes).size() == 0);
+}
+
+void checkNodeList2(const DocNodeList &node_list) {
+ assert(node_list.size() == 1);
+ DocNodeList::const_iterator list_iter = node_list.begin(), list_iter2, list_iter3;
+ assert(list_iter->type == DocNode::TYPE_CHOOSE);
+
+ list_iter2 = list_iter->child_nodes.begin();
+ assert(list_iter2->type == DocNode::TYPE_WHEN);
+ assert(list_iter2->attr_list.size() == 1);
+ check_node_attr(list_iter2->attr_list.front(), "test", "c1");
+ list_iter2 = list_iter2->child_nodes.begin();
+ assert(list_iter2->type == DocNode::TYPE_TRY);
+ list_iter2 = list_iter2->child_nodes.begin();
+ assert(list_iter2->type == DocNode::TYPE_ATTEMPT);
+ assert(list_iter2->child_nodes.size() == 2);
+ list_iter3 = list_iter2->child_nodes.begin();
+ assert(list_iter3->type == DocNode::TYPE_INCLUDE);
+ assert(list_iter3->data_len == 0);
+ assert(list_iter3->attr_list.size() == 1);
+ check_node_attr(list_iter3->attr_list.front(), "src", "foo1");
+ ++list_iter3;
+ assert(list_iter3->type == DocNode::TYPE_PRE);
+ assert(list_iter3->data_len == strlen("raw1"));
+ assert(strncmp(list_iter3->data, "raw1", list_iter3->data_len) == 0);
+ ++list_iter2;
+ assert(list_iter2->type == DocNode::TYPE_EXCEPT);
+ list_iter3 = list_iter2->child_nodes.begin();
+ assert(list_iter3->type == DocNode::TYPE_INCLUDE);
+ assert(list_iter3->data_len == 0);
+ assert(list_iter3->attr_list.size() == 1);
+ check_node_attr(list_iter3->attr_list.front(), "src", "bar1");
+
+ list_iter2 = list_iter->child_nodes.begin();
+ ++list_iter2;
+ assert(list_iter2->type == DocNode::TYPE_WHEN);
+ assert(list_iter2->attr_list.size() == 1);
+ check_node_attr(list_iter2->attr_list.front(), "test", "c2");
+ list_iter2 = list_iter2->child_nodes.begin();
+ assert(list_iter2->type == DocNode::TYPE_TRY);
+ list_iter2 = list_iter2->child_nodes.begin();
+ assert(list_iter2->type == DocNode::TYPE_ATTEMPT);
+ list_iter3 = list_iter2->child_nodes.begin();
+ assert(list_iter3->type == DocNode::TYPE_INCLUDE);
+ assert(list_iter3->data_len == 0);
+ assert(list_iter3->attr_list.size() == 1);
+ check_node_attr(list_iter3->attr_list.front(), "src", "foo2");
+ ++list_iter2;
+ assert(list_iter2->type == DocNode::TYPE_EXCEPT);
+ assert(list_iter2->child_nodes.size() == 2);
+ list_iter3 = list_iter2->child_nodes.begin();
+ assert(list_iter3->type == DocNode::TYPE_PRE);
+ assert(list_iter3->data_len == strlen("raw2"));
+ assert(strncmp(list_iter3->data, "raw2", list_iter3->data_len) == 0);
+ ++list_iter3;
+ assert(list_iter3->type == DocNode::TYPE_INCLUDE);
+ assert(list_iter3->data_len == 0);
+ assert(list_iter3->attr_list.size() == 1);
+ check_node_attr(list_iter3->attr_list.front(), "src", "bar2");
+
+ list_iter2 = list_iter->child_nodes.begin();
+ ++list_iter2;
+ ++list_iter2;
+ assert(list_iter2->type == DocNode::TYPE_OTHERWISE);
+ assert(list_iter2->attr_list.size() == 0);
+ list_iter2 = list_iter2->child_nodes.begin();
+ assert(list_iter2->type == DocNode::TYPE_TRY);
+ list_iter2 = list_iter2->child_nodes.begin();
+ assert(list_iter2->type == DocNode::TYPE_ATTEMPT);
+ list_iter3 = list_iter2->child_nodes.begin();
+ assert(list_iter3->type == DocNode::TYPE_INCLUDE);
+ assert(list_iter3->data_len == 0);
+ assert(list_iter3->attr_list.size() == 1);
+ check_node_attr(list_iter3->attr_list.front(), "src", "foo3");
+ ++list_iter2;
+ assert(list_iter2->type == DocNode::TYPE_EXCEPT);
+ list_iter3 = list_iter2->child_nodes.begin();
+ assert(list_iter3->type == DocNode::TYPE_INCLUDE);
+ assert(list_iter3->data_len == 0);
+ assert(list_iter3->attr_list.size() == 1);
+ check_node_attr(list_iter3->attr_list.front(), "src", "bar3");
+}
+
+int main()
+{
+ Utils::init(&Debug, &Error);
+
+ {
+ cout << endl << "==================== Test 1" << endl;
+ EsiParser parser("parser_test", &Debug, &Error);
+ string input_data = "foo <esi:include src=blah /> bar";
+
+ DocNodeList node_list;
+ assert(parser.completeParse(node_list, input_data) == true);
+ checkNodeList1(node_list);
+ assert(node_list.size() == 3);
+ string packed = node_list.pack();
+ node_list.clear();
+
+ DocNodeList node_list2;
+ assert(node_list2.unpack(packed) == true);
+ assert(node_list2.size() == 3);
+ checkNodeList1(node_list2);
+
+ DocNodeList node_list3;
+ assert(node_list3.unpack(0, 90) == false);
+ assert(node_list3.unpack(packed.data(), 3) == false);
+ *(reinterpret_cast<int *>(&packed[0])) = -1;
+ assert(node_list3.unpack(packed) == true);
+ assert(node_list3.size() == 0);
+ *(reinterpret_cast<int *>(&packed[0])) = 3;
+
+ DocNodeList node_list4;
+ assert(node_list4.unpack(packed) == true);
+ assert(node_list4.size() == 3);
+ checkNodeList1(node_list4);
+ }
+
+ {
+ cout << endl << "==================== Test 2" << endl;
+ EsiParser parser("parser_test", &Debug, &Error);
+ string input_data("<esi:choose>"
+ "<esi:when test=c1>"
+ "<esi:try>"
+ "<esi:attempt>"
+ "<esi:include src=foo1 />"
+ "raw1"
+ "</esi:attempt>"
+ "<esi:except>"
+ "<esi:include src=bar1 />"
+ "</esi:except>"
+ "</esi:try>"
+ "</esi:when>"
+ "<esi:when test=c2>"
+ "<esi:try>"
+ "<esi:attempt>"
+ "<esi:include src=foo2 />"
+ "</esi:attempt>"
+ "<esi:except>"
+ "raw2"
+ "<esi:include src=bar2 />"
+ "</esi:except>"
+ "</esi:try>"
+ "</esi:when>"
+ "<esi:otherwise>"
+ "<esi:try>"
+ "<esi:attempt>"
+ "<esi:include src=foo3 />"
+ "</esi:attempt>"
+ "<esi:except>"
+ "<esi:include src=bar3 />"
+ "</esi:except>"
+ "</esi:try>"
+ "</esi:otherwise>"
+ "</esi:choose>");
+
+ DocNodeList node_list;
+ assert(parser.completeParse(node_list, input_data) == true);
+ checkNodeList2(node_list);
+
+ string packed = node_list.pack();
+ DocNodeList node_list2;
+ assert(node_list2.unpack(packed) == true);
+ checkNodeList2(node_list2);
+
+ string packed2;
+ node_list.pack(packed2);
+ assert(packed == packed2);
+ node_list2.clear();
+ assert(node_list2.unpack(packed2) == true);
+ checkNodeList2(node_list2);
+
+ string packed3("hello");
+ node_list.pack(packed3, true);
+ assert(packed3.size() == (packed.size() + 5));
+ node_list2.clear();
+ assert(node_list2.unpack(packed3) == false);
+ assert(node_list2.unpack(packed3.data() + 5, packed3.size() - 5) == true);
+ checkNodeList2(node_list2);
+ }
+
+ cout << "All tests passed" << endl;
+ return 0;
+}
+