| /** @file |
| 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 "client.h" |
| |
| #include "Config.h" |
| #include "util.h" |
| |
| #include <cinttypes> |
| |
| // this is called once per transaction when the client sends a req header |
| bool |
| handle_client_req(TSCont contp, TSEvent event, Data *const data) |
| { |
| switch (event) { |
| case TS_EVENT_VCONN_READ_READY: |
| case TS_EVENT_VCONN_READ_COMPLETE: { |
| if (nullptr == data->m_http_parser) { |
| data->m_http_parser = TSHttpParserCreate(); |
| } |
| |
| // Read the header from the buffer |
| int64_t consumed = 0; |
| if (TS_PARSE_DONE != |
| data->m_req_hdrmgr.populateFrom(data->m_http_parser, data->m_dnstream.m_read.m_reader, TSHttpHdrParseReq, &consumed)) { |
| return false; |
| } |
| |
| // update the VIO |
| TSVIO const input_vio = data->m_dnstream.m_read.m_vio; |
| TSVIONDoneSet(input_vio, TSVIONDoneGet(input_vio) + consumed); |
| |
| // make the header manipulator |
| HttpHeader header(data->m_req_hdrmgr.m_buffer, data->m_req_hdrmgr.m_lochdr); |
| |
| // set the request url back to pristine in case of plugin stacking |
| header.setUrl(data->m_urlbuf, data->m_urlloc); |
| |
| header.setKeyVal(TS_MIME_FIELD_HOST, TS_MIME_LEN_HOST, data->m_hostname, data->m_hostlen); |
| |
| // default: whole file (unknown, wait for first server response) |
| Range rangebe; |
| |
| char rangestr[1024]; |
| int rangelen = sizeof(rangestr); |
| bool const hasRange = header.valueForKey(TS_MIME_FIELD_RANGE, TS_MIME_LEN_RANGE, rangestr, &rangelen, |
| 0); // <-- first range only |
| Config const *const conf = data->m_config; |
| if (hasRange) { |
| // write parsed header into slicer meta tag |
| header.setKeyVal(conf->m_skip_header.c_str(), conf->m_skip_header.size(), rangestr, rangelen); |
| bool const isRangeGood = rangebe.fromStringClosed(rangestr); |
| |
| if (isRangeGood) { |
| DEBUG_LOG("%p Partial content request", data); |
| data->m_statustype = TS_HTTP_STATUS_PARTIAL_CONTENT; |
| } else // signal a 416 needs to be formed and sent |
| { |
| DEBUG_LOG("%p Ill formed/unhandled range: %s", data, rangestr); |
| data->m_statustype = TS_HTTP_STATUS_REQUESTED_RANGE_NOT_SATISFIABLE; |
| |
| // First block will give Content-Length |
| rangebe = Range(0, conf->m_blockbytes); |
| } |
| } else { |
| DEBUG_LOG("%p Full content request", data); |
| static char const *const valstr = "-"; |
| static size_t const vallen = strlen(valstr); |
| header.setKeyVal(conf->m_skip_header.data(), conf->m_skip_header.size(), valstr, vallen); |
| data->m_statustype = TS_HTTP_STATUS_OK; |
| rangebe = Range(0, Range::maxval); |
| } |
| |
| if (Config::RefType::First == conf->m_reftype) { |
| data->m_blocknum = 0; |
| } else { |
| data->m_blocknum = rangebe.firstBlockFor(conf->m_blockbytes); |
| } |
| |
| data->m_req_range = rangebe; |
| |
| // remove ATS keys to avoid 404 loop |
| header.removeKey(TS_MIME_FIELD_VIA, TS_MIME_LEN_VIA); |
| header.removeKey(TS_MIME_FIELD_X_FORWARDED_FOR, TS_MIME_LEN_X_FORWARDED_FOR); |
| |
| // send block request to server |
| if (!request_block(contp, data)) { |
| abort(contp, data); |
| return false; |
| } |
| |
| // for subsequent blocks remove any conditionals which may fail |
| // an optimization would be to wait until the first block succeeds |
| header.removeKey(TS_MIME_FIELD_IF_MATCH, TS_MIME_LEN_IF_MATCH); |
| header.removeKey(TS_MIME_FIELD_IF_MODIFIED_SINCE, TS_MIME_LEN_IF_MODIFIED_SINCE); |
| header.removeKey(TS_MIME_FIELD_IF_NONE_MATCH, TS_MIME_LEN_IF_NONE_MATCH); |
| header.removeKey(TS_MIME_FIELD_IF_RANGE, TS_MIME_LEN_IF_RANGE); |
| header.removeKey(TS_MIME_FIELD_IF_UNMODIFIED_SINCE, TS_MIME_LEN_IF_UNMODIFIED_SINCE); |
| } break; |
| default: { |
| DEBUG_LOG("%p handle_client_req unhandled event %d %s", data, event, TSHttpEventNameLookup(event)); |
| } break; |
| } |
| |
| return true; |
| } |
| |
| // this is when the client starts asking us for more data |
| void |
| handle_client_resp(TSCont contp, TSEvent event, Data *const data) |
| { |
| switch (event) { |
| case TS_EVENT_VCONN_WRITE_READY: { |
| switch (data->m_blockstate) { |
| case BlockState::Fail: |
| case BlockState::PendingRef: |
| case BlockState::ActiveRef: { |
| TSVIO const output_vio = data->m_dnstream.m_write.m_vio; |
| int64_t const output_done = TSVIONDoneGet(output_vio); |
| int64_t const output_sent = data->m_bytessent; |
| |
| if (output_sent == output_done) { |
| DEBUG_LOG("Downstream output is done, shutting down"); |
| shutdown(contp, data); |
| } |
| } break; |
| |
| case BlockState::Pending: { |
| // throttle |
| TSVIO const output_vio = data->m_dnstream.m_write.m_vio; |
| int64_t const output_done = TSVIONDoneGet(output_vio); |
| int64_t const output_sent = data->m_bytessent; |
| int64_t const threshout = data->m_config->m_blockbytes; |
| int64_t const buffered = output_sent - output_done; |
| |
| if (threshout < buffered) { |
| DEBUG_LOG("%p handle_client_resp: throttling %" PRId64, data, buffered); |
| } else { |
| DEBUG_LOG("Starting next block request"); |
| if (!request_block(contp, data)) { |
| data->m_blockstate = BlockState::Fail; |
| return; |
| } |
| } |
| } break; |
| case BlockState::Passthru: { |
| } break; |
| default: |
| break; |
| } |
| } break; |
| case TS_EVENT_VCONN_WRITE_COMPLETE: { |
| if (dbg_ctl.on() && reader_avail_more_than(data->m_upstream.m_read.m_reader, 0)) { |
| int64_t const left = TSIOBufferReaderAvail(data->m_upstream.m_read.m_reader); |
| DEBUG_LOG("%p WRITE_COMPLETE called with %" PRId64 " bytes left", data, left); |
| } |
| |
| data->m_dnstream.close(); |
| if (!data->m_upstream.m_read.isOpen()) { |
| shutdown(contp, data); |
| } |
| } break; |
| default: { |
| DEBUG_LOG("%p handle_client_resp unhandled event %d %s", data, event, TSHttpEventNameLookup(event)); |
| } break; |
| } |
| } |