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);
+}