trafficserver-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From jpe...@apache.org
Subject trafficserver git commit: TS-3239: add a new generator plugin
Date Tue, 16 Dec 2014 00:50:28 GMT
Repository: trafficserver
Updated Branches:
  refs/heads/master f2fc28902 -> 566719961


TS-3239: add a new generator plugin

The generator plugin is a server intercept that can generate HTTP
responses according to the specification given in the request URL.


Project: http://git-wip-us.apache.org/repos/asf/trafficserver/repo
Commit: http://git-wip-us.apache.org/repos/asf/trafficserver/commit/56671996
Tree: http://git-wip-us.apache.org/repos/asf/trafficserver/tree/56671996
Diff: http://git-wip-us.apache.org/repos/asf/trafficserver/diff/56671996

Branch: refs/heads/master
Commit: 566719961ba5f5cc6fc68f57a8fd19c296301ede
Parents: f2fc289
Author: James Peach <jpeach@apache.org>
Authored: Thu Dec 11 13:26:52 2014 -0800
Committer: James Peach <jpeach@apache.org>
Committed: Mon Dec 15 16:49:20 2014 -0800

----------------------------------------------------------------------
 CHANGES                                     |   2 +
 configure.ac                                |   5 +-
 doc/reference/plugins/generator.en.rst      |  69 +++
 doc/reference/plugins/index.en.rst          |  10 +-
 plugins/experimental/Makefile.am            |   1 +
 plugins/experimental/generator/Makefile.am  |  22 +
 plugins/experimental/generator/generator.cc | 622 +++++++++++++++++++++++
 7 files changed, 724 insertions(+), 7 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/trafficserver/blob/56671996/CHANGES
----------------------------------------------------------------------
diff --git a/CHANGES b/CHANGES
index 318e9fd..4c8c891 100644
--- a/CHANGES
+++ b/CHANGES
@@ -1,6 +1,8 @@
                                                          -*- coding: utf-8 -*-
 Changes with Apache Traffic Server 5.3.0
 
+  *) [TS-3239] Add a new `generator` plugin.
+
   *) [TS-3238] Stop referencing the global _res symbol.
 
   *) [TS-3237] Change HostDB to not segregate DNS results by port.

