| /** @file |
| |
| @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 "ServerIntercept.h" |
| #include "BodyData.h" |
| #include "stale_response.h" |
| |
| #include "ts/ts.h" |
| #include "ts_wrap.h" |
| |
| #include <climits> |
| #include <cstdio> |
| #include <cstring> |
| #include <search.h> |
| #include <strings.h> |
| |
| #include <arpa/inet.h> |
| |
| using namespace std; |
| |
| #define PLUGIN_TAG_SERV "stale_response_intercept" |
| const int64_t c_max_single_write = 64 * 1024; |
| |
| DEF_DBG_CTL(TAG_SERV) |
| |
| /*-----------------------------------------------------------------------------------------------*/ |
| struct SContData { |
| struct IoHandle { |
| TSVIO vio; |
| TSIOBuffer buffer; |
| TSIOBufferReader reader; |
| IoHandle() : vio(0), buffer(0), reader(0){}; |
| ~IoHandle() |
| { |
| if (reader) { |
| TSIOBufferReaderFree(reader); |
| } |
| if (buffer) { |
| TSIOBufferDestroy(buffer); |
| } |
| }; |
| }; |
| |
| TSVConn net_vc; |
| TSCont contp; |
| |
| IoHandle input; |
| IoHandle output; |
| TSHttpParser http_parser; |
| TSMBuffer req_hdr_bufp; |
| TSMLoc req_hdr_loc; |
| bool req_hdr_parsed; |
| |
| bool conn_setup; |
| bool write_setup; |
| |
| ConfigInfo *plugin_config; |
| BodyData *pBody; |
| uint32_t next_chunk_written; |
| |
| SContData(TSCont cont) |
| : net_vc(0), |
| contp(cont), |
| input(), |
| output(), |
| req_hdr_bufp(0), |
| req_hdr_loc(0), |
| req_hdr_parsed(false), |
| conn_setup(false), |
| write_setup(false), |
| plugin_config(nullptr), |
| pBody(nullptr), |
| next_chunk_written(0) |
| { |
| http_parser = TSHttpParserCreate(); |
| } |
| |
| ~SContData() |
| { |
| SRDBG(TAG_SERV, "[%s] Destroying continuation data", __FUNCTION__); |
| TSHttpParserDestroy(http_parser); |
| if (req_hdr_loc) { |
| TSHandleMLocRelease(req_hdr_bufp, TS_NULL_MLOC, req_hdr_loc); |
| } |
| if (req_hdr_bufp) { |
| TSMBufferDestroy(req_hdr_bufp); |
| } |
| }; |
| }; |
| |
| /*-----------------------------------------------------------------------------------------------*/ |
| static bool |
| connSetup(SContData *cont_data, TSVConn vconn) |
| { |
| if (cont_data->conn_setup) { |
| SRDBG(TAG_BAD, "[%s] SContData already init", __FUNCTION__); |
| return false; |
| } |
| cont_data->conn_setup = true; |
| cont_data->net_vc = vconn; |
| |
| cont_data->input.buffer = TSIOBufferCreate(); |
| cont_data->input.reader = TSIOBufferReaderAlloc(cont_data->input.buffer); |
| cont_data->input.vio = TSVConnRead(cont_data->net_vc, cont_data->contp, cont_data->input.buffer, INT_MAX); |
| |
| cont_data->req_hdr_bufp = TSMBufferCreate(); |
| cont_data->req_hdr_loc = TSHttpHdrCreate(cont_data->req_hdr_bufp); |
| TSHttpHdrTypeSet(cont_data->req_hdr_bufp, cont_data->req_hdr_loc, TS_HTTP_TYPE_REQUEST); |
| |
| SRDBG(TAG_SERV, "[%s] Done", __FUNCTION__); |
| return true; |
| } |
| |
| /*-----------------------------------------------------------------------------------------------*/ |
| static void |
| connShutdownDataDestory(SContData *cont_data) |
| { |
| if (cont_data->net_vc) { |
| TSVConnClose(cont_data->net_vc); |
| } |
| // we need to destroy the body data object |
| if (cont_data->pBody->key_hash_active) { |
| if (!async_remove_active(cont_data->pBody->key_hash, cont_data->plugin_config)) { |
| SRDBG(TAG_BAD, "[%s] didnt delete async active", __FUNCTION__); |
| } |
| } else { |
| body_memory_release(cont_data->plugin_config, cont_data->pBody); |
| delete cont_data->pBody; |
| } |
| // clean up my cont |
| TSContDestroy(cont_data->contp); |
| delete cont_data; |
| SRDBG(TAG_SERV, "[%s] Done", __FUNCTION__); |
| } |
| |
| /*-----------------------------------------------------------------------------------------------*/ |
| static bool |
| writeOutData(SContData *cont_data) |
| { |
| int64_t total_current_write = 0; |
| uint32_t max_chunk_count = cont_data->pBody->getChunkCount(); |
| for (uint32_t chunk_index = cont_data->next_chunk_written; chunk_index < max_chunk_count; chunk_index++) { |
| const char *start; |
| int64_t avail; |
| if (!cont_data->pBody->getChunk(chunk_index, &start, &avail)) { |
| SRDBG(TAG_BAD, "[%s] Error while getting chunk_index %d", __FUNCTION__, chunk_index); |
| TSError("[%s] Error while getting chunk_index %d", __FUNCTION__, chunk_index); |
| break; |
| } |
| if (TSIOBufferWrite(cont_data->output.buffer, start, avail) != avail) { |
| SRDBG(TAG_BAD, "[%s] Error while writing content avail=%d", __FUNCTION__, (int)avail); |
| } |
| cont_data->pBody->removeChunk(chunk_index); |
| total_current_write += avail; |
| cont_data->next_chunk_written = chunk_index + 1; |
| if (total_current_write >= c_max_single_write) { |
| break; |
| } |
| } |
| TSVIOReenable(cont_data->output.vio); |
| |
| // SRDBG(TAG_SERV, "[%s] written=%d done=%d", __FUNCTION__, (int)total_current_write,(cont_data->next_chunk_written >= |
| // max_chunk_count)); |
| return (cont_data->next_chunk_written >= max_chunk_count); |
| } |
| |
| /*-----------------------------------------------------------------------------------------------*/ |
| void |
| writeSetup(SContData *cont_data) |
| { |
| if (!cont_data->write_setup) { |
| cont_data->write_setup = true; |
| cont_data->output.buffer = TSIOBufferCreate(); |
| cont_data->output.reader = TSIOBufferReaderAlloc(cont_data->output.buffer); |
| cont_data->output.vio = TSVConnWrite(cont_data->net_vc, cont_data->contp, cont_data->output.reader, INT_MAX); |
| // set the total length to write |
| TSVIONBytesSet(cont_data->output.vio, cont_data->pBody->getSize()); |
| SRDBG(TAG_SERV, "[%s] Done length=%d", __FUNCTION__, (int)cont_data->pBody->getSize()); |
| } else { |
| SRDBG(TAG_BAD, "[%s] Already init", __FUNCTION__); |
| } |
| } |
| |
| /*-----------------------------------------------------------------------------------------------*/ |
| static bool |
| handleRead(SContData *cont_data) |
| { |
| int avail = TSIOBufferReaderAvail(cont_data->input.reader); |
| if (avail == TS_ERROR) { |
| TSError("[%s] Error while getting number of bytes available", __FUNCTION__); |
| return false; |
| } |
| |
| SRDBG(TAG_SERV, "[%s] avail %d", __FUNCTION__, avail); |
| |
| int consumed = 0; |
| if (avail > 0) { |
| int64_t data_len; |
| const char *data; |
| TSIOBufferBlock block = TSIOBufferReaderStart(cont_data->input.reader); |
| while (block != nullptr) { |
| data = TSIOBufferBlockReadStart(block, cont_data->input.reader, &data_len); |
| |
| if (!cont_data->req_hdr_parsed) { |
| const char *endptr = data + data_len; |
| if (TSHttpHdrParseReq(cont_data->http_parser, cont_data->req_hdr_bufp, cont_data->req_hdr_loc, &data, endptr) == |
| TS_PARSE_DONE) { |
| cont_data->req_hdr_parsed = true; |
| SRDBG(TAG_SERV, "[%s] Parsed header", __FUNCTION__); |
| } |
| } |
| consumed += data_len; |
| block = TSIOBufferBlockNext(block); |
| } |
| } |
| |
| TSIOBufferReaderConsume(cont_data->input.reader, consumed); |
| |
| SRDBG(TAG_SERV, "[%s] Consumed %d bytes from input vio, avail: %d", __FUNCTION__, consumed, avail); |
| |
| // Modify the input VIO to reflect how much data we've completed. |
| TSVIONDoneSet(cont_data->input.vio, TSVIONDoneGet(cont_data->input.vio) + consumed); |
| |
| if (!cont_data->req_hdr_parsed) { |
| SRDBG(TAG_SERV, "[%s] Reenabling input vio need more header data", __FUNCTION__); |
| TSVIOReenable(cont_data->input.vio); |
| } |
| |
| return true; |
| } |
| |
| /*-----------------------------------------------------------------------------------------------*/ |
| static int |
| serverIntercept(TSCont contp, TSEvent event, void *edata) |
| { |
| SContData *cont_data = static_cast<SContData *>(TSContDataGet(contp)); |
| bool shutdown = false; |
| |
| switch (event) { |
| case TS_EVENT_NET_ACCEPT: |
| SRDBG(TAG_SERV, "[%s] {%u} net accept event %d", __FUNCTION__, cont_data->pBody->key_hash, event); |
| if (!connSetup(cont_data, static_cast<TSVConn>(edata))) { |
| SRDBG(TAG_BAD, "[%s] {%u} connSetup aleady initalized", __FUNCTION__, cont_data->pBody->key_hash); |
| } |
| break; |
| |
| case TS_EVENT_NET_ACCEPT_FAILED: |
| // not sure why this would happen, but it does |
| SRDBG(TAG_BAD, "[%s] {%u} net accept failed %d", __FUNCTION__, cont_data->pBody->key_hash, event); |
| shutdown = true; |
| break; |
| |
| case TS_EVENT_VCONN_READ_READY: |
| SRDBG(TAG_SERV, "[%s] {%u} vconn read ready event %d", __FUNCTION__, cont_data->pBody->key_hash, event); |
| if (!handleRead(cont_data)) { |
| SRDBG(TAG_BAD, "[%s] {%u} handleRead failed", __FUNCTION__, cont_data->pBody->key_hash); |
| break; |
| } |
| // VCONN_READ_READY should not happen again since we dont reenable input.vio |
| if (cont_data->req_hdr_parsed && !cont_data->write_setup) { |
| writeSetup(cont_data); |
| writeOutData(cont_data); |
| } |
| break; |
| |
| case TS_EVENT_VCONN_READ_COMPLETE: |
| case TS_EVENT_VCONN_EOS: |
| // intentional fall-through |
| SRDBG(TAG_SERV, "[%s] {%u} vconn read complete/eos event %d", __FUNCTION__, cont_data->pBody->key_hash, event); |
| // shutdown id we havent parsed headers and get a read eos/complete |
| if (!cont_data->req_hdr_parsed) { |
| SRDBG(TAG_BAD, "[%s] {%u} read complete but headers not parsed", __FUNCTION__, cont_data->pBody->key_hash); |
| shutdown = true; |
| } |
| break; |
| |
| case TS_EVENT_VCONN_WRITE_READY: |
| // SRDBG(TAG_SERV, "[%s] {%u} vconn write ready event %d", __FUNCTION__,cont_data->pBody->key_hash,event); |
| // trying not to write out the whole body at once if its big |
| writeOutData(cont_data); |
| break; |
| |
| case TS_EVENT_VCONN_WRITE_COMPLETE: |
| SRDBG(TAG_SERV, "[%s] {%u} vconn write complete event %d", __FUNCTION__, cont_data->pBody->key_hash, event); |
| shutdown = true; |
| break; |
| |
| case TS_EVENT_ERROR: |
| // todo: do some error handling here |
| SRDBG(TAG_BAD, "[%s] {%u} error event %d", __FUNCTION__, cont_data->pBody->key_hash, event); |
| shutdown = true; |
| break; |
| |
| default: |
| SRDBG(TAG_BAD, "[%s] {%u} default event %d", __FUNCTION__, cont_data->pBody->key_hash, event); |
| break; |
| } |
| |
| if (shutdown) { |
| connShutdownDataDestory(cont_data); |
| } |
| |
| return 1; |
| } |
| |
| /*-----------------------------------------------------------------------------------------------*/ |
| bool |
| serverInterceptSetup(TSHttpTxn txnp, BodyData *pBody, ConfigInfo *plugin_config) |
| { |
| // make sure we have data to deliver -- note called body but its headers+body |
| if (!pBody || (pBody->getSize() <= 0)) { |
| SRDBG(TAG_BAD, "[%s] must have body and size > 0", __FUNCTION__); |
| // remove async active if pBody exists but size <= 0 |
| if (pBody && pBody->key_hash_active) { |
| if (async_remove_active(pBody->key_hash, plugin_config)) { |
| SRDBG(TAG_BAD, "[%s] removed async active due to pbody size <= 0", __FUNCTION__); |
| } else { |
| SRDBG(TAG_BAD, "[%s] failed to delete async active when pbody size <= 0", __FUNCTION__); |
| } |
| } |
| return false; |
| } |
| // make sure we have a contp |
| TSCont contp = TSContCreate(serverIntercept, TSMutexCreate()); |
| if (!contp) { |
| SRDBG(TAG_BAD, "[%s] Could not create intercept contp", __FUNCTION__); |
| // remove async active if couldn't create intercept contp |
| if (pBody->key_hash_active) { |
| if (async_remove_active(pBody->key_hash, plugin_config)) { |
| SRDBG(TAG_BAD, "[%s] removed async active since couldn't create intercept contp", __FUNCTION__); |
| } else { |
| SRDBG(TAG_BAD, "[%s] failed to delete async active when couldn't create intercept contp", __FUNCTION__); |
| } |
| } |
| return false; |
| } |
| // create the data block for the cont |
| SContData *cont_data = new SContData(contp); |
| // set some pointers |
| cont_data->plugin_config = plugin_config; |
| cont_data->pBody = pBody; |
| // set the intercept and turn on caching |
| TSContDataSet(contp, cont_data); |
| TSHttpTxnServerIntercept(contp, txnp); |
| TSHttpTxnReqCacheableSet(txnp, 1); |
| TSHttpTxnRespCacheableSet(txnp, 1); |
| SRDBG(TAG_SERV, "[%s] {%u} Success length=%d", __FUNCTION__, cont_data->pBody->key_hash, (int)cont_data->pBody->getSize()); |
| return true; |
| } |
| |
| /*-----------------------------------------------------------------------------------------------*/ |