You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@trafficserver.apache.org by zw...@apache.org on 2012/08/22 20:32:02 UTC
git commit: Import from GSOC code:
Updated Branches:
refs/heads/master ed4154700 -> 019a9adc3
Import from GSOC code:
https://github.com/jablko/dedup
Project: http://git-wip-us.apache.org/repos/asf/trafficserver/repo
Commit: http://git-wip-us.apache.org/repos/asf/trafficserver/commit/019a9adc
Tree: http://git-wip-us.apache.org/repos/asf/trafficserver/tree/019a9adc
Diff: http://git-wip-us.apache.org/repos/asf/trafficserver/diff/019a9adc
Branch: refs/heads/master
Commit: 019a9adc311926bd9c8e6c674392c2e308ab193a
Parents: ed41547
Author: Jack Bates <lo...@nottheoilrig.com>
Authored: Wed Aug 22 11:27:29 2012 -0600
Committer: Leif Hedstrom <zw...@apache.org>
Committed: Wed Aug 22 12:31:35 2012 -0600
----------------------------------------------------------------------
plugins/experimental/metalink/metalink.cc | 669 ++++++++++++++++++++++++
1 files changed, 669 insertions(+), 0 deletions(-)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/trafficserver/blob/019a9adc/plugins/experimental/metalink/metalink.cc
----------------------------------------------------------------------
diff --git a/plugins/experimental/metalink/metalink.cc b/plugins/experimental/metalink/metalink.cc
new file mode 100644
index 0000000..dec26ec
--- /dev/null
+++ b/plugins/experimental/metalink/metalink.cc
@@ -0,0 +1,669 @@
+/** @file
+
+ Implement the Metalink protocol to "dedup" cache entries for
+ equivalent content. This can for example improve the cache hit
+ ratio for content with many different (unique) URLs.
+
+ @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.
+*/
+
+
+/*
+ This plugin was originally developed by Jack Bates during his Google
+ Summer of Code 2012 project for Metalinker.
+*/
+
+
+#include <stdio.h>
+#include <string.h>
+
+#include <openssl/sha.h>
+
+#define __STDC_LIMIT_MACROS
+
+#include <ts/ts.h>
+
+typedef struct {
+ TSVConn connp;
+ TSIOBuffer bufp;
+
+} WriteData;
+
+typedef struct {
+ TSHttpTxn txnp;
+
+ /* Null transform */
+ TSIOBuffer bufp;
+ TSVIO viop;
+
+ /* Message digest handle */
+ SHA256_CTX c;
+
+ TSCacheKey key;
+
+} TransformData;
+
+typedef struct {
+ TSHttpTxn txnp;
+
+ TSMBuffer resp_bufp;
+ TSMLoc hdr_loc;
+
+ /* "Location: ..." header */
+ TSMLoc location_loc;
+
+ /* Cache key */
+ TSMLoc url_loc;
+ TSCacheKey key;
+
+ /* "Digest: SHA-256=..." header */
+ TSMLoc digest_loc;
+
+ /* Digest header field value index */
+ int idx;
+
+ TSIOBuffer read_bufp;
+
+} SendData;
+
+static int
+write_vconn_write_complete(TSCont contp, void *edata)
+{
+ WriteData *data = (WriteData *) TSContDataGet(contp);
+ TSContDestroy(contp);
+
+ /* The object is not committed to the cache until the vconnection is closed.
+ * When all data has been transferred, the user (contp) must do a
+ * TSVConnClose() */
+ TSVConnClose(data->connp);
+
+ TSIOBufferDestroy(data->bufp);
+ TSfree(data);
+
+ return 0;
+}
+
+static int
+write_handler(TSCont contp, TSEvent event, void *edata)
+{
+ switch (event) {
+ case TS_EVENT_VCONN_WRITE_COMPLETE:
+ return write_vconn_write_complete(contp, edata);
+
+ default:
+ TSAssert(!"Unexpected event");
+ }
+
+ return 0;
+}
+
+static int
+cache_open_write(TSCont contp, void *edata)
+{
+ TSMBuffer bufp;
+
+ TSMLoc hdr_loc;
+ TSMLoc url_loc;
+
+ const char *value;
+ int length;
+
+ TransformData *transform_data = (TransformData *) TSContDataGet(contp);
+
+ TSCacheKeyDestroy(transform_data->key);
+
+ WriteData *write_data = (WriteData *) TSmalloc(sizeof(WriteData));
+ write_data->connp = (TSVConn) edata;
+
+ contp = TSContCreate(write_handler, NULL);
+ TSContDataSet(contp, write_data);
+
+ write_data->bufp = TSIOBufferCreate();
+ TSIOBufferReader readerp = TSIOBufferReaderAlloc(write_data->bufp);
+
+ if (TSHttpTxnClientReqGet(transform_data->txnp, &bufp, &hdr_loc) != TS_SUCCESS) {
+ TSError("Couldn't retrieve client request header");
+
+ return 0;
+ }
+
+ if (TSHttpHdrUrlGet(bufp, hdr_loc, &url_loc) != TS_SUCCESS) {
+ TSHandleMLocRelease(bufp, TS_NULL_MLOC, hdr_loc);
+
+ return 0;
+ }
+
+ value = TSUrlStringGet(bufp, url_loc, &length);
+ if (!value) {
+ TSHandleMLocRelease(bufp, hdr_loc, url_loc);
+ TSHandleMLocRelease(bufp, TS_NULL_MLOC, hdr_loc);
+
+ return 0;
+ }
+
+ TSHandleMLocRelease(bufp, hdr_loc, url_loc);
+ TSHandleMLocRelease(bufp, TS_NULL_MLOC, hdr_loc);
+
+ int nbytes = TSIOBufferWrite(write_data->bufp, value, length);
+
+ TSVConnWrite(write_data->connp, contp, readerp, nbytes);
+
+ return 0;
+}
+
+static int
+cache_open_write_failed(TSCont contp, void *edata)
+{
+ TransformData *data = (TransformData *) TSContDataGet(contp);
+
+ TSCacheKeyDestroy(data->key);
+
+ return 0;
+}
+
+static int
+vconn_write_ready(TSCont contp, void *edata)
+{
+ const char *value;
+ int64_t length;
+ char digest[32];
+ TransformData *data = (TransformData *) TSContDataGet(contp);
+
+ /* Can't TSVConnWrite() before TS_HTTP_RESPONSE_TRANSFORM_HOOK */
+ if (!data->bufp) {
+ TSVConn connp = TSTransformOutputVConnGet(contp);
+
+ data->bufp = TSIOBufferCreate();
+ TSIOBufferReader readerp = TSIOBufferReaderAlloc(data->bufp);
+
+ data->viop = TSVConnWrite(connp, contp, readerp, INT64_MAX);
+
+ SHA256_Init(&data->c);
+ }
+
+ TSVIO viop = TSVConnWriteVIOGet(contp);
+ TSIOBuffer bufp = TSVIOBufferGet(viop);
+
+ if (!bufp) {
+ int ndone = TSVIONDoneGet(viop);
+ TSVIONBytesSet(data->viop, ndone);
+
+ TSVIOReenable(data->viop);
+
+ return 0;
+ }
+
+ TSIOBufferReader readerp = TSVIOReaderGet(viop);
+ int avail = TSIOBufferReaderAvail(readerp);
+
+ if (avail > 0) {
+ TSIOBufferCopy(data->bufp, readerp, avail, 0);
+
+ /* Feed content to message digest */
+ TSIOBufferBlock blockp = TSIOBufferReaderStart(readerp);
+ while (blockp) {
+
+ value = TSIOBufferBlockReadStart(blockp, readerp, &length);
+ SHA256_Update(&data->c, value, length);
+
+ blockp = TSIOBufferBlockNext(blockp);
+ }
+
+ TSIOBufferReaderConsume(readerp, avail);
+
+ int ndone = TSVIONDoneGet(viop);
+ TSVIONDoneSet(viop, ndone + avail);
+ }
+
+ /* If not finished and we copied some content */
+ int ntodo = TSVIONTodoGet(viop);
+
+ if (ntodo > 0) {
+ if (avail > 0) {
+ TSContCall(TSVIOContGet(viop), TS_EVENT_VCONN_WRITE_READY, viop);
+
+ TSVIOReenable(data->viop);
+ }
+ /* If finished */
+ } else {
+ TSContCall(TSVIOContGet(viop), TS_EVENT_VCONN_WRITE_COMPLETE, viop);
+
+ int ndone = TSVIONDoneGet(viop);
+ TSVIONBytesSet(data->viop, ndone);
+
+ TSVIOReenable(data->viop);
+
+ SHA256_Final((unsigned char *) digest, &data->c);
+
+ data->key = TSCacheKeyCreate();
+ if (TSCacheKeyDigestSet(data->key, digest, sizeof(digest)) != TS_SUCCESS) {
+ return 0;
+ }
+
+ TSCacheWrite(contp, data->key);
+ }
+
+ return 0;
+}
+
+static int
+transform_vconn_write_complete(TSCont contp, void *edata)
+{
+ TransformData *data = (TransformData *) TSContDataGet(contp);
+
+ TSVConn connp = TSTransformOutputVConnGet(contp);
+ TSVConnShutdown(connp, 0, 1);
+
+ TSIOBufferDestroy(data->bufp);
+ TSfree(data);
+
+ TSContDestroy(contp);
+
+ return 0;
+}
+
+static int
+transform_handler(TSCont contp, TSEvent event, void *edata)
+{
+ switch (event) {
+ case TS_EVENT_CACHE_OPEN_WRITE:
+ return cache_open_write(contp, edata);
+
+ case TS_EVENT_CACHE_OPEN_WRITE_FAILED:
+ return cache_open_write_failed(contp, edata);
+
+ case TS_EVENT_IMMEDIATE:
+ case TS_EVENT_VCONN_WRITE_READY:
+ return vconn_write_ready(contp, edata);
+
+ case TS_EVENT_VCONN_WRITE_COMPLETE:
+ return transform_vconn_write_complete(contp, edata);
+
+ default:
+ TSAssert(!"Unexpected event");
+ }
+
+ return 0;
+}
+
+static int
+rewrite_handler(TSCont contp, TSEvent event, void *edata)
+{
+ const char *value;
+ int length;
+
+ SendData *data = (SendData *) TSContDataGet(contp);
+ TSContDestroy(contp);
+
+ switch (event) {
+
+ /* Yes: Rewrite "Location: ..." header and reenable response */
+ case TS_EVENT_CACHE_OPEN_READ:
+ value = TSUrlStringGet(data->resp_bufp, data->url_loc, &length);
+ TSMimeHdrFieldValuesClear(data->resp_bufp, data->hdr_loc, data->location_loc);
+ TSMimeHdrFieldValueStringInsert(data->resp_bufp, data->hdr_loc, data->location_loc, -1, value, length);
+ break;
+
+ case TS_EVENT_CACHE_OPEN_READ_FAILED:
+ break;
+
+ default:
+ TSAssert(!"Unexpected event");
+ }
+
+ TSCacheKeyDestroy(data->key);
+
+ TSHandleMLocRelease(data->resp_bufp, TS_NULL_MLOC, data->url_loc);
+ TSHandleMLocRelease(data->resp_bufp, data->hdr_loc, data->location_loc);
+ TSHandleMLocRelease(data->resp_bufp, TS_NULL_MLOC, data->hdr_loc);
+
+ TSHttpTxnReenable(data->txnp, TS_EVENT_HTTP_CONTINUE);
+ TSfree(data);
+
+ return 0;
+}
+
+static int
+cache_open_read(TSCont contp, void *edata)
+{
+ SendData *data = (SendData *) TSContDataGet(contp);
+ TSVConn connp = (TSVConn) edata;
+
+ data->read_bufp = TSIOBufferCreate();
+ TSVConnRead(connp, contp, data->read_bufp, INT64_MAX);
+
+ return 0;
+}
+
+static int
+cache_open_read_failed(TSCont contp, void *edata)
+{
+ SendData *data = (SendData *) TSContDataGet(contp);
+ TSContDestroy(contp);
+
+ TSCacheKeyDestroy(data->key);
+
+ TSHandleMLocRelease(data->resp_bufp, TS_NULL_MLOC, data->url_loc);
+ TSHandleMLocRelease(data->resp_bufp, data->hdr_loc, data->location_loc);
+ TSHandleMLocRelease(data->resp_bufp, TS_NULL_MLOC, data->hdr_loc);
+
+ TSHttpTxnReenable(data->txnp, TS_EVENT_HTTP_CONTINUE);
+ TSfree(data);
+
+ return 0;
+}
+
+static int
+vconn_read_ready(TSCont contp, void *edata)
+{
+ const char *value;
+ int64_t length;
+ SendData *data = (SendData *) TSContDataGet(contp);
+
+ TSContDestroy(contp);
+
+ TSIOBufferReader readerp = TSIOBufferReaderAlloc(data->read_bufp);
+ TSIOBufferBlock blockp = TSIOBufferReaderStart(readerp);
+
+ value = TSIOBufferBlockReadStart(blockp, readerp, &length);
+ if (TSUrlParse(data->resp_bufp, data->url_loc, &value, value + length) != TS_PARSE_DONE) {
+ TSIOBufferDestroy(data->read_bufp);
+
+ TSCacheKeyDestroy(data->key);
+
+ TSHandleMLocRelease(data->resp_bufp, TS_NULL_MLOC, data->url_loc);
+ TSHandleMLocRelease(data->resp_bufp, data->hdr_loc, data->location_loc);
+ TSHandleMLocRelease(data->resp_bufp, TS_NULL_MLOC, data->hdr_loc);
+
+ TSHttpTxnReenable(data->txnp, TS_EVENT_HTTP_CONTINUE);
+ TSfree(data);
+
+ return 0;
+ }
+
+ TSIOBufferDestroy(data->read_bufp);
+
+ if (TSCacheKeyDigestFromUrlSet(data->key, data->url_loc) != TS_SUCCESS) {
+ TSCacheKeyDestroy(data->key);
+
+ TSHandleMLocRelease(data->resp_bufp, TS_NULL_MLOC, data->url_loc);
+ TSHandleMLocRelease(data->resp_bufp, data->hdr_loc, data->location_loc);
+ TSHandleMLocRelease(data->resp_bufp, TS_NULL_MLOC, data->hdr_loc);
+
+ TSHttpTxnReenable(data->txnp, TS_EVENT_HTTP_CONTINUE);
+ TSfree(data);
+
+ return 0;
+ }
+
+ contp = TSContCreate(rewrite_handler, NULL);
+ TSContDataSet(contp, data);
+
+ TSCacheRead(contp, data->key);
+
+ return 0;
+}
+
+/* Check if "Digest: SHA-256=..." digest already exist in cache */
+
+static int
+digest_handler(TSCont contp, TSEvent event, void *edata)
+{
+ switch (event) {
+ case TS_EVENT_CACHE_OPEN_READ:
+ return cache_open_read(contp, edata);
+
+ case TS_EVENT_CACHE_OPEN_READ_FAILED:
+ return cache_open_read_failed(contp, edata);
+
+ case TS_EVENT_VCONN_READ_READY:
+ return vconn_read_ready(contp, edata);
+
+ default:
+ TSAssert(!"Unexpected event");
+ }
+
+ return 0;
+}
+
+/* Check if "Location: ..." URL already exist in cache */
+
+static int
+location_handler(TSCont contp, TSEvent event, void *edata)
+{
+ SendData *data = (SendData *) TSContDataGet(contp);
+ TSContDestroy(contp);
+
+ switch (event) {
+ /* Yes: Do nothing, just reenable response */
+ case TS_EVENT_CACHE_OPEN_READ:
+ break;
+
+ /* No: Check "Digest: SHA-256=..." digest */
+ case TS_EVENT_CACHE_OPEN_READ_FAILED:
+ {
+ const char *value;
+ int length;
+
+ /* ATS_BASE64_DECODE_DSTLEN() */
+ char digest[33];
+
+ value = TSMimeHdrFieldValueStringGet(data->resp_bufp, data->hdr_loc, data->digest_loc, data->idx, &length);
+ if (TSBase64Decode(value + 8, length - 8, (unsigned char *) digest, sizeof(digest), NULL) != TS_SUCCESS
+ || TSCacheKeyDigestSet(data->key, digest, 32 /* SHA-256 */ ) != TS_SUCCESS) {
+ break;
+ }
+
+ contp = TSContCreate(digest_handler, NULL);
+ TSContDataSet(contp, data);
+
+ TSCacheRead(contp, data->key);
+ TSHandleMLocRelease(data->resp_bufp, data->hdr_loc, data->digest_loc);
+
+ return 0;
+ }
+ break;
+
+ default:
+ TSAssert(!"Unexpected event");
+ }
+
+ TSHandleMLocRelease(data->resp_bufp, data->hdr_loc, data->digest_loc);
+
+ TSCacheKeyDestroy(data->key);
+
+ TSHandleMLocRelease(data->resp_bufp, TS_NULL_MLOC, data->url_loc);
+ TSHandleMLocRelease(data->resp_bufp, data->hdr_loc, data->location_loc);
+ TSHandleMLocRelease(data->resp_bufp, TS_NULL_MLOC, data->hdr_loc);
+
+ TSHttpTxnReenable(data->txnp, TS_EVENT_HTTP_CONTINUE);
+ TSfree(data);
+
+ return 0;
+}
+
+/* Compute SHA-256 digest, write to cache, and store there the request URL */
+
+static int
+http_read_response_hdr(TSCont contp, void *edata)
+{
+ TransformData *data = (TransformData *) TSmalloc(sizeof(TransformData));
+ data->txnp = (TSHttpTxn) edata;
+
+ /* Can't TSVConnWrite() before TS_HTTP_RESPONSE_TRANSFORM_HOOK */
+ data->bufp = NULL;
+
+ TSVConn connp = TSTransformCreate(transform_handler, data->txnp);
+ TSContDataSet(connp, data);
+
+ TSHttpTxnHookAdd(data->txnp, TS_HTTP_RESPONSE_TRANSFORM_HOOK, connp);
+
+ TSHttpTxnReenable(data->txnp, TS_EVENT_HTTP_CONTINUE);
+
+ return 0;
+}
+
+static int
+http_send_response_hdr(TSCont contp, void *edata)
+{
+ const char *value;
+ int length;
+
+ SendData *data = (SendData *) TSmalloc(sizeof(SendData));
+
+ data->txnp = (TSHttpTxn) edata;
+ if (TSHttpTxnClientRespGet(data->txnp, &data->resp_bufp, &data->hdr_loc) != TS_SUCCESS) {
+ TSError("Couldn't retrieve client response header");
+
+ TSHttpTxnReenable(data->txnp, TS_EVENT_HTTP_CONTINUE);
+ TSfree(data);
+
+ return 0;
+ }
+
+ /* If Instance Digests are not provided by the Metalink servers, the Link
+ * header fields pertaining to this specification MUST be ignored */
+
+ /* Metalinks contain whole file hashes as described in Section 6, and MUST
+ * include SHA-256, as specified in [FIPS-180-3] */
+
+ /* Assumption: Want to minimize cache read, so check first that:
+ *
+ * 1. response has "Location: ..." header
+ * 2. response has "Digest: SHA-256=..." header
+ *
+ * Then scan if URL or digest already exist in cache */
+
+ /* If response has "Location: ..." header */
+ data->location_loc = TSMimeHdrFieldFind(data->resp_bufp, data->hdr_loc, TS_MIME_FIELD_LOCATION, TS_MIME_LEN_LOCATION);
+ if (!data->location_loc) {
+ TSHandleMLocRelease(data->resp_bufp, TS_NULL_MLOC, data->hdr_loc);
+
+ TSHttpTxnReenable(data->txnp, TS_EVENT_HTTP_CONTINUE);
+ TSfree(data);
+
+ return 0;
+ }
+
+ TSUrlCreate(data->resp_bufp, &data->url_loc);
+
+ /* If can't parse or lookup "Location: ..." URL, should still check if
+ * response has "Digest: SHA-256=..." header? No: Can't parse or lookup URL
+ * in "Location: ..." header is error */
+ value = TSMimeHdrFieldValueStringGet(data->resp_bufp, data->hdr_loc, data->location_loc, 0, &length);
+ if (TSUrlParse(data->resp_bufp, data->url_loc, &value, value + length) != TS_PARSE_DONE) {
+
+ TSHandleMLocRelease(data->resp_bufp, TS_NULL_MLOC, data->url_loc);
+ TSHandleMLocRelease(data->resp_bufp, data->hdr_loc, data->location_loc);
+ TSHandleMLocRelease(data->resp_bufp, TS_NULL_MLOC, data->hdr_loc);
+
+ TSHttpTxnReenable(data->txnp, TS_EVENT_HTTP_CONTINUE);
+ TSfree(data);
+
+ return 0;
+ }
+
+ data->key = TSCacheKeyCreate();
+ if (TSCacheKeyDigestFromUrlSet(data->key, data->url_loc) != TS_SUCCESS) {
+ TSCacheKeyDestroy(data->key);
+
+ TSHandleMLocRelease(data->resp_bufp, TS_NULL_MLOC, data->url_loc);
+ TSHandleMLocRelease(data->resp_bufp, data->hdr_loc, data->location_loc);
+ TSHandleMLocRelease(data->resp_bufp, TS_NULL_MLOC, data->hdr_loc);
+
+ TSHttpTxnReenable(data->txnp, TS_EVENT_HTTP_CONTINUE);
+ TSfree(data);
+
+ return 0;
+ }
+
+ /* ... and "Digest: SHA-256=..." header */
+ data->digest_loc = TSMimeHdrFieldFind(data->resp_bufp, data->hdr_loc, "Digest", 6);
+ while (data->digest_loc) {
+
+ int count = TSMimeHdrFieldValuesCount(data->resp_bufp, data->hdr_loc, data->digest_loc);
+ for (data->idx = 0; data->idx < count; data->idx += 1) {
+
+ value = TSMimeHdrFieldValueStringGet(data->resp_bufp, data->hdr_loc, data->digest_loc, data->idx, &length);
+ if (length < 8 + 44 /* 32 bytes, Base64 */ || strncasecmp(value, "SHA-256=", 8)) {
+ continue;
+ }
+
+ contp = TSContCreate(location_handler, NULL);
+ TSContDataSet(contp, data);
+
+ TSCacheRead(contp, data->key);
+
+ return 0;
+ }
+
+ TSMLoc next_loc = TSMimeHdrFieldNextDup(data->resp_bufp, data->hdr_loc, data->digest_loc);
+
+ TSHandleMLocRelease(data->resp_bufp, data->hdr_loc, data->digest_loc);
+
+ data->digest_loc = next_loc;
+ }
+
+ TSCacheKeyDestroy(data->key);
+
+ TSHandleMLocRelease(data->resp_bufp, TS_NULL_MLOC, data->url_loc);
+ TSHandleMLocRelease(data->resp_bufp, data->hdr_loc, data->location_loc);
+ TSHandleMLocRelease(data->resp_bufp, TS_NULL_MLOC, data->hdr_loc);
+
+ TSHttpTxnReenable(data->txnp, TS_EVENT_HTTP_CONTINUE);
+ TSfree(data);
+
+ return 0;
+}
+
+static int
+handler(TSCont contp, TSEvent event, void *edata)
+{
+ switch (event) {
+ case TS_EVENT_HTTP_READ_RESPONSE_HDR:
+ return http_read_response_hdr(contp, edata);
+
+ case TS_EVENT_HTTP_SEND_RESPONSE_HDR:
+ return http_send_response_hdr(contp, edata);
+
+ default:
+ TSAssert(!"Unexpected event");
+ }
+
+ return 0;
+}
+
+void
+TSPluginInit(int argc, const char *argv[])
+{
+ TSPluginRegistrationInfo info;
+
+ info.plugin_name = const_cast<char*>("metalink");
+ info.vendor_name = const_cast<char*>("Jack Bates");
+ info.support_email = const_cast<char*>("jack@nottheoilrig.com");
+
+ if (TSPluginRegister(TS_SDK_VERSION_3_0, &info) != TS_SUCCESS) {
+ TSError("Plugin registration failed");
+ }
+
+ TSCont contp = TSContCreate(handler, NULL);
+
+ TSHttpHookAdd(TS_HTTP_READ_RESPONSE_HDR_HOOK, contp);
+ TSHttpHookAdd(TS_HTTP_SEND_RESPONSE_HDR_HOOK, contp);
+}