You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@trafficserver.apache.org by jp...@apache.org on 2014/04/23 22:54:13 UTC
git commit: TS-2741: add a new server intercept example plugin
Repository: trafficserver
Updated Branches:
refs/heads/master 94ed95b15 -> eb183eef9
TS-2741: add a new server intercept example plugin
Add a new example plugin to demonstrate the use of TSHttpTxnServerIntercept
to proxy origin requests to a secondary server.
Document TSHttpTxnServerIntercept, and update surrounding documentation
that references it.
Add a new test, test-server-intercept, that combines the example
plugin and jtest to do an end-to-end test of the intercept API.
Project: http://git-wip-us.apache.org/repos/asf/trafficserver/repo
Commit: http://git-wip-us.apache.org/repos/asf/trafficserver/commit/eb183eef
Tree: http://git-wip-us.apache.org/repos/asf/trafficserver/tree/eb183eef
Diff: http://git-wip-us.apache.org/repos/asf/trafficserver/diff/eb183eef
Branch: refs/heads/master
Commit: eb183eef9e25c28d2244bc7195da61fd47380726
Parents: 94ed95b
Author: James Peach <jp...@apache.org>
Authored: Tue Apr 15 17:10:20 2014 -0700
Committer: James Peach <jp...@apache.org>
Committed: Wed Apr 23 13:38:46 2014 -0700
----------------------------------------------------------------------
CHANGES | 2 +
ci/tsqa/test-server-intercept | 80 +++
.../api/TSHttpTxnServerIntercept.en.rst | 67 +++
doc/reference/api/TSRemap.en.rst | 4 +-
doc/reference/api/index.en.rst | 7 +-
.../writing-handler-functions.en.rst | 120 ++--
example/Makefile.am | 2 +
example/intercept/intercept.cc | 576 +++++++++++++++++++
8 files changed, 794 insertions(+), 64 deletions(-)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/trafficserver/blob/eb183eef/CHANGES
----------------------------------------------------------------------
diff --git a/CHANGES b/CHANGES
index 6ffcde2..9f55f55 100644
--- a/CHANGES
+++ b/CHANGES
@@ -1,6 +1,8 @@
-*- coding: utf-8 -*-
Changes with Apache Traffic Server 5.0.0
+ *) [TS-2741] Add a server intercept example plugins and documentation.
+
*) [TS-2616] Sanitize duplicate Transfer-Encoding: chunked headers.
Author: Dimitry Andric <di...@andric.com>
http://git-wip-us.apache.org/repos/asf/trafficserver/blob/eb183eef/ci/tsqa/test-server-intercept
----------------------------------------------------------------------
diff --git a/ci/tsqa/test-server-intercept b/ci/tsqa/test-server-intercept
new file mode 100755
index 0000000..ee1eab3
--- /dev/null
+++ b/ci/tsqa/test-server-intercept
@@ -0,0 +1,80 @@
+#! /usr/bin/env bash
+
+# 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.
+
+TSQA_TSXS=${TSQA_TSXS:-/opt/ats/bin/tsxs}
+TSQA_TESTNAME=$(basename $0)
+source $(dirname $0)/functions
+
+# The intercept example plugin is hard-coded to port 60000.
+SERVER_PORT=${SERVER_PORT:-60000}
+
+# Use traffic_line -m to list all the configuration variables. Verify their values with
+# traffic_line -r. This tests the TSRecordGet() and TSRecordGetMatchMult() remote APIs.
+check() {
+ local key
+ local val1
+ local val2
+
+ local bindir=$(bindir)
+
+ tsexec traffic_line -m proxy.config | while read key val1 ; do
+ val2=$(TS_ROOT=$TSQA_ROOT $bindir/traffic_line -r $key)
+ if [ "$?" != "0" ]; then
+ fail failed to fetch value for $key
+ elif [ "$val1" != "$val1" ] ; then
+ fail value mismatch for $key, expected:\"$val1\", received:\"$val2\"
+ fi
+ done
+}
+
+bootstrap
+
+if [ ! -x $(bindir)/jtest ] ; then
+ fatal "missing jtest program; rebuild with --enable-test-tools"
+fi
+
+cat >$TSQA_ROOT/$(sysconfdir)/remap.config <<REMAP
+map http://jtest.trafficserver.apache.org:$SERVER_PORT http://127.0.0.1:$SERVER_PORT
+REMAP
+
+cat >$TSQA_ROOT/$(sysconfdir)/plugin.config <<REMAP
+#intercept.so
+REMAP
+
+# If Traffic Server is not up, bring it up ...
+alive cop || startup || fatal unable to start Traffic Server
+trap shutdown 0 EXIT
+
+# Wait for traffic_manager to start.
+alive manager
+alive server
+msgwait 1
+
+# Run jtest for a while to exercise the intercept path. Note that jtest only exercises
+# one of many possible sequences of events. Specifically, the jtest server will always
+# close the socket before the client finishes reasing the response.
+$(bindir)/jtest --proxy_port $PORT --proxy_host 127.0.0.1 \
+ --server_port $SERVER_PORT --server_host jtest.trafficserver.apache.org \
+ --clients 10 --test_time 60
+
+# Check for a crash ...
+crash
+
+exit $TSQA_FAIL
+
+# vim: set sw=2 ts=2 et :
http://git-wip-us.apache.org/repos/asf/trafficserver/blob/eb183eef/doc/reference/api/TSHttpTxnServerIntercept.en.rst
----------------------------------------------------------------------
diff --git a/doc/reference/api/TSHttpTxnServerIntercept.en.rst b/doc/reference/api/TSHttpTxnServerIntercept.en.rst
new file mode 100644
index 0000000..850638e
--- /dev/null
+++ b/doc/reference/api/TSHttpTxnServerIntercept.en.rst
@@ -0,0 +1,67 @@
+.. 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.
+
+.. default-domain:: c
+
+========================
+TSHttpTxnServerIntercept
+========================
+
+Synopsis
+========
+
+`#include <ts/ts.h>`
+
+.. function:: void TSHttpTxnServerIntercept(TSCont contp, TSHttpTxn txnp)
+
+Description
+===========
+
+:func:`TSHttpTxnServerIntercept` allows a plugin take over the
+servicing of the request as though it was the origin server. In the
+event a request needs to be made to the server for transaction
+:arg:`txnp`, :arg:`contp` will be sent a :data:`TS_EVENT_NET_ACCEPT`
+event. The :arg:`edata` passed with :data:`TS_NET_EVENT_ACCEPT` is
+an :type:`TSVConn` just as it would be for a normal accept. The
+plugin must act as if it is an HTTP server and read the HTTP request
+and body from the :type:`TSVConn` and send an HTTP response header
+and body.
+
+:func:`TSHttpTxnServerIntercept` must be not be called after the
+connection to the server has taken place. This means that the last
+hook last hook that it can be called from is
+:data:`TS_HTTP_READ_CACHE_HDR_HOOK`. If a connection to the server
+is not necessary, the continuation :arg:`contp` will be sent a
+:data:`TS_EVENT_NET_ACCEPT_FAILED` event when the transaction
+completes.
+
+The response from the plugin is cached subject to standard and
+configured HTTP caching rules. Should the plugin wish the response
+not be cached, the plugin must use appropriate HTTP response headers
+to prevent caching. The primary purpose of :func:`TSHttpTxnServerIntercept`
+is allow plugins to provide gateways to other protocols or to allow
+to plugin to it's own transport for the next hop to the server.
+:func:`TSHttpTxnServerIntercept` overrides parent cache configuration.
+
+:func:`TSHttpTxnServerIntercept` must only be called once per
+transaction. The continuation :arg:`contp` must have been created
+with a valid :type:`TSMutex`.
+
+See also
+========
+
+:manpage:`TSAPI(3ts)`
http://git-wip-us.apache.org/repos/asf/trafficserver/blob/eb183eef/doc/reference/api/TSRemap.en.rst
----------------------------------------------------------------------
diff --git a/doc/reference/api/TSRemap.en.rst b/doc/reference/api/TSRemap.en.rst
index 4094c63..e0cb597 100644
--- a/doc/reference/api/TSRemap.en.rst
+++ b/doc/reference/api/TSRemap.en.rst
@@ -23,8 +23,8 @@ TSRemapInit
Synopsis
========
-`#include <ts/ts.h>`
-`#include <ts/remap.h>`
+| `#include <ts/ts.h>`
+| `#include <ts/remap.h>`
.. function:: TSReturnCode TSRemapInit(TSRemapInterface * api_info, char* errbuf, int errbuf_size)
.. function:: void TSRemapDone(void)
http://git-wip-us.apache.org/repos/asf/trafficserver/blob/eb183eef/doc/reference/api/index.en.rst
----------------------------------------------------------------------
diff --git a/doc/reference/api/index.en.rst b/doc/reference/api/index.en.rst
index 2cadf91..86f931d 100644
--- a/doc/reference/api/index.en.rst
+++ b/doc/reference/api/index.en.rst
@@ -24,21 +24,22 @@ API Reference
TSAPI.en
TSDebug.en
TSHttpHookAdd.en
+ TSHttpOverridableConfig.en
TSHttpParserCreate.en
TSHttpTxnMilestoneGet.en
+ TSHttpTxnServerIntercept.en
TSIOBufferCreate.en
TSInstallDirGet.en
+ TSLifecycleHookAdd.en
TSMBufferCreate.en
TSMimeHdrFieldValueStringGet.en
TSPluginInit.en
TSRemap.en
TSTrafficServerVersionGet.en
+ TSTypes.en
TSUrlCreate.en
TSUrlHostGet.en
TSUrlHostSet.en
TSUrlPercentEncode.en
TSUrlStringGet.en
- TSLifecycleHookAdd.en
- TSHttpOverridableConfig.en
TSmalloc.en
- TSTypes.en
http://git-wip-us.apache.org/repos/asf/trafficserver/blob/eb183eef/doc/sdk/continuations/writing-handler-functions.en.rst
----------------------------------------------------------------------
diff --git a/doc/sdk/continuations/writing-handler-functions.en.rst b/doc/sdk/continuations/writing-handler-functions.en.rst
index 8f451e6..c698857 100644
--- a/doc/sdk/continuations/writing-handler-functions.en.rst
+++ b/doc/sdk/continuations/writing-handler-functions.en.rst
@@ -18,6 +18,8 @@ Writing Handler Functions
specific language governing permissions and limitations
under the License.
+.. default-domain:: c
+
The handler function is the key component of a continuation. It is
supposed to examine the event and event data, and then do something
appropriate. The probable action might be to schedule another event for
@@ -25,10 +27,10 @@ the continuation to received, to open up a connection to a server, or
simply to destroy itself.
The continuation's handler function is a function of type
-``TSEventFunc``. Its arguments are a continuation, an event, and a
+:type:`TSEventFunc`. Its arguments are a continuation, an event, and a
pointer to some data (this data is passed to the continuation by the
caller - do not confuse this data with the continuation's own data,
-associated by ``TSContDataSet``). When the continuation is called back,
+associated by :func:`TSContDataSet`). When the continuation is called back,
the continuation and an event are passed to the handler function. The
continuation is a handle to the same continuation that is invoked. The
handler function typically has a switch statement to handle the events
@@ -57,67 +59,67 @@ it receives:
.. caution::
You might notice that a continuation cannot determine if more events are
- "in flight" toward it. Do not use ``TSContDestroy`` to delete a
+ "in flight" toward it. Do not use :func:`TSContDestroy` to delete a
continuation before you make sure that all incoming events, such as
- those sent because of ``TSHttpTxnHookAdd``, have been handled.
+ those sent because of :func:`TSHttpTxnHookAdd`, have been handled.
The following table lists events and the corresponding type of
-``void* data`` passed to handler functions:
+`void* data` passed to handler functions:
-======================================== ======================================= ======================
-Event Event Sender Data Type
-======================================== ======================================= ======================
-``TS_EVENT_HTTP_READ_REQUEST_HDR`` ``TS_HTTP_READ_REQUEST_HDR_HOOK`` ``TSHttpTxn``
-``TS_EVENT_HTTP_OS_DNS`` ``TS_HTTP_OS_DNS_HOOK`` ``TSHttpTxn``
-``TS_EVENT_HTTP_SEND_REQUEST_HDR`` ``TS_HTTP_SEND_REQUEST_HDR_HOOK`` ``TSHttpTxn``
-``TS_EVENT_HTTP_READ_CACHE_HDR`` ``TS_HTTP_READ_CACHE_HDR_HOOK`` ``TSHttpTxn``
-``TS_EVENT_HTTP_READ_RESPONSE_HDR`` ``TS_HTTP_READ_RESPONSE_HDR_HOOK`` ``TSHttpTxn``
-``TS_EVENT_HTTP_SEND_RESPONSE_HDR`` ``TS_HTTP_SEND_RESPONSE_HDR_HOOK`` ``TSHttpTxn``
-``TS_EVENT_HTTP_SELECT_ALT`` ``TS_HTTP_SELECT_ALT_HOOK`` ``TSHttpTxn``
-``TS_EVENT_HTTP_TXN_START`` ``TS_HTTP_TXN_START_HOOK`` ``TSHttpTxn``
-``TS_EVENT_HTTP_TXN_CLOSE`` ``TS_HTTP_TXN_CLOSE_HOOK`` ``TSHttpTxn``
-``TS_EVENT_HTTP_SSN_START`` ``TS_HTTP_SSN_START_HOOK`` ``TSHttpSsn``
-``TS_EVENT_HTTP_SSN_CLOSE`` ``TS_HTTP_SSN_CLOSE_HOOK`` ``TSHttpSsn``
-``TS_EVENT_NONE``
-``TS_EVENT_CACHE_LOOKUP_COMPLETE`` ``TS_HTTP_CACHE_LOOKUP_COMPLETE_HOOK`` ``TSHttpTxn``
-``TS_EVENT_IMMEDIATE`` ``TSVConnClose``
- ``TSVIOReenable``
- ``TSContSchedule``
-``TS_EVENT_IMMEDIATE`` ``TS_HTTP_REQUEST_TRANSFORM_HOOK``
-``TS_EVENT_IMMEDIATE`` ``TS_HTTP_RESPONSE_TRANSFORM_HOOK``
-``TS_EVENT_CACHE_OPEN_READ`` ``TSCacheRead`` Cache VC
-``TS_EVENT_CACHE_OPEN_READ_FAILED`` ``TSCacheRead`` TS_CACHE_ERROR code
-``TS_EVENT_CACHE_OPEN_WRITE`` ``TSCacheWrite`` Cache VC
-``TS_EVENT_CACHE_OPEN_WRITE_FAILED`` ``TSCacheWrite`` TS_CACHE_ERROR code
-``TS_EVENT_CACHE_REMOVE`` ``TSCacheRemove``
-``TS_EVENT_CACHE_REMOVE_FAILED`` ``TSCacheRemove`` TS_CACHE_ERROR code
-``TS_EVENT_NET_ACCEPT`` ``TSNetAccept`` ``NetVConnection``
- ``TSHttpTxnServerIntercept``
- ``TSHttpTxnIntercept``
-``TS_EVENT_NET_ACCEPT_FAILED`` ``TSNetAccept``
- ``TSHttpTxnServerIntercept``
- ``TSHttpTxnIntercept``
-``TS_EVENT_HOST_LOOKUP`` ``TSHostLookup`` ``TSHostLookupResult``
-``TS_EVENT_TIMEOUT`` ``TSContSchedule``
-``TS_EVENT_ERROR``
-``TS_EVENT_VCONN_READ_READY`` ``TSVConnRead`` ``TSVIO``
-``TS_EVENT_VCONN_WRITE_READY`` ``TSVConnWrite`` ``TSVIO``
-``TS_EVENT_VCONN_READ_COMPLETE`` ``TSVConnRead`` ``TSVIO``
-``TS_EVENT_VCONN_WRITE_COMPLETE`` ``TSVConnWrite`` ``TSVIO``
-``TS_EVENT_VCONN_EOS`` ``TSVConnRead`` ``TSVIO``
-``TS_EVENT_NET_CONNECT`` ``TSNetConnect`` ``TSVConn``
-``TS_EVENT_NET_CONNECT_FAILED`` ``TSNetConnect`` ``TSVConn``
-``TS_EVENT_HTTP_CONTINUE``
-``TS_EVENT_HTTP_ERROR``
-``TS_EVENT_MGMT_UPDATE`` ``TSMgmtUpdateRegister``
-======================================== ======================================= ======================
+============================================ =========================================== ==========================
+Event Event Sender Data Type
+============================================ =========================================== ==========================
+:data:`TS_EVENT_HTTP_READ_REQUEST_HDR` :data:`TS_HTTP_READ_REQUEST_HDR_HOOK` :type:`TSHttpTxn`
+:data:`TS_EVENT_HTTP_OS_DNS` :data:`TS_HTTP_OS_DNS_HOOK` :type:`TSHttpTxn`
+:data:`TS_EVENT_HTTP_SEND_REQUEST_HDR` :data:`TS_HTTP_SEND_REQUEST_HDR_HOOK` :type:`TSHttpTxn`
+:data:`TS_EVENT_HTTP_READ_CACHE_HDR` :data:`TS_HTTP_READ_CACHE_HDR_HOOK` :type:`TSHttpTxn`
+:data:`TS_EVENT_HTTP_READ_RESPONSE_HDR` :data:`TS_HTTP_READ_RESPONSE_HDR_HOOK` :type:`TSHttpTxn`
+:data:`TS_EVENT_HTTP_SEND_RESPONSE_HDR` :data:`TS_HTTP_SEND_RESPONSE_HDR_HOOK` :type:`TSHttpTxn`
+:data:`TS_EVENT_HTTP_SELECT_ALT` :data:`TS_HTTP_SELECT_ALT_HOOK` :type:`TSHttpTxn`
+:data:`TS_EVENT_HTTP_TXN_START` :data:`TS_HTTP_TXN_START_HOOK` :type:`TSHttpTxn`
+:data:`TS_EVENT_HTTP_TXN_CLOSE` :data:`TS_HTTP_TXN_CLOSE_HOOK` :type:`TSHttpTxn`
+:data:`TS_EVENT_HTTP_SSN_START` :data:`TS_HTTP_SSN_START_HOOK` :type:`TSHttpSsn`
+:data:`TS_EVENT_HTTP_SSN_CLOSE` :data:`TS_HTTP_SSN_CLOSE_HOOK` :type:`TSHttpSsn`
+:data:`TS_EVENT_NONE`
+:data:`TS_EVENT_CACHE_LOOKUP_COMPLETE` :data:`TS_HTTP_CACHE_LOOKUP_COMPLETE_HOOK` :type:`TSHttpTxn`
+:data:`TS_EVENT_IMMEDIATE` :func:`TSVConnClose`
+ :func:`TSVIOReenable`
+ :func:`TSContSchedule`
+:data:`TS_EVENT_IMMEDIATE` :data:`TS_HTTP_REQUEST_TRANSFORM_HOOK`
+:data:`TS_EVENT_IMMEDIATE` :data:`TS_HTTP_RESPONSE_TRANSFORM_HOOK`
+:data:`TS_EVENT_CACHE_OPEN_READ` :func:`TSCacheRead` Cache VC
+:data:`TS_EVENT_CACHE_OPEN_READ_FAILED` :func:`TSCacheRead` TS_CACHE_ERROR code
+:data:`TS_EVENT_CACHE_OPEN_WRITE` :func:`TSCacheWrite` Cache VC
+:data:`TS_EVENT_CACHE_OPEN_WRITE_FAILED` :func:`TSCacheWrite` TS_CACHE_ERROR code
+:data:`TS_EVENT_CACHE_REMOVE` :func:`TSCacheRemove`
+:data:`TS_EVENT_CACHE_REMOVE_FAILED` :func:`TSCacheRemove` TS_CACHE_ERROR code
+:data:`TS_EVENT_NET_ACCEPT` :func:`TSNetAccept` :type:`TSNetVConnection`
+ :func:`TSHttpTxnServerIntercept`
+ :func:`TSHttpTxnIntercept`
+:data:`TS_EVENT_NET_ACCEPT_FAILED` :func:`TSNetAccept`
+ :func:`TSHttpTxnServerIntercept`
+ :func:`TSHttpTxnIntercept`
+:data:`TS_EVENT_HOST_LOOKUP` :func:`TSHostLookup` :type:`TSHostLookupResult`
+:data:`TS_EVENT_TIMEOUT` :func:`TSContSchedule`
+:data:`TS_EVENT_ERROR`
+:data:`TS_EVENT_VCONN_READ_READY` :func:`TSVConnRead` :type:`TSVIO`
+:data:`TS_EVENT_VCONN_WRITE_READY` :func:`TSVConnWrite` :type:`TSVIO`
+:data:`TS_EVENT_VCONN_READ_COMPLETE` :func:`TSVConnRead` :type:`TSVIO`
+:data:`TS_EVENT_VCONN_WRITE_COMPLETE` :func:`TSVConnWrite` :type:`TSVIO`
+:data:`TS_EVENT_VCONN_EOS` :func:`TSVConnRead` :type:`TSVIO`
+:data:`TS_EVENT_NET_CONNECT` :func:`TSNetConnect` :type:`TSVConn`
+:data:`TS_EVENT_NET_CONNECT_FAILED` :func:`TSNetConnect` :type:`TSVConn`
+:data:`TS_EVENT_HTTP_CONTINUE`
+:data:`TS_EVENT_HTTP_ERROR`
+:data:`TS_EVENT_MGMT_UPDATE` :func:`TSMgmtUpdateRegister`
+============================================ =========================================== ==========================
The continuation functions are listed below:
-- ``TSContCall``
-- ``TSContCreate``
-- ``TSContDataGet``
-- ``TSContDataSet``
-- ``TSContDestroy``
-- ``TSContMutexGet``
-- ``TSContSchedule``
+- :func:`TSContCall`
+- :func:`TSContCreate`
+- :func:`TSContDataGet`
+- :func:`TSContDataSet`
+- :func:`TSContDestroy`
+- :func:`TSContMutexGet`
+- :func:`TSContSchedule`
http://git-wip-us.apache.org/repos/asf/trafficserver/blob/eb183eef/example/Makefile.am
----------------------------------------------------------------------
diff --git a/example/Makefile.am b/example/Makefile.am
index c459991..c57fbea 100644
--- a/example/Makefile.am
+++ b/example/Makefile.am
@@ -28,6 +28,7 @@ plugins = \
cache-scan.la \
file-1.la \
hello.la \
+ intercept.la \
lifecycle-plugin.la \
null-transform.la \
output-header.la \
@@ -57,6 +58,7 @@ bnull_transform_la_SOURCES = bnull-transform/bnull-transform.c
cache_scan_la_SOURCES = cache-scan/cache-scan.cc
file_1_la_SOURCES = file-1/file-1.c
hello_la_SOURCES = hello/hello.c
+intercept_la_SOURCES = intercept/intercept.cc
lifecycle_plugin_la_SOURCES = lifecycle-plugin/lifecycle-plugin.c
null_transform_la_SOURCES = null-transform/null-transform.c
output_header_la_SOURCES = output-header/output-header.c
http://git-wip-us.apache.org/repos/asf/trafficserver/blob/eb183eef/example/intercept/intercept.cc
----------------------------------------------------------------------
diff --git a/example/intercept/intercept.cc b/example/intercept/intercept.cc
new file mode 100644
index 0000000..b6eda22
--- /dev/null
+++ b/example/intercept/intercept.cc
@@ -0,0 +1,576 @@
+/** @file
+
+ an example hello world plugin
+
+ @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 __STDC_LIMIT_MACROS
+#define __STDC_LIMIT_MACROS 1
+#endif
+
+#include <ts/ts.h>
+#include <stdlib.h>
+#include <inttypes.h>
+#include <string.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+
+// intercept plugin
+//
+// This plugin primarily demonstrates the use of server interceptions to allow a
+// plugin to act as an origin server. It also demonstrates how to use
+// TSNetConnect to make a TCP connection to another server, and the how to use
+// the TSVConn APIs to transfer data between virtual connections.
+//
+// This plugin intercepts all cache misses and proxies them to a separate server
+// that is assumed to be running on localhost:60000. The plugin does no HTTP
+// processing at all, it simply shuffles data until the client closes the
+// request. The TSQA test test-server-intercept exercises this plugin. You can
+// enable extensive logging with the "intercept" diagnostic tag.
+
+
+#define PLUGIN "intercept"
+#define PORT 60000
+
+#define VDEBUG(fmt, ...) TSDebug(PLUGIN, fmt, ##__VA_ARGS__)
+
+#if DEBUG
+#define VERROR(fmt, ...) TSDebug(PLUGIN, fmt, ##__VA_ARGS__)
+#else
+#define VERROR(fmt, ...) TSError("[%s] %s: " fmt, PLUGIN, __FUNCTION__, ##__VA_ARGS__)
+#endif
+
+#define VIODEBUG(vio, fmt, ...) VDEBUG("vio=%p vio.cont=%p, vio.cont.data=%p, vio.vc=%p " fmt, \
+ (vio), TSVIOContGet(vio), TSContDataGet(TSVIOContGet(vio)), TSVIOVConnGet(vio), ##__VA_ARGS__)
+
+static TSCont TxnHook;
+static TSCont InterceptHook;
+
+static int InterceptInterceptionHook(TSCont contp, TSEvent event, void * edata);
+static int InterceptTxnHook(TSCont contp, TSEvent event, void * edata);
+
+// We are going to stream data between Traffic Server and an
+// external server. This structure represents the state of a
+// streaming I/O request. Is is directional (ie. either a read or
+// a write). We need two of these for each TSVConn; one to push
+// data into the TSVConn and one to pull data out.
+struct InterceptIOChannel
+{
+ TSVIO vio;
+ TSIOBuffer iobuf;
+ TSIOBufferReader reader;
+
+ InterceptIOChannel() : vio(NULL), iobuf(NULL), reader(NULL) {
+ }
+
+ ~InterceptIOChannel() {
+ if (this->reader) {
+ TSIOBufferReaderFree(this->reader);
+ }
+
+ if (this->iobuf) {
+ TSIOBufferDestroy(this->iobuf);
+ }
+ }
+
+ void read(TSVConn vc, TSCont contp) {
+ TSReleaseAssert(this->vio == NULL);
+ TSReleaseAssert((this->iobuf = TSIOBufferCreate()));
+ TSReleaseAssert((this->reader = TSIOBufferReaderAlloc(this->iobuf)));
+
+ this->vio = TSVConnRead(vc, contp, this->iobuf, INT64_MAX);
+ }
+
+ void write(TSVConn vc, TSCont contp) {
+ TSReleaseAssert(this->vio == NULL);
+ TSReleaseAssert((this->iobuf = TSIOBufferCreate()));
+ TSReleaseAssert((this->reader = TSIOBufferReaderAlloc(this->iobuf)));
+
+ this->vio = TSVConnWrite(vc, contp, this->reader, INT64_MAX);
+ }
+
+};
+
+// A simple encapsulation of the IO state of a TSVConn. We need the TSVConn itself, and the
+// IO metadata for the read side and the write side.
+struct InterceptIO
+{
+ TSVConn vc;
+ InterceptIOChannel readio;
+ InterceptIOChannel writeio;
+
+ void close() {
+ if (this->vc) {
+ TSVConnClose(this->vc);
+ }
+ this->vc = NULL;
+ this->readio.vio = this->writeio.vio = NULL;
+ }
+};
+
+// Interception proxy state block. From our perspective, Traffic
+// Server is the client, and the origin server on whose behalf we
+// are intercepting is the server. Hence the "client" and
+// "server" nomenclature here.
+struct InterceptState
+{
+ TSHttpTxn txn; // The transaction on whose behalf we are intercepting.
+
+ InterceptIO client; // Server intercept VC state.
+ InterceptIO server; // Intercept origin VC state.
+
+ InterceptState() : txn(NULL) {
+ }
+
+ ~InterceptState() {
+ }
+};
+
+// Return the InterceptIO control block that owns the given VC.
+static InterceptIO *
+InterceptGetThisSide(InterceptState * istate, TSVConn vc)
+{
+ return (istate->client.vc == vc) ? &istate->client : &istate->server;
+}
+
+// Return the InterceptIO control block that doesn't own the given VC.
+static InterceptIO *
+InterceptGetOtherSide(InterceptState * istate, TSVConn vc)
+{
+ return (istate->client.vc == vc) ? &istate->server : &istate->client;
+}
+
+// Evaluates to a human-readable name for a TSVConn in the
+// intercept proxy state.
+static const char *
+InterceptProxySide(const InterceptState * istate, const InterceptIO * io)
+{
+ return (io == &istate->client) ? "<client>"
+ : (io == &istate->server) ? "<server>"
+ : "<unknown>";
+}
+
+static const char *
+InterceptProxySideVC(const InterceptState * istate, TSVConn vc)
+{
+ return (istate->client.vc && vc == istate->client.vc) ? "<client>"
+ : (istate->server.vc && vc == istate->server.vc) ? "<server>"
+ : "<unknown>";
+}
+
+static bool
+InterceptAttemptDestroy(InterceptState * istate, TSCont contp)
+{
+ if (istate->server.vc == NULL && istate->client.vc == NULL) {
+ VDEBUG("destroying server intercept state istate=%p contp=%p", istate, contp);
+ TSContDataSet(contp, NULL); // Force a crash if we get additional events.
+ TSContDestroy(contp);
+ delete istate;
+ return true;
+ }
+
+ return false;
+}
+
+union socket_type {
+ struct sockaddr_storage storage;
+ struct sockaddr sa;
+ struct sockaddr_in sin;
+ struct sockaddr_in6 sin6;
+};
+
+union argument_type {
+ void * ptr;
+ intptr_t ecode;
+ TSVConn vc;
+ TSVIO vio;
+ TSHttpTxn txn;
+ InterceptState * istate;
+
+ argument_type(void * _p) : ptr(_p) {}
+};
+
+static TSCont
+InterceptContCreate(TSEventFunc hook, TSMutex mutexp, void * data)
+{
+ TSCont contp;
+
+ TSReleaseAssert((contp = TSContCreate(hook, mutexp)));
+ TSContDataSet(contp, data);
+ return contp;
+}
+
+static bool
+InterceptShouldInterceptRequest(TSHttpTxn txn)
+{
+ // Normally, this function would inspect the request and
+ // determine whether it should be intercepted. We might examine
+ // the URL path, or some headers. For the sake of this example,
+ // we will intercept everything that is not a cache hit.
+
+ int status;
+ TSReleaseAssert(TSHttpTxnCacheLookupStatusGet(txn, &status) == TS_SUCCESS);
+ return status != TS_CACHE_LOOKUP_HIT_FRESH;
+}
+
+// This function is called in response to a READ_READY event. We
+// should transfer any data we find from one side of the transfer
+// to the other.
+static int64_t
+InterceptTransferData(InterceptIO * from, InterceptIO * to)
+{
+ TSIOBufferBlock block;
+ int64_t consumed = 0;
+
+ // Walk the list of buffer blocks in from the read VIO.
+ for (block = TSIOBufferReaderStart(from->readio.reader); block; block = TSIOBufferBlockNext(block)) {
+ int64_t remain = 0;
+ const char * ptr;
+
+ VDEBUG("attempting to transfer %" PRId64 " available bytes", TSIOBufferBlockReadAvail(block, from->readio.reader));
+
+ // Take the data from each buffer block, and write it into
+ // the buffer of the write VIO.
+ ptr = TSIOBufferBlockReadStart(block, from->readio.reader, &remain);
+ while (ptr && remain) {
+ int64_t nbytes;
+
+ nbytes = TSIOBufferWrite(to->writeio.iobuf, ptr, remain);
+ remain -= nbytes;
+ ptr += nbytes;
+ consumed += nbytes;
+ }
+ }
+
+ VDEBUG("consumed %" PRId64 " bytes reading from vc=%p, writing to vc=%p", consumed, from->vc, to->vc);
+ if (consumed) {
+ TSIOBufferReaderConsume(from->readio.reader, consumed);
+ // Note that we don't have to call TSIOBufferProduce here.
+ // This is because data passed into TSIOBufferWrite is
+ // automatically "produced".
+ }
+
+ return consumed;
+}
+
+// Handle events from TSHttpTxnServerIntercept. The intercept
+// starts with TS_EVENT_NET_ACCEPT, and then continues with
+// TSVConn events.
+static int
+InterceptInterceptionHook(TSCont contp, TSEvent event, void * edata)
+{
+ argument_type arg(edata);
+
+ VDEBUG("contp=%p, event=%s (%d), edata=%p", contp, TSHttpEventNameLookup(event), event, arg.ptr);
+
+ switch (event) {
+ case TS_EVENT_NET_ACCEPT: {
+ // Set up the server intercept. We have the original
+ // TSHttpTxn from the continuation. We need to connect to the
+ // real origin and get ready to shuffle data around.
+ TSAction action;
+ char buf[INET_ADDRSTRLEN];
+ socket_type addr;
+ argument_type cdata(TSContDataGet(contp));
+ InterceptState * istate = new InterceptState();
+
+ istate->txn = cdata.txn;
+ istate->client.vc = arg.vc;
+
+ // This event is delivered by the continuation that we
+ // attached in InterceptTxnHook, so the continuation data is
+ // the TSHttpTxn pointer.
+ VDEBUG("allocated server intercept state istate=%p for txn=%p", istate, cdata.txn);
+
+ // Set up a connection to our real origin, which will be
+ // 127.0.0.1:$PORT.
+ memset(&addr, 0, sizeof(addr));
+ addr.sin.sin_family = AF_INET;
+ addr.sin.sin_addr.s_addr = htonl(INADDR_LOOPBACK); // XXX config option
+ addr.sin.sin_port = htons(PORT); // XXX config option
+
+ VDEBUG("binding client vc=%p to %s:%u",
+ istate->client.vc, inet_ntop(AF_INET, &addr.sin.sin_addr, buf, sizeof(buf)), (unsigned)ntohs(addr.sin.sin_port));
+
+ // Reset the continuation data to be our intercept state
+ // block. We will need this so that we can access both of the
+ // VCs at the same time. We need to do this before calling
+ // TSNetConnect so that we can handle the failure case.
+ TSContDataSet(contp, istate);
+
+ action = TSNetConnect(contp, &addr.sa);
+ if (TSActionDone(action)) {
+ VDEBUG("origin connection was done inline");
+ }
+
+ // We should not do anything after the TSNetConnect call. The
+ // NET_CONNECT events take care of everything and we don't
+ // want to risk referencing any stale data here.
+
+ return TS_EVENT_NONE;
+ }
+
+ case TS_EVENT_NET_ACCEPT_FAILED: {
+ // TS_EVENT_NET_ACCEPT_FAILED will be delivered if the
+ // transaction is cancelled before we start tunnelling
+ // through the server intercept. One way that this can happen
+ // is if the intercept is attached early, and then we server
+ // the document out of cache.
+ argument_type cdata(TSContDataGet(contp));
+
+ // There's nothing to do here except nuke the continuation
+ // that was allocated in InterceptTxnHook().
+ VDEBUG("cancelling server intercept request for txn=%p", cdata.txn);
+
+ TSContDestroy(contp);
+ return TS_EVENT_NONE;
+ }
+
+ case TS_EVENT_NET_CONNECT: {
+ // TSNetConnect is asynchronous, so all we know right now is
+ // that the connect was scheduled. We need to kick off some
+ // I/O in order to start proxying data between the Traffic
+ // Server core and our intercepted origin server. The server
+ // intercept will begin the process by sending a read
+ // request.
+ argument_type cdata(TSContDataGet(contp));
+
+ VDEBUG("connected to intercepted origin server, binding server vc=%p to istate=%p", arg.vc, cdata.istate);
+
+ TSReleaseAssert(cdata.istate->client.vc != NULL);
+ cdata.istate->server.vc = arg.vc;
+
+ // Start reading the request from the server intercept VC.
+ cdata.istate->client.readio.read(cdata.istate->client.vc, contp);
+ VIODEBUG(cdata.istate->client.readio.vio, "started %s read", InterceptProxySide(cdata.istate, &cdata.istate->client));
+
+ // Start reading the response from the intercepted origin server VC.
+ cdata.istate->server.readio.read(cdata.istate->server.vc, contp);
+ VIODEBUG(cdata.istate->server.readio.vio, "started %s read", InterceptProxySide(cdata.istate, &cdata.istate->server));
+
+ // Start writing the response to the server intercept VC.
+ cdata.istate->client.writeio.write(cdata.istate->client.vc, contp);
+ VIODEBUG(cdata.istate->client.writeio.vio, "started %s write", InterceptProxySide(cdata.istate, &cdata.istate->client));
+
+ // Start writing the request to the intercepted origin server VC.
+ cdata.istate->server.writeio.write(cdata.istate->server.vc, contp);
+ VIODEBUG(cdata.istate->server.writeio.vio, "started %s write", InterceptProxySide(cdata.istate, &cdata.istate->server));
+
+ return TS_EVENT_NONE;
+ }
+
+ case TS_EVENT_NET_CONNECT_FAILED: {
+ // Connecting to the intercepted origin failed. We should
+ // pass the error on up to the server intercept.
+ argument_type cdata(TSContDataGet(contp));
+
+ VDEBUG("origin connection for txn=%p failed with error code %ld", cdata.istate->txn, arg.ecode);
+
+ TSReleaseAssert(cdata.istate->client.vc != NULL);
+ TSReleaseAssert(cdata.istate->server.vc == NULL);
+
+ // We failed to connect to the intercepted origin. Abort the
+ // server intercept since we cannot handle it.
+ TSVConnAbort(cdata.istate->client.vc, arg.ecode);
+
+ delete cdata.istate;
+ TSContDestroy(contp);
+
+ return TS_EVENT_NONE;
+ }
+
+ case TS_EVENT_VCONN_READ_READY: {
+ argument_type cdata = TSContDataGet(contp);
+ TSVConn vc = TSVIOVConnGet(arg.vio);
+ InterceptIO * from = InterceptGetThisSide(cdata.istate, vc);
+ InterceptIO * to = InterceptGetOtherSide(cdata.istate, vc);;
+ int64_t nbytes;
+
+ VIODEBUG(arg.vio, "ndone=%" PRId64 " ntodo=%" PRId64, TSVIONDoneGet(arg.vio), TSVIONTodoGet(arg.vio));
+ VDEBUG("reading vio=%p vc=%p, istate=%p is bound to client vc=%p and server vc=%p",
+ arg.vio, TSVIOVConnGet(arg.vio), cdata.istate, cdata.istate->client.vc, cdata.istate->server.vc);
+
+ if (to->vc == NULL) {
+ VDEBUG("closing %s vc=%p", InterceptProxySide(cdata.istate, from), from->vc);
+ from->close();
+ }
+
+ if (from->vc == NULL) {
+ VDEBUG("closing %s vc=%p", InterceptProxySide(cdata.istate, to), to->vc);
+ to->close();
+ }
+
+ if (InterceptAttemptDestroy(cdata.istate, contp)) {
+ return TS_EVENT_NONE;
+ }
+
+ VDEBUG("reading from %s (vc=%p), writing to %s (vc=%p)",
+ InterceptProxySide(cdata.istate, from), from->vc, InterceptProxySide(cdata.istate, to), to->vc);
+
+ nbytes = InterceptTransferData(from, to);
+
+ // Reenable the read VIO to get more events.
+ if (nbytes) {
+ TSVIO writeio = to->writeio.vio;
+ VIODEBUG(writeio, "WRITE VIO ndone=%" PRId64 " ntodo=%" PRId64, TSVIONDoneGet(writeio), TSVIONTodoGet(writeio));
+ TSVIOReenable(from->readio.vio); // Re-enable the read side.
+ TSVIOReenable(to->writeio.vio); // Reenable the write side.
+ }
+
+ return TS_EVENT_NONE;
+ }
+
+ case TS_EVENT_VCONN_WRITE_READY: {
+ // WRITE_READY events happen all the time, when the TSVConn
+ // buffer drains. There's no need to do anything with these
+ // because we only fill the buffer when we have data to read.
+ // The exception is where one side of the proxied connection
+ // has been closed. Then we want to close the other side.
+ argument_type cdata = TSContDataGet(contp);
+ TSVConn vc = TSVIOVConnGet(arg.vio);
+ InterceptIO * to = InterceptGetThisSide(cdata.istate, vc);
+ InterceptIO * from = InterceptGetOtherSide(cdata.istate, vc);;
+
+ // If the other side is closed, close this side too, but only if there
+ // we have drained the write buffer.
+ if (from->vc == NULL) {
+ VDEBUG("closing %s vc=%p with %" PRId64 " bytes to left",
+ InterceptProxySide(cdata.istate, to), to->vc, TSIOBufferReaderAvail(to->writeio.reader));
+ if (TSIOBufferReaderAvail(to->writeio.reader) == 0) {
+ to->close();
+ }
+ }
+
+ InterceptAttemptDestroy(cdata.istate, contp);
+ return TS_EVENT_NONE;
+ }
+
+ case TS_EVENT_ERROR:
+ case TS_EVENT_VCONN_EOS: {
+ // If we get an EOS on one side, we should just send and EOS
+ // on the other side too. The server intercept will always
+ // send us an EOS after Traffic Server has finished reading
+ // the response. Once that happens, we are also finished with
+ // the intercepted origin server. The same reasoning applies
+ // to receiving EOS from the intercepted origin server, and
+ // when handling errors.
+
+ TSVConn vc = TSVIOVConnGet(arg.vio);
+ argument_type cdata = TSContDataGet(contp);
+
+ InterceptIO * from = InterceptGetThisSide(cdata.istate, vc);
+ InterceptIO * to = InterceptGetOtherSide(cdata.istate, vc);;
+
+ VIODEBUG(arg.vio, "received EOS or ERROR from %s side", InterceptProxySideVC(cdata.istate, vc));
+
+ // Close the side that we received the EOS event from.
+ if (from) {
+ VDEBUG("%s writeio has %" PRId64 " bytes left",
+ InterceptProxySide(cdata.istate, from), TSIOBufferReaderAvail(from->writeio.reader));
+ from->close();
+ }
+
+ // Should we also close the other side? Well, that depends on whether the reader
+ // has drained the data. If we close too early they will see a truncated read.
+ if (to) {
+ VDEBUG("%s writeio has %" PRId64 " bytes left",
+ InterceptProxySide(cdata.istate, to), TSIOBufferReaderAvail(to->writeio.reader));
+ if (TSIOBufferReaderAvail(to->writeio.reader) == 0) {
+ to->close();
+ }
+ }
+
+ InterceptAttemptDestroy(cdata.istate, contp);
+ return event == TS_EVENT_ERROR ? TS_EVENT_ERROR : TS_EVENT_NONE;
+ }
+
+ case TS_EVENT_VCONN_READ_COMPLETE:
+ // We read data forever, so we should never get a
+ // READ_COMPLETE.
+ VIODEBUG(arg.vio, "unexpected TS_EVENT_VCONN_READ_COMPLETE");
+ return TS_EVENT_NONE;
+
+ case TS_EVENT_VCONN_WRITE_COMPLETE:
+ // We write data forever, so we should never get a
+ // WRITE_COMPLETE.
+ VIODEBUG(arg.vio, "unexpected TS_EVENT_VCONN_WRITE_COMPLETE");
+ return TS_EVENT_NONE;
+
+ case TS_EVENT_VCONN_INACTIVITY_TIMEOUT:
+ VERROR("unexpected event %s (%d) edata=%p", TSHttpEventNameLookup(event), event, arg.ptr);
+ return TS_EVENT_ERROR;
+
+ default:
+ VERROR("unexpected event %s (%d) edata=%p", TSHttpEventNameLookup(event), event, arg.ptr);
+ return TS_EVENT_ERROR;
+ }
+}
+
+// Handle events that occur on the TSHttpTxn.
+static int
+InterceptTxnHook(TSCont contp, TSEvent event, void * edata)
+{
+ argument_type arg(edata);
+
+ VDEBUG("contp=%p, event=%s (%d), edata=%p", contp, TSHttpEventNameLookup(event), event, arg.ptr);
+
+ switch (event) {
+ case TS_EVENT_HTTP_CACHE_LOOKUP_COMPLETE: {
+ if (InterceptShouldInterceptRequest(arg.txn)) {
+ TSCont c = InterceptContCreate(InterceptInterceptionHook, TSMutexCreate(), arg.txn);
+
+ VDEBUG("intercepting orgin server request for txn=%p, cont=%p", arg.txn, c);
+ TSHttpTxnServerIntercept(c, arg.txn);
+ }
+
+ break;
+ }
+
+ default:
+ VERROR("unexpected event %s (%d)", TSHttpEventNameLookup(event), event);
+ break;
+ }
+
+ TSHttpTxnReenable(arg.txn, TS_EVENT_HTTP_CONTINUE);
+ return TS_EVENT_NONE;
+}
+
+void
+TSPluginInit(int /* argc */, const char * /* argv */ [])
+{
+ TSPluginRegistrationInfo info;
+
+ info.plugin_name = (char *)PLUGIN;
+ info.vendor_name = (char *)"MyCompany";
+ info.support_email = (char *)"ts-api-support@MyCompany.com";
+
+ if (TSPluginRegister(TS_SDK_VERSION_3_0, &info) != TS_SUCCESS) {
+ VERROR("plugin registration failed\n");
+ }
+
+ // XXX accept hostname and port arguments
+
+ TxnHook = InterceptContCreate(InterceptTxnHook, NULL, NULL);
+ InterceptHook = InterceptContCreate(InterceptInterceptionHook, NULL, NULL);
+
+ // Wait until after the cache lookup to decide whether to
+ // intercept a request. For cache hits we will never intercept.
+ TSHttpHookAdd(TS_HTTP_CACHE_LOOKUP_COMPLETE_HOOK, TxnHook);
+}