http://git-wip-us.apache.org/repos/asf/trafficserver/blob/56671996/configure.ac
----------------------------------------------------------------------
diff --git a/configure.ac b/configure.ac
index 845b023..1738613 100644
--- a/configure.ac
+++ b/configure.ac
@@ -1925,7 +1925,6 @@ AM_CONDITIONAL([HAS_MYSQL], [ test "x${has_mysql}" = "x1" ])
 
 AS_IF([test "x$enable_experimental_plugins" = xyes], [  
   AC_CONFIG_FILES([
-    plugins/experimental/mysql_remap/Makefile
     plugins/experimental/Makefile
     plugins/experimental/authproxy/Makefile
     plugins/experimental/background_fetch/Makefile
@@ -1937,16 +1936,18 @@ AS_IF([test "x$enable_experimental_plugins" = xyes], [
     plugins/experimental/epic/Makefile
     plugins/experimental/escalate/Makefile
     plugins/experimental/esi/Makefile
+    plugins/experimental/generator/Makefile
     plugins/experimental/geoip_acl/Makefile
     plugins/experimental/header_normalize/Makefile
     plugins/experimental/healthchecks/Makefile
     plugins/experimental/hipes/Makefile
     plugins/experimental/metalink/Makefile
+    plugins/experimental/mysql_remap/Makefile
     plugins/experimental/regex_revalidate/Makefile
     plugins/experimental/remap_stats/Makefile
     plugins/experimental/s3_auth/Makefile
-    plugins/experimental/sslheaders/Makefile
     plugins/experimental/ssl_cert_loader/Makefile
+    plugins/experimental/sslheaders/Makefile
     plugins/experimental/stale_while_revalidate/Makefile
     plugins/experimental/ts_lua/Makefile
     plugins/experimental/url_sig/Makefile

http://git-wip-us.apache.org/repos/asf/trafficserver/blob/56671996/doc/reference/plugins/generator.en.rst
----------------------------------------------------------------------
diff --git a/doc/reference/plugins/generator.en.rst b/doc/reference/plugins/generator.en.rst
new file mode 100644
index 0000000..196645a
--- /dev/null
+++ b/doc/reference/plugins/generator.en.rst
@@ -0,0 +1,69 @@
+.. _generator-plugin:
+
+Generator Plugin
+****************
+
+.. 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.
+
+The `Generator` allows testing of synthetic workloads by generating
+HTTP responses of various sizes. The size and cacheability of the
+response is specified by the first two coomponents of the requested
+URL path. This plugin only supports the ``GET`` and ``HEAD`` HTTP
+methods.
+
+=============== ===========
+Path component  Description
+=============== ===========
+1               ``cache`` or ``nocache``. If ``cache`` is specifed, the
+                `Generator` plugin will respond with ``Cache-Control`` headers
+                marking the response as cacheable for 24 hours.
+2               Integral number of bytes to return in the response.
+=============== ===========
+
+Path components after the first 2 are ignored. This means that the
+trailing path components can be manipulated to create unique URLs
+following any covenient convention.
+
+The `Generator` plugin publishes the following metrics:
+
+  generator.response_bytes:
+    The total number of bytes emitted
+  generator.response_count:
+    The number of HTTP responses generated by the plugin
+
+Examples:
+---------
+
+The most common way to use the `Generator` plugin is to configure
+it as a remap plugin in :file:`remap.config`::
+
+  map http://workload.example.com/ http://127.0.0.1/ \
+    @plugin=generator.so
+
+Notice that although the remap target is never contacted because
+the `Generator` plugin intercepts the request and acts as the origin
+server, it must be syntactically valid and resolvable in DNS.
+
+A 10 byte, cacheable object can then be generated::
+
+  $ curl -o /dev/null -x 127.0.0.1:8080 http://workload.example.com/cache/10/caf1fc92332b3a3c8cb8b3826b6a1658
+
+The `Generator` plugin can return responses as large as you like::
+
+  $ curl -o /dev/null -x 127.0.0.1:8080 http://workload.example.com/cache/$((10 * 1024 *
1024))/$RANDOM
+

http://git-wip-us.apache.org/repos/asf/trafficserver/blob/56671996/doc/reference/plugins/index.en.rst
----------------------------------------------------------------------
diff --git a/doc/reference/plugins/index.en.rst b/doc/reference/plugins/index.en.rst
index fe8a793..f15444c 100644
--- a/doc/reference/plugins/index.en.rst
+++ b/doc/reference/plugins/index.en.rst
@@ -8,9 +8,9 @@ Plugin Reference
   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
@@ -70,12 +70,12 @@ directory of the Apache Traffic Server source tree. Experimental plugins
can be
   Combohandler Plugin: provides an intelligent way to combine multiple URLs into a single
URL, and have Apache Traffic Server combine the components into one response <combo_handler.en>
   Epic Plugin: emits Traffic Server metrics in a format that is consumed tby the Epic Network
Monitoring System <epic.en>
   ESI Plugin: implements the ESI specification <esi.en>
+  Generator Plugin: generate arbitrary response data <generator.en>
   GeoIP ACLs Plugin: denying (or allowing) requests based on the source IP geo-location <geoip_acl.en>
-  hipes.en 
+  hipes.en
   Metalink Plugin: implements the Metalink download description format in order to try not
to download the same file twice. <metalink.en>
   MySQL Remap Plugin: allows dynamic “remaps” from a database <mysql_remap.en>
   AWS S3 Authentication plugin: provides support for the Amazon S3 authentication features
<s3_auth.en>
-  stale_while_revalidate.en 
+  stale_while_revalidate.en
   ts-lua Plugin: allows plugins to be written in Lua instead of C code <ts_lua.en>
   XDebug Plugin: allows HTTP clients to debug the operation of the Traffic Server cache using
the X-Debug header <xdebug.en>
-

http://git-wip-us.apache.org/repos/asf/trafficserver/blob/56671996/plugins/experimental/Makefile.am
----------------------------------------------------------------------
diff --git a/plugins/experimental/Makefile.am b/plugins/experimental/Makefile.am
index 51b06f0..2ef296c 100644
--- a/plugins/experimental/Makefile.am
+++ b/plugins/experimental/Makefile.am
@@ -25,6 +25,7 @@ SUBDIRS = \
  epic \
  escalate \
  esi \
+ generator \
  geoip_acl \
  header_normalize \
  healthchecks \

http://git-wip-us.apache.org/repos/asf/trafficserver/blob/56671996/plugins/experimental/generator/Makefile.am
----------------------------------------------------------------------
diff --git a/plugins/experimental/generator/Makefile.am b/plugins/experimental/generator/Makefile.am
new file mode 100644
index 0000000..1a5b678
--- /dev/null
+++ b/plugins/experimental/generator/Makefile.am
@@ -0,0 +1,22 @@
+#  Licensed to the Apache Software Foundation (ASF) under one
+#  or more contributor license agreements.  See the NOTICE file
+#  distributed with this work for additional information
+#  regarding copyright ownership.  The ASF licenses this file
+#  to you under the Apache License, Version 2.0 (the
+#  "License"); you may not use this file except in compliance
+#  with the License.  You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+#  Unless required by applicable law or agreed to in writing, software
+#  distributed under the License is distributed on an "AS IS" BASIS,
+#  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+#  See the License for the specific language governing permissions and
+#  limitations under the License.
+
+include $(top_srcdir)/build/plugins.mk
+
+pkglib_LTLIBRARIES = generator.la
+generator_la_SOURCES = generator.cc
+generator_la_LDFLAGS = $(TS_PLUGIN_LDFLAGS)
+

http://git-wip-us.apache.org/repos/asf/trafficserver/blob/56671996/plugins/experimental/generator/generator.cc
----------------------------------------------------------------------
diff --git a/plugins/experimental/generator/generator.cc b/plugins/experimental/generator/generator.cc
new file mode 100644
index 0000000..26138cb
--- /dev/null
+++ b/plugins/experimental/generator/generator.cc
@@ -0,0 +1,622 @@
+/** @file
+
+  Traffic generator intercept 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.
+ */
+
+#include <ts/ts.h>
+#include <ts/remap.h>
+#include <stdlib.h>
+#include <errno.h>
+#include <inttypes.h>
+#include <string.h>
+#include <unistd.h>
+#include <time.h>
+#include <iterator>
+
+// Generator plugin
+//
+// The incoming URL must consist of 2 or more path components. The first
+// component indicates cacheability, the second the number of bytes in the
+// response body. Subsequent path components are ignored, so they can be used
+// to uniqify cache keys (assuming that caching is enabled).
+//
+// Examples,
+//
+// /cache/100/6b1e2b1fa555b52124cb4e511acbae2a
+//      -> return 100 bytes, cached
+//
+// /cache/21474836480/large/response
+//      -> return 20G bytes, cached
+//
+// TODO Add query parameter options.
+//
+// It would be pretty useful to add support for query parameters to tweak how the response
+// is handled. The following parameters seem useful:
+//
+//  - delay before sending the response headers
+//  - delay before sending the response body
+//  - turn off local caching of the response (ie. even for /cache/* URLs)
+//  - force chunked encoding by omitting Content-Length
+//  - specify the Cache-Control max-age
+//
+// We ought to scale the IO buffer size in proportion to the size of the response we are
generating.
+
+#define PLUGIN "generator"
+
+#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 uint8_t GeneratorData[32 * 1024];
+
+static int StatCountBytes = -1;
+static int StatCountResponses = -1;
+
+static int GeneratorInterceptionHook(TSCont contp, TSEvent event, void * edata);
+static int GeneratorTxnHook(TSCont contp, TSEvent event, void * edata);
+
+struct GeneratorRequest;
+
+union argument_type {
+  void *            ptr;
+  intptr_t          ecode;
+  TSVConn           vc;
+  TSVIO             vio;
+  TSHttpTxn         txn;
+  GeneratorRequest *grq;
+
+  argument_type(void * _p) : ptr(_p) {}
+};
+
+// 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 IOChannel
+{
+  TSVIO             vio;
+  TSIOBuffer        iobuf;
+  TSIOBufferReader  reader;
+
+  IOChannel() : vio(NULL), iobuf(TSIOBufferSizedCreate(TS_IOBUFFER_SIZE_INDEX_32K )), reader(TSIOBufferReaderAlloc(iobuf))
{
+  }
+
+  ~IOChannel() {
+    if (this->reader) {
+      TSIOBufferReaderFree(this->reader);
+    }
+
+    if (this->iobuf) {
+      TSIOBufferDestroy(this->iobuf);
+    }
+  }
+
+  void read(TSVConn vc, TSCont contp) {
+    this->vio = TSVConnRead(vc, contp, this->iobuf, INT64_MAX);
+  }
+
+  void write(TSVConn vc, TSCont contp) {
+    this->vio = TSVConnWrite(vc, contp, this->reader, INT64_MAX);
+  }
+
+};
+
+struct GeneratorHttpHeader
+{
+  TSMBuffer buffer;
+  TSMLoc    header;
+  TSHttpParser  parser;
+
+  GeneratorHttpHeader() {
+    this->buffer = TSMBufferCreate();
+    this->header = TSHttpHdrCreate(this->buffer);
+    this->parser = TSHttpParserCreate();
+  }
+
+  ~GeneratorHttpHeader() {
+    if (this->parser) {
+      TSHttpParserDestroy(this->parser);
+    }
+
+    TSHttpHdrDestroy(this->buffer, this->header);
+    TSHandleMLocRelease(this->buffer, TS_NULL_MLOC, this->header);
+  }
+};
+
+struct GeneratorRequest
+{
+  off_t         nbytes;     // number of bytes to generate
+  unsigned      flags;
+  IOChannel     readio;
+  IOChannel     writeio;
+  GeneratorHttpHeader rqheader;
+
+  enum {
+    CACHEABLE = 0x0001,
+    ISHEAD    = 0x0002,
+  };
+
+  GeneratorRequest() : nbytes(0), flags(0) {
+  }
+
+  ~GeneratorRequest() {
+  }
+};
+
+// Destroy a generator request, iincluding the per-txn continuation.
+static void
+GeneratorRequestDestroy(GeneratorRequest * grq, TSVIO vio, TSCont contp)
+{
+  if (vio) {
+    TSVConnClose(TSVIOVConnGet(vio));
+  }
+
+  TSContDestroy(contp);
+  delete grq;
+}
+
+static off_t
+GeneratorParseByteCount(const char * ptr, const char * end)
+{
+  off_t nbytes = 0;
+
+  for (;ptr < end; ++ptr) {
+    switch (*ptr) {
+    case '0': nbytes = nbytes * 10 + 0; break;
+    case '1': nbytes = nbytes * 10 + 1; break;
+    case '2': nbytes = nbytes * 10 + 2; break;
+    case '3': nbytes = nbytes * 10 + 3; break;
+    case '4': nbytes = nbytes * 10 + 4; break;
+    case '5': nbytes = nbytes * 10 + 5; break;
+    case '6': nbytes = nbytes * 10 + 6; break;
+    case '7': nbytes = nbytes * 10 + 7; break;
+    case '8': nbytes = nbytes * 10 + 8; break;
+    case '9': nbytes = nbytes * 10 + 9; break;
+    default:
+      return -1;
+    }
+  }
+
+  return nbytes;
+}
+
+static void
+GeneratorTimestamp(char * buf, size_t bufsz)
+{
+  time_t now = time(NULL);
+  struct tm clock;
+
+  strftime(buf, bufsz, "%a, %d %b %Y %H:%M:%S GMT", gmtime_r(&now, &clock));
+}
+
+static bool
+GeneratorWriteResponseHeader(GeneratorRequest * grq, TSVConn vc, TSCont contp)
+{
+  TSMLoc field;
+  GeneratorHttpHeader response;
+
+  TSReleaseAssert(TSHttpHdrTypeSet(response.buffer, response.header, TS_HTTP_TYPE_RESPONSE)
== TS_SUCCESS);
+  TSReleaseAssert(TSHttpHdrVersionSet(response.buffer, response.header, TS_HTTP_VERSION(1,
1)) == TS_SUCCESS);
+
+  TSReleaseAssert(TSHttpHdrStatusSet(response.buffer, response.header, TS_HTTP_STATUS_OK)
== TS_SUCCESS);
+  TSHttpHdrReasonSet(response.buffer, response.header, TSHttpHdrReasonLookup(TS_HTTP_STATUS_OK),
-1);
+
+  // Set the Content-Length header.
+  TSMimeHdrFieldCreateNamed(response.buffer, response.header, TS_MIME_FIELD_CONTENT_LENGTH,
TS_MIME_LEN_CONTENT_LENGTH,  &field);
+  TSMimeHdrFieldValueInt64Set(response.buffer, response.header, field, -1 /* idx */, grq->nbytes);
+  TSMimeHdrFieldAppend(response.buffer, response.header, field);
+  TSHandleMLocRelease(response.buffer, response.header, field);
+
+  // Set the Cache-Control header.
+  if (grq->flags & GeneratorRequest::CACHEABLE) {
+    char datebuf[64];
+
+    GeneratorTimestamp(datebuf, sizeof(datebuf));
+    TSMimeHdrFieldCreateNamed(response.buffer, response.header, TS_MIME_FIELD_CACHE_CONTROL,
TS_MIME_LEN_CACHE_CONTROL,  &field);
+    TSMimeHdrFieldValueStringSet(response.buffer, response.header, field, -1 /* idx */, "max-age=86400,
public", -1);
+    TSMimeHdrFieldAppend(response.buffer, response.header, field);
+    TSHandleMLocRelease(response.buffer, response.header, field);
+
+    TSMimeHdrFieldCreateNamed(response.buffer, response.header, TS_MIME_FIELD_LAST_MODIFIED,
TS_MIME_LEN_LAST_MODIFIED,  &field);
+    TSMimeHdrFieldValueStringSet(response.buffer, response.header, field, -1 /* idx */, datebuf,
-1);
+    TSMimeHdrFieldAppend(response.buffer, response.header, field);
+    TSHandleMLocRelease(response.buffer, response.header, field);
+  } else {
+    TSMimeHdrFieldCreateNamed(response.buffer, response.header, TS_MIME_FIELD_CACHE_CONTROL,
TS_MIME_LEN_CACHE_CONTROL,  &field);
+    TSMimeHdrFieldValueStringSet(response.buffer, response.header, field, -1 /* idx */, "private",
-1);
+    TSMimeHdrFieldAppend(response.buffer, response.header, field);
+    TSHandleMLocRelease(response.buffer, response.header, field);
+  }
+
+  // Start the vconn write.
+  grq->writeio.write(vc, contp);
+
+  // Write the header to the IO buffer. Set the VIO bytes so that we can get a WRITE_COMPLETE
+  // event when this is done.
+  int hdrlen = TSHttpHdrLengthGet(response.buffer, response.header);
+
+  TSHttpHdrPrint(response.buffer, response.header, grq->writeio.iobuf);
+  TSVIONBytesSet(grq->writeio.vio, hdrlen);
+  TSStatIntIncrement(StatCountBytes, hdrlen);
+
+  return true;
+}
+
+static bool
+GeneratorParseRequest(GeneratorRequest * grq)
+{
+  TSMLoc url;
+  const char * path;
+  const char * end;
+  int pathsz;
+  unsigned count = 0;
+
+  // First, make sure this is a GET request.
+  path = TSHttpHdrMethodGet(grq->rqheader.buffer, grq->rqheader.header, &pathsz);
+  if (path != TS_HTTP_METHOD_GET && path != TS_HTTP_METHOD_HEAD) {
+    VDEBUG("%.*s method is not supported", pathsz, path);
+    return false;
+  }
+
+  if (path == TS_HTTP_METHOD_HEAD) {
+    grq->flags |= GeneratorRequest::ISHEAD;
+  }
+
+  // Next, parse our parameters out of the URL.
+  TSReleaseAssert(TSHttpHdrUrlGet(grq->rqheader.buffer, grq->rqheader.header, &url)
== TS_SUCCESS);
+  TSReleaseAssert(path = TSUrlPathGet(grq->rqheader.buffer, url, &pathsz));
+
+  VDEBUG("requested path is %.*s", pathsz, path);
+
+  end = path + pathsz;
+  while (path < end) {
+    const char * sep = path;
+    size_t nbytes;
+
+    while (*sep != '/' && sep < end) {
+      ++sep;
+    }
+
+    nbytes = std::distance(path, sep);
+    if (nbytes) {
+
+      VDEBUG("path component is %.*s", (int)nbytes, path);
+
+      switch (count) {
+      case 0:
+        // First path component is "cache" or "nocache".
+        if (memcmp(path, "cache", 5) == 0) {
+          grq->flags |= GeneratorRequest::CACHEABLE;
+        } else if (memcmp(path, "nocache", 7) == 0) {
+          grq->flags &= ~GeneratorRequest::CACHEABLE;
+        } else {
+          VDEBUG("first component is %.*s, expecting 'cache' or 'nocache'", (int)nbytes,
path);
+          goto fail;
+        }
+
+        break;
+
+      case 1:
+        // Second path component is a byte count.
+        grq->nbytes = GeneratorParseByteCount(path, sep);
+        VDEBUG("generator byte count is %lld", (long long)grq->nbytes);
+        if (grq->nbytes >= 0) {
+          // We don't care about any other path components.
+          TSHandleMLocRelease(grq->rqheader.buffer, grq->rqheader.header, url);
+          return true;
+        }
+
+        goto fail;
+      }
+
+      ++count;
+    }
+
+    path = sep + 1;
+  }
+
+fail:
+  TSHandleMLocRelease(grq->rqheader.buffer, grq->rqheader.header, url);
+  return false;
+}
+
+// Handle events from TSHttpTxnServerIntercept. The intercept
+// starts with TS_EVENT_NET_ACCEPT, and then continues with
+// TSVConn events.
+static int
+GeneratorInterceptionHook(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: {
+    // TS_EVENT_NET_ACCEPT will be delivered when the server intercept
+    // is set up by the core. We just need to allocate a generator
+    // request state and start reading the VC.
+    GeneratorRequest * grq = new GeneratorRequest();
+
+    TSStatIntIncrement(StatCountResponses, 1);
+    VDEBUG("allocated server intercept generator grq=%p", grq);
+
+    // This continuation was allocated in GeneratorTxnHook. Reset the
+    // data to keep track of this generator request.
+    TSContDataSet(contp, grq);
+
+    // Start reading the request from the server intercept VC.
+    grq->readio.read(arg.vc, contp);
+    VIODEBUG(grq->readio.vio, "started reading generator request");
+
+    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 GeneratorTxnHook().
+    VDEBUG("cancelling server intercept request for txn=%p", cdata.txn);
+
+    TSContDestroy(contp);
+    return TS_EVENT_NONE;
+  }
+
+  case TS_EVENT_VCONN_READ_READY: {
+    argument_type cdata = TSContDataGet(contp);
+    GeneratorHttpHeader& rqheader = cdata.grq->rqheader;
+
+    VDEBUG("reading vio=%p vc=%p, grq=%p", arg.vio, TSVIOVConnGet(arg.vio), cdata.grq);
+
+    TSIOBufferBlock blk;
+    ssize_t         consumed = 0;
+    TSParseResult   result;
+
+    for (blk = TSIOBufferReaderStart(cdata.grq->readio.reader); blk; blk = TSIOBufferBlockNext(blk))
{
+        const char *    ptr;
+        const char *    end;
+        int64_t         nbytes;
+
+        ptr = TSIOBufferBlockReadStart(blk, cdata.grq->readio.reader, &nbytes);
+        if (ptr == NULL || nbytes == 0) {
+            continue;
+        }
+
+        end = ptr + nbytes;
+        result = TSHttpHdrParseReq(rqheader.parser, rqheader.buffer, rqheader.header, &ptr,
end);
+        switch (result) {
+        case TS_PARSE_ERROR:
+          break;
+        case TS_PARSE_DONE:
+        case TS_PARSE_OK:
+          result = TS_PARSE_OK;
+        case TS_PARSE_CONT:
+          // We consumed the buffer we got minus the remainder.
+          consumed += (nbytes - std::distance(ptr, end));
+        }
+
+        if (result == TS_PARSE_ERROR || result == TS_PARSE_OK) {
+            break;
+        }
+    }
+
+    // If we got a bad request, just shut it down.
+    if (result == TS_PARSE_ERROR) {
+      VDEBUG("bad request on grq=%p, sending an error", cdata.grq);
+      GeneratorRequestDestroy(cdata.grq, arg.vio, contp);
+      return TS_EVENT_ERROR;
+    }
+
+    if (result == TS_PARSE_OK) {
+      // Check the response.
+      VDEBUG("parsed request on grq=%p, sending a response ", cdata.grq);
+      if (GeneratorParseRequest(cdata.grq) && GeneratorWriteResponseHeader(cdata.grq,
TSVIOVConnGet(arg.vio), contp)) {
+        // If this is a HEAD request, we don't need to send any bytes.
+        if (cdata.grq->flags & GeneratorRequest::ISHEAD) {
+          cdata.grq->nbytes = 0;
+        }
+        return TS_EVENT_NONE;
+      }
+
+      // We got a syntactically bad URL. It would be graceful to send
+      // a 400 response, but we are graceless and just fail the
+      // transaction.
+      GeneratorRequestDestroy(cdata.grq, arg.vio, contp);
+      return TS_EVENT_ERROR;
+    }
+
+    TSReleaseAssert(result == TS_PARSE_CONT);
+
+    // Reenable the read VIO to get more events.
+    TSVIOReenable(arg.vio);
+    return TS_EVENT_NONE;
+  }
+
+  case TS_EVENT_VCONN_WRITE_READY: {
+    argument_type cdata = TSContDataGet(contp);
+
+    if (cdata.grq->nbytes) {
+      int64_t nbytes;
+
+      if (cdata.grq->nbytes >= sizeof(GeneratorData)) {
+        nbytes = sizeof(GeneratorData);
+      } else {
+        nbytes = cdata.grq->nbytes % sizeof(GeneratorData);
+      }
+
+      VIODEBUG(arg.vio, "writing %" PRId64 " bytes for grq=%p", nbytes, cdata.grq);
+      nbytes = TSIOBufferWrite(cdata.grq->writeio.iobuf, GeneratorData, nbytes);
+
+      cdata.grq->nbytes -= nbytes;
+      TSStatIntIncrement(StatCountBytes, nbytes);
+
+      // Update the number of bytes to write.
+      TSVIONBytesSet(arg.vio, TSVIONBytesGet(arg.vio) + nbytes);
+      TSVIOReenable(arg.vio);
+    }
+
+    return TS_EVENT_NONE;
+  }
+
+  case TS_EVENT_ERROR:
+  case TS_EVENT_VCONN_EOS: {
+    argument_type cdata = TSContDataGet(contp);
+
+    VIODEBUG(arg.vio, "received EOS or ERROR for grq=%p", cdata.grq);
+    GeneratorRequestDestroy(cdata.grq, arg.vio, 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: {
+    argument_type cdata = TSContDataGet(contp);
+
+    // If we still have bytes to write, kick off a new write operation, otherwise
+    // we are done and we can shut down the VC.
+    if (cdata.grq->nbytes) {
+      cdata.grq->writeio.write(TSVIOVConnGet(arg.vio), contp);
+      TSVIONBytesSet(cdata.grq->writeio.vio, cdata.grq->nbytes);
+    } else {
+      VIODEBUG(arg.vio, "TS_EVENT_VCONN_WRITE_COMPLETE %" PRId64 " todo", TSVIONTodoGet(arg.vio));
+      GeneratorRequestDestroy(cdata.grq, arg.vio, contp);
+    }
+
+    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
+GeneratorTxnHook(TSCont contp, TSEvent event, void * edata)
+{
+  argument_type arg(edata);
+
+  VDEBUG("contp=%p, event=%s (%d), edata=%p", contp, TSHttpEventNameLookup(event), event,
edata);
+
+  switch (event) {
+  case TS_EVENT_HTTP_CACHE_LOOKUP_COMPLETE: {
+    int status;
+
+    TSReleaseAssert(TSHttpTxnCacheLookupStatusGet(arg.txn, &status) == TS_SUCCESS);
+    if (status != TS_CACHE_LOOKUP_HIT_FRESH) {
+      // This transaction is going to be a cache miss, so intercept it.
+      VDEBUG("intercepting orgin server request for txn=%p", arg.txn);
+      TSHttpTxnServerIntercept(TSContCreate(GeneratorInterceptionHook, TSMutexCreate()),
arg.txn);
+    }
+
+    break;
+  }
+
+  default:
+    VERROR("unexpected event %s (%d)", TSHttpEventNameLookup(event), event);
+    break;
+  }
+
+  TSHttpTxnReenable(arg.txn, TS_EVENT_HTTP_CONTINUE);
+  return TS_EVENT_NONE;
+}
+
+static void
+GeneratorInitialize(void)
+{
+  TxnHook = TSContCreate(GeneratorTxnHook, NULL);
+  memset(GeneratorData, 'x', sizeof(GeneratorData));
+
+  if (TSStatFindName("generator.response_bytes", &StatCountBytes) == TS_ERROR) {
+    StatCountBytes = TSStatCreate("generator.response_bytes",  TS_RECORDDATATYPE_COUNTER,
TS_STAT_NON_PERSISTENT, TS_STAT_SYNC_SUM);
+  }
+
+  if (TSStatFindName("generator.response_count", &StatCountResponses) == TS_ERROR) {
+    StatCountResponses = TSStatCreate("generator.response_count",  TS_RECORDDATATYPE_COUNTER,
TS_STAT_NON_PERSISTENT, TS_STAT_SYNC_COUNT);
+  }
+}
+
+void
+TSPluginInit(int /* argc */, const char * /* argv */ [])
+{
+  TSPluginRegistrationInfo info;
+
+  info.plugin_name = (char *)PLUGIN;
+  info.vendor_name = (char *)"Apache Software Foundation";
+  info.support_email = (char *)"dev@trafficserver.apache.org";
+
+  if (TSPluginRegister(TS_SDK_VERSION_3_0, &info) != TS_SUCCESS) {
+    VERROR("plugin registration failed\n");
+  }
+
+  GeneratorInitialize();
+
+  // 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);
+}
+
+TSReturnCode
+TSRemapInit(TSRemapInterface * /* api_info */, char * /* errbuf */, int /* errbuf_size */)
+{
+  GeneratorInitialize();
+  return TS_SUCCESS;
+}
+
+TSRemapStatus
+TSRemapDoRemap(void * /* ih */, TSHttpTxn txn, TSRemapRequestInfo * /* rri ATS_UNUSED */)
+{
+  TSHttpTxnHookAdd(txn, TS_HTTP_CACHE_LOOKUP_COMPLETE_HOOK, TxnHook);
+  return TSREMAP_NO_REMAP; // This plugin never rewrites anything.
+}
+
+TSReturnCode
+TSRemapNewInstance(int /* argc */, char * /* argv */[], void ** ih, char * /* errbuf ATS_UNUSED
*/, int /* errbuf_size ATS_UNUSED */)
+{
+  *ih = NULL;
+  return TS_SUCCESS;
+}
+
+void
+TSRemapDeleteInstance(void * /* ih */)
+{
+}


Mime
View raw message