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 <[email protected]> Authored: Thu Dec 11 13:26:52 2014 -0800 Committer: James Peach <[email protected]> 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 *)"[email protected]"; + + 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 */) +{ +}
