| /* |
| 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. |
| */ |
| |
| /** |
| * @file plugin.cc |
| * @brief traffic server plugin entry points. |
| */ |
| |
| #include <sstream> |
| #include <iomanip> |
| |
| #include "ts/ts.h" /* ATS API */ |
| |
| #include "ts/remap.h" /* TSRemapInterface, TSRemapStatus, apiInfo */ |
| |
| #include "common.h" |
| #include "configs.h" |
| #include "fetch.h" |
| #include "fetch_policy.h" |
| #include "headers.h" |
| |
| static const char * |
| getEventName(TSEvent event) |
| { |
| switch (event) { |
| case TS_EVENT_HTTP_CONTINUE: |
| return "TS_EVENT_HTTP_CONTINUE"; |
| case TS_EVENT_HTTP_ERROR: |
| return "TS_EVENT_HTTP_ERROR"; |
| case TS_EVENT_HTTP_READ_REQUEST_HDR: |
| return "TS_EVENT_HTTP_READ_REQUEST_HDR"; |
| case TS_EVENT_HTTP_OS_DNS: |
| return "TS_EVENT_HTTP_OS_DNS"; |
| case TS_EVENT_HTTP_SEND_REQUEST_HDR: |
| return "TS_EVENT_HTTP_SEND_REQUEST_HDR"; |
| case TS_EVENT_HTTP_READ_CACHE_HDR: |
| return "TS_EVENT_HTTP_READ_CACHE_HDR"; |
| case TS_EVENT_HTTP_READ_RESPONSE_HDR: |
| return "TS_EVENT_HTTP_READ_RESPONSE_HDR"; |
| case TS_EVENT_HTTP_SEND_RESPONSE_HDR: |
| return "TS_EVENT_HTTP_SEND_RESPONSE_HDR"; |
| case TS_EVENT_HTTP_REQUEST_TRANSFORM: |
| return "TS_EVENT_HTTP_REQUEST_TRANSFORM"; |
| case TS_EVENT_HTTP_RESPONSE_TRANSFORM: |
| return "TS_EVENT_HTTP_RESPONSE_TRANSFORM"; |
| case TS_EVENT_HTTP_SELECT_ALT: |
| return "TS_EVENT_HTTP_SELECT_ALT"; |
| case TS_EVENT_HTTP_TXN_START: |
| return "TS_EVENT_HTTP_TXN_START"; |
| case TS_EVENT_HTTP_TXN_CLOSE: |
| return "TS_EVENT_HTTP_TXN_CLOSE"; |
| case TS_EVENT_HTTP_SSN_START: |
| return "TS_EVENT_HTTP_SSN_START"; |
| case TS_EVENT_HTTP_SSN_CLOSE: |
| return "TS_EVENT_HTTP_SSN_CLOSE"; |
| case TS_EVENT_HTTP_CACHE_LOOKUP_COMPLETE: |
| return "TS_EVENT_HTTP_CACHE_LOOKUP_COMPLETE"; |
| case TS_EVENT_HTTP_PRE_REMAP: |
| return "TS_EVENT_HTTP_PRE_REMAP"; |
| case TS_EVENT_HTTP_POST_REMAP: |
| return "TS_EVENT_HTTP_POST_REMAP"; |
| default: |
| return "UNHANDLED"; |
| } |
| return "UNHANDLED"; |
| } |
| |
| static const char * |
| getCacheLookupResultName(TSCacheLookupResult result) |
| { |
| switch (result) { |
| case TS_CACHE_LOOKUP_MISS: |
| return "TS_CACHE_LOOKUP_MISS"; |
| break; |
| case TS_CACHE_LOOKUP_HIT_STALE: |
| return "TS_CACHE_LOOKUP_HIT_STALE"; |
| break; |
| case TS_CACHE_LOOKUP_HIT_FRESH: |
| return "TS_CACHE_LOOKUP_HIT_FRESH"; |
| break; |
| case TS_CACHE_LOOKUP_SKIPPED: |
| return "TS_CACHE_LOOKUP_SKIPPED"; |
| break; |
| default: |
| return "UNKNOWN_CACHE_LOOKUP_EVENT"; |
| break; |
| } |
| return "UNKNOWN_CACHE_LOOKUP_EVENT"; |
| } |
| |
| /** |
| * @brief Plugin initialization. |
| * @param apiInfo remap interface info pointer |
| * @param errBuf error message buffer |
| * @param errBufSize error message buffer size |
| * @return always TS_SUCCESS. |
| */ |
| TSReturnCode |
| TSRemapInit(TSRemapInterface *apiInfo, char *errBuf, int erroBufSize) |
| { |
| return TS_SUCCESS; |
| } |
| |
| /** |
| * @brief Plugin instance data. |
| */ |
| |
| struct PrefetchInstance { |
| PrefetchInstance() = default; |
| ; |
| |
| private: |
| PrefetchInstance(PrefetchInstance const &); |
| void operator=(BgFetchState const &); |
| |
| public: |
| PrefetchConfig _config; |
| BgFetchState *_state = nullptr; |
| }; |
| |
| /** |
| * brief Plugin transaction data. |
| */ |
| class PrefetchTxnData |
| { |
| public: |
| PrefetchTxnData(PrefetchInstance *inst) |
| : _inst(inst), _front(true), _firstPass(true), _fetchable(false), _status(TS_HTTP_STATUS_OK) |
| { |
| } |
| |
| bool |
| firstPass() const |
| { |
| return _firstPass; |
| } |
| |
| bool |
| secondPass() const |
| { |
| return !_firstPass; |
| } |
| |
| bool |
| frontend() const |
| { |
| return _front; |
| } |
| |
| bool |
| backend() const |
| { |
| return !_front; |
| } |
| |
| PrefetchInstance *_inst; /* Pointer to the plugin instance */ |
| |
| bool _front; /* front-end vs back-end */ |
| bool _firstPass; /* first vs second pass */ |
| |
| /* saves state between hooks */ |
| String _cachekey; /* cache key */ |
| bool _fetchable; /* saves the result of the attempt to fetch */ |
| TSHttpStatus _status; /* status to return to the UA */ |
| String _body; /* body to return to the UA */ |
| }; |
| |
| /** |
| * @brief Evaluate a math addition or subtraction expression. |
| * |
| * @param v string containing an expression, i.e. "3 + 4" |
| * @return string containing the result, i.e. "7" |
| */ |
| static String |
| evaluate(const String &v) |
| { |
| if (v.empty()) { |
| return String(""); |
| } |
| |
| /* Find out if width is specified (hence leading zeros are required if the width is bigger then the result width) */ |
| String stmt; |
| size_t len = 0; |
| size_t pos = v.find_first_of(':'); |
| if (String::npos != pos) { |
| stmt.assign(v.substr(0, pos)); |
| len = getValue(v.substr(pos + 1)); |
| } else { |
| stmt.assign(v); |
| } |
| PrefetchDebug("statement: '%s', formatting length: %zu", stmt.c_str(), len); |
| |
| int result = 0; |
| pos = stmt.find_first_of("+-"); |
| |
| if (String::npos == pos) { |
| result = getValue(stmt); |
| } else { |
| unsigned a = getValue(stmt.substr(0, pos)); |
| unsigned b = getValue(stmt.substr(pos + 1)); |
| |
| if ('+' == stmt[pos]) { |
| result = a + b; |
| } else { |
| result = a - b; |
| } |
| } |
| |
| std::ostringstream convert; |
| convert << std::setw(len) << std::setfill('0') << result; |
| PrefetchDebug("evaluation of '%s' resulted in '%s'", v.c_str(), convert.str().c_str()); |
| return convert.str(); |
| } |
| |
| /** |
| * @brief Expand+evaluate (in place) an expression surrounded with "{" and "}" and uses evaluate() to evaluate the math expression. |
| * |
| * @param s string containing an expression, i.e. "{3 + 4}" |
| * @return void |
| */ |
| static void |
| expand(String &s) |
| { |
| size_t cur = 0; |
| while (String::npos != cur) { |
| size_t start = s.find_first_of('{', cur); |
| size_t stop = s.find_first_of('}', start); |
| |
| if (String::npos != start && String::npos != stop) { |
| s.replace(start, stop - start + 1, evaluate(s.substr(start + 1, stop - start - 1))); |
| cur = stop + 1; |
| } else { |
| cur = stop; |
| } |
| } |
| } |
| |
| /** |
| * @brief Get the cachekey used for the particular object in this transaction. |
| * |
| * @param txnp HTTP transaction structure |
| * @param reqBuffer request TSMBuffer |
| * @param destination string reference to where the result is to be appended. |
| * @return true if success or false if failure |
| */ |
| bool |
| appendCacheKey(const TSHttpTxn txnp, const TSMBuffer reqBuffer, String &key) |
| { |
| bool ret = false; |
| TSMLoc keyLoc = TS_NULL_MLOC; |
| if (TS_SUCCESS == TSUrlCreate(reqBuffer, &keyLoc)) { |
| if (TS_SUCCESS == TSHttpTxnCacheLookupUrlGet(txnp, reqBuffer, keyLoc)) { |
| int urlLen = 0; |
| char *url = TSUrlStringGet(reqBuffer, keyLoc, &urlLen); |
| if (nullptr != url) { |
| key.append(url, urlLen); |
| PrefetchDebug("cache key: %s", key.c_str()); |
| TSfree(static_cast<void *>(url)); |
| ret = true; |
| } |
| } |
| TSHandleMLocRelease(reqBuffer, TS_NULL_MLOC, keyLoc); |
| } |
| |
| if (!ret) { |
| PrefetchError("failed to get cache key"); |
| } |
| return ret; |
| } |
| |
| /** |
| * @brief Find out if the object was found fresh in cache. |
| * |
| * This function finely controls if the pre-fetch should be scheduled or not. |
| * @param txnp HTTP transaction structure |
| * @return true - hit fresh, false - miss/stale/skipped or error |
| */ |
| static bool |
| foundFresh(TSHttpTxn txnp) |
| { |
| bool fresh = false; |
| int lookupStatus; |
| if (TS_SUCCESS == TSHttpTxnCacheLookupStatusGet(txnp, &lookupStatus)) { |
| PrefetchDebug("lookup status: %s", getCacheLookupResultName((TSCacheLookupResult)lookupStatus)); |
| if (TS_CACHE_LOOKUP_HIT_FRESH == lookupStatus) { |
| fresh = true; |
| } |
| } else { |
| /* Failed to get the lookup status, likely a previous plugin already prepared the client response w/o a cache lookup, |
| * we don't really know if the cache has a fresh object, so just don't trigger pre-fetch */ |
| PrefetchDebug("failed to check cache-ability"); |
| } |
| return fresh; |
| } |
| |
| /** |
| * @brief Check if the response from origin for N-th object is success (200 and 206) |
| * |
| * and only then schedule a pre-fetch for the next |
| * |
| * @param txnp HTTP transaction structure |
| * @return true - yes, false - no |
| */ |
| bool |
| isResponseGood(TSHttpTxn txnp) |
| { |
| bool good = false; |
| TSMBuffer respBuffer; |
| TSMLoc respHdrLoc; |
| if (TS_SUCCESS == TSHttpTxnServerRespGet(txnp, &respBuffer, &respHdrLoc)) { |
| TSHttpStatus status = TSHttpHdrStatusGet(respBuffer, respHdrLoc); |
| PrefetchDebug("origin response code: %d", status); |
| if (TS_HTTP_STATUS_PARTIAL_CONTENT == status || TS_HTTP_STATUS_OK == status) { |
| good = true; |
| } |
| /* Release the response MLoc */ |
| TSHandleMLocRelease(respBuffer, TS_NULL_MLOC, respHdrLoc); |
| } else { |
| /* Failed to get the origin response, possible cause could be a origin connection problems or timeouts or |
| * a previous plugin could have already prepared the client response w/o going to origin server */ |
| PrefetchDebug("failed to get origin response"); |
| } |
| return good; |
| } |
| |
| /** |
| * @brief get the pristin URL path |
| * |
| * @param txnp HTTP transaction structure |
| * @return pristine URL path |
| */ |
| static String |
| getPristineUrlPath(TSHttpTxn txnp) |
| { |
| String pristinePath; |
| TSMLoc pristineUrlLoc; |
| TSMBuffer reqBuffer; |
| |
| if (TS_SUCCESS == TSHttpTxnPristineUrlGet(txnp, &reqBuffer, &pristineUrlLoc)) { |
| int pathLen = 0; |
| const char *path = TSUrlPathGet(reqBuffer, pristineUrlLoc, &pathLen); |
| if (nullptr != path) { |
| PrefetchDebug("path: '%.*s'", pathLen, path); |
| pristinePath.assign(path, pathLen); |
| } else { |
| PrefetchError("failed to get pristine URL path"); |
| } |
| TSHandleMLocRelease(reqBuffer, TS_NULL_MLOC, pristineUrlLoc); |
| } else { |
| PrefetchError("failed to get pristine URL"); |
| } |
| return pristinePath; |
| } |
| |
| /** |
| * @brief short-cut to set the response . |
| */ |
| TSEvent |
| shortcutResponse(PrefetchTxnData *data, TSHttpStatus status, const char *body, TSEvent event) |
| { |
| data->_status = status; |
| data->_body.assign(body); |
| return event; |
| } |
| |
| /** |
| * @brief Checks if we are still supposed to schedule a background fetch based on whether the object is in the cache. |
| * It is 'fetchable' only if not a fresh hit. |
| * |
| * @param txnp HTTP transaction structure |
| * @param data transaction data |
| * @return true if fetchable and false if not. |
| */ |
| static bool |
| isFetchable(TSHttpTxn txnp, PrefetchTxnData *data) |
| { |
| bool fetchable = false; |
| BgFetchState *state = data->_inst->_state; |
| if (!foundFresh(txnp)) { |
| /* Schedule fetch only if not in cache */ |
| PrefetchDebug("object to be fetched"); |
| fetchable = true; |
| } else { |
| PrefetchDebug("object already in cache or to be skipped"); |
| state->incrementMetric(FETCH_ALREADY_CACHED); |
| state->incrementMetric(FETCH_TOTAL); |
| } |
| return fetchable; |
| } |
| |
| /** |
| * @brief Find out if the current response to trigger a background prefetch. |
| * |
| * Pre-fetch only on HTTP codes 200 and 206 or object found in cache (previous good response) |
| * |
| * @param txnp HTTP transaction structure |
| * @return true - trigger prefetch, false - don't trigger. |
| */ |
| static bool |
| respToTriggerPrefetch(TSHttpTxn txnp) |
| { |
| bool trigger = false; |
| if (foundFresh(txnp)) { |
| /* If found in cache and fresh trigger next (same as good response from origin) */ |
| PrefetchDebug("trigger background fetch (cached)"); |
| trigger = true; |
| } else if (isResponseGood(txnp)) { |
| /* Trigger all necessary background fetches based on the next path pattern */ |
| PrefetchDebug("trigger background fetch (good origin response)"); |
| trigger = true; |
| } else { |
| PrefetchDebug("don't trigger background fetch"); |
| } |
| return trigger; |
| } |
| |
| /** |
| * @brief Callback function that handles necessary foreground / background fetch operations. |
| * |
| * @param contp continuation associated with this function. |
| * @param event corresponding event triggered at different hooks. |
| * @param edata HTTP transaction structures. |
| * @return always 0 |
| */ |
| int |
| contHandleFetch(const TSCont contp, TSEvent event, void *edata) |
| { |
| PrefetchTxnData *data = static_cast<PrefetchTxnData *>(TSContDataGet(contp)); |
| TSHttpTxn txnp = static_cast<TSHttpTxn>(edata); |
| PrefetchConfig &config = data->_inst->_config; |
| BgFetchState *state = data->_inst->_state; |
| TSMBuffer reqBuffer; |
| TSMLoc reqHdrLoc; |
| |
| PrefetchDebug("event: %s (%d)", getEventName(event), event); |
| |
| TSEvent retEvent = TS_EVENT_HTTP_CONTINUE; |
| |
| if ((event == TS_EVENT_HTTP_POST_REMAP || event == TS_EVENT_HTTP_CACHE_LOOKUP_COMPLETE || |
| event == TS_EVENT_HTTP_SEND_RESPONSE_HDR) && |
| TS_SUCCESS != TSHttpTxnClientReqGet(txnp, &reqBuffer, &reqHdrLoc)) { |
| PrefetchError("failed to get client request"); |
| TSHttpTxnReenable(txnp, TS_EVENT_HTTP_ERROR); |
| return 0; |
| } |
| |
| switch (event) { |
| case TS_EVENT_HTTP_POST_REMAP: { |
| /* Use the cache key since this has better lookup behavior when using plugins like the cachekey plugin, |
| * for example multiple URIs can match a single cache key */ |
| if (data->frontend() && data->secondPass()) { |
| /* Create a separate cache key name space to be used only for front-end and second-pass fetch policy checks. */ |
| data->_cachekey.assign("/prefetch"); |
| } |
| if (!appendCacheKey(txnp, reqBuffer, data->_cachekey)) { |
| PrefetchError("failed to get the cache key"); |
| TSHttpTxnReenable(txnp, TS_EVENT_HTTP_ERROR); |
| return 0; |
| } |
| |
| if (data->frontend()) { |
| /* front-end instance */ |
| if (data->firstPass()) { |
| /* first-pass */ |
| if (!config.isExactMatch()) { |
| data->_fetchable = state->acquire(data->_cachekey); |
| PrefetchDebug("request is %s fetchable", data->_fetchable ? " " : " not "); |
| } |
| } |
| } |
| } break; |
| |
| case TS_EVENT_HTTP_CACHE_LOOKUP_COMPLETE: { |
| if (data->frontend()) { |
| /* front-end instance */ |
| if (data->secondPass()) { |
| /* second-pass */ |
| data->_fetchable = state->acquire(data->_cachekey); |
| data->_fetchable = data->_fetchable && state->uniqueAcquire(data->_cachekey); |
| PrefetchDebug("request is %s fetchable", data->_fetchable ? " " : " not "); |
| |
| if (isFetchable(txnp, data)) { |
| if (!data->_fetchable) { |
| /* Cancel the requested fetch */ |
| retEvent = shortcutResponse(data, TS_HTTP_STATUS_ALREADY_REPORTED, "fetch not scheduled\n", TS_EVENT_HTTP_ERROR); |
| } else { |
| /* Fetch */ |
| } |
| } else { |
| retEvent = shortcutResponse(data, TS_HTTP_STATUS_ALREADY_REPORTED, "fetch not scheduled\n", TS_EVENT_HTTP_ERROR); |
| } |
| } |
| } else { |
| /* back-end instance */ |
| if (data->firstPass()) { |
| if (isFetchable(txnp, data)) { |
| if (BgFetch::schedule(state, config, /* askPermission */ true, reqBuffer, reqHdrLoc, txnp, nullptr, 0, data->_cachekey)) { |
| retEvent = shortcutResponse(data, TS_HTTP_STATUS_OK, "fetch scheduled\n", TS_EVENT_HTTP_ERROR); |
| } else { |
| retEvent = shortcutResponse(data, TS_HTTP_STATUS_ALREADY_REPORTED, "fetch not scheduled\n", TS_EVENT_HTTP_ERROR); |
| } |
| } else { |
| retEvent = shortcutResponse(data, TS_HTTP_STATUS_ALREADY_REPORTED, "fetch not scheduled\n", TS_EVENT_HTTP_ERROR); |
| } |
| } |
| } |
| } break; |
| |
| case TS_EVENT_HTTP_SEND_RESPONSE_HDR: { |
| if (data->frontend()) { |
| /* front-end instance */ |
| |
| if (data->firstPass() && data->_fetchable && !config.getNextPath().empty() && respToTriggerPrefetch(txnp)) { |
| /* Trigger all necessary background fetches based on the next path pattern */ |
| |
| String currentPath = getPristineUrlPath(txnp); |
| if (!currentPath.empty()) { |
| unsigned total = config.getFetchCount(); |
| for (unsigned i = 0; i < total; ++i) { |
| PrefetchDebug("generating prefetch request %d/%d", i + 1, total); |
| String expandedPath; |
| |
| if (config.getNextPath().replace(currentPath, expandedPath)) { |
| PrefetchDebug("replaced: %s", expandedPath.c_str()); |
| expand(expandedPath); |
| PrefetchDebug("expanded: %s", expandedPath.c_str()); |
| |
| BgFetch::schedule(state, config, /* askPermission */ false, reqBuffer, reqHdrLoc, txnp, expandedPath.c_str(), |
| expandedPath.length(), data->_cachekey); |
| } else { |
| /* We should be here only if the pattern replacement fails (match already checked) */ |
| PrefetchError("failed to process the pattern"); |
| |
| /* If the first or any matches fails there must be something wrong so don't continue */ |
| break; |
| } |
| currentPath.assign(expandedPath); |
| } |
| } else { |
| PrefetchDebug("failed to get current path"); |
| } |
| } |
| } |
| |
| if ((data->backend() && data->firstPass()) || (data->frontend() && data->secondPass() && !data->_body.empty())) { |
| TSMBuffer bufp; |
| TSMLoc hdrLoc; |
| |
| if (TS_SUCCESS == TSHttpTxnClientRespGet(txnp, &bufp, &hdrLoc)) { |
| const char *reason = TSHttpHdrReasonLookup(data->_status); |
| int reasonLen = strlen(reason); |
| TSHttpHdrStatusSet(bufp, hdrLoc, data->_status); |
| TSHttpHdrReasonSet(bufp, hdrLoc, reason, reasonLen); |
| PrefetchDebug("set response: %d %.*s '%s'", data->_status, reasonLen, reason, data->_body.c_str()); |
| |
| char *buf = static_cast<char *>(TSmalloc(data->_body.length() + 1)); |
| sprintf(buf, "%s", data->_body.c_str()); |
| TSHttpTxnErrorBodySet(txnp, buf, strlen(buf), nullptr); |
| |
| setHeader(bufp, hdrLoc, TS_MIME_FIELD_CACHE_CONTROL, TS_MIME_LEN_CACHE_CONTROL, TS_HTTP_VALUE_NO_STORE, |
| TS_HTTP_LEN_NO_STORE); |
| |
| TSHandleMLocRelease(bufp, TS_NULL_MLOC, hdrLoc); |
| } else { |
| PrefetchError("failed to retrieve client response header"); |
| } |
| } |
| } break; |
| |
| case TS_EVENT_HTTP_TXN_CLOSE: { |
| if (data->_fetchable) { |
| if (data->frontend()) { |
| /* front-end */ |
| if (data->firstPass()) { |
| /* first-pass */ |
| if (!config.isExactMatch()) { |
| state->release(data->_cachekey); |
| } |
| } else { |
| /* second-pass */ |
| state->uniqueRelease(data->_cachekey); |
| state->release(data->_cachekey); |
| } |
| } |
| } |
| |
| /* Destroy the txn continuation and its data */ |
| delete data; |
| TSContDestroy(contp); |
| } break; |
| |
| default: { |
| PrefetchError("unhandled event: %s(%d)", getEventName(event), event); |
| } break; |
| } |
| |
| /* Release the request MLoc */ |
| if (event == TS_EVENT_HTTP_POST_REMAP || event == TS_EVENT_HTTP_CACHE_LOOKUP_COMPLETE || |
| event == TS_EVENT_HTTP_SEND_RESPONSE_HDR) { |
| TSHandleMLocRelease(reqBuffer, TS_NULL_MLOC, reqHdrLoc); |
| } |
| |
| /* Reenable and continue with the state machine. */ |
| TSHttpTxnReenable(txnp, retEvent); |
| return 0; |
| } |
| |
| /** |
| * @brief Plugin new instance entry point. |
| * |
| * Processes the configuration and initializes the plugin instance. |
| * @param argc plugin arguments number |
| * @param argv plugin arguments |
| * @param instance new plugin instance pointer (initialized in this function) |
| * @param errBuf error message buffer |
| * @param errBufSize error message buffer size |
| * @return TS_SUCCES if success or TS_ERROR if failure |
| */ |
| TSReturnCode |
| TSRemapNewInstance(int argc, char *argv[], void **instance, char *errBuf, int errBufSize) |
| { |
| bool failed = true; |
| |
| PrefetchInstance *inst = new PrefetchInstance(); |
| if (nullptr != inst) { |
| if (inst->_config.init(argc, argv)) { |
| inst->_state = BgFetchStates::get()->getStateByName(inst->_config.getNameSpace()); |
| if (nullptr != inst->_state) { |
| failed = !inst->_state->init(inst->_config); |
| } |
| } |
| } |
| |
| if (failed) { |
| PrefetchError("failed to initialize the plugin"); |
| delete inst; |
| *instance = nullptr; |
| return TS_ERROR; |
| } |
| |
| *instance = inst; |
| return TS_SUCCESS; |
| } |
| |
| /** |
| * @brief Plugin instance deletion clean-up entry point. |
| * @param plugin instance pointer. |
| */ |
| void |
| TSRemapDeleteInstance(void *instance) |
| { |
| PrefetchInstance *inst = static_cast<PrefetchInstance *>(instance); |
| delete inst; |
| } |
| |
| /** |
| * @brief Organizes the background fetch by registering necessary hooks, by identifying front-end vs back-end, first vs second |
| * pass. |
| * |
| * Remap is never done, continue with next in chain. |
| * @param instance plugin instance pointer |
| * @param txnp transaction handle |
| * @param rri remap request info pointer |
| * @return always TSREMAP_NO_REMAP |
| */ |
| TSRemapStatus |
| TSRemapDoRemap(void *instance, TSHttpTxn txnp, TSRemapRequestInfo *rri) |
| { |
| PrefetchInstance *inst = static_cast<PrefetchInstance *>(instance); |
| |
| if (nullptr != inst) { |
| PrefetchConfig &config = inst->_config; |
| |
| int methodLen = 0; |
| const char *method = TSHttpHdrMethodGet(rri->requestBufp, rri->requestHdrp, &methodLen); |
| const String &header = config.getApiHeader(); |
| if (nullptr != method && methodLen == TS_HTTP_LEN_GET && 0 == memcmp(TS_HTTP_METHOD_GET, method, TS_HTTP_LEN_GET)) { |
| bool front = config.isFront(); |
| bool firstPass = false; |
| if (headerExist(rri->requestBufp, rri->requestHdrp, header.c_str(), header.length())) { |
| PrefetchDebug("%s: found %.*s", front ? "front-end" : "back-end", (int)header.length(), header.c_str()); |
| /* On front-end: presence of header means second-pass, on back-end means first-pass. */ |
| firstPass = !front; |
| } else { |
| /* On front-end: lack of header means first-pass, on back-end means second-pass. */ |
| firstPass = front; |
| } |
| |
| /* Make sure we handle only URLs that match the path pattern on the front-end + first-pass, cancel otherwise */ |
| bool handleFetch = true; |
| if (front && firstPass) { |
| /* Front-end plug-in instance + first pass. */ |
| if (config.getNextPath().empty()) { |
| /* No next path pattern specified then pass this request untouched. */ |
| PrefetchDebug("next object pattern not specified, skip"); |
| handleFetch = false; |
| } else { |
| /* Next path pattern specified hence try to match. */ |
| String pristinePath = getPristineUrlPath(txnp); |
| if (!pristinePath.empty()) { |
| if (config.getNextPath().match(pristinePath)) { |
| /* Matched - handle the request */ |
| PrefetchDebug("matched next object pattern"); |
| inst->_state->incrementMetric(FETCH_MATCH_YES); |
| } else { |
| /* Next path pattern specified but did not match. */ |
| PrefetchDebug("failed to match next object pattern, skip"); |
| inst->_state->incrementMetric(FETCH_MATCH_NO); |
| handleFetch = false; |
| } |
| } else { |
| PrefetchDebug("failed to get path to (pre)match"); |
| } |
| } |
| } |
| |
| if (handleFetch) { |
| PrefetchTxnData *data = new PrefetchTxnData(inst); |
| if (nullptr != data) { |
| data->_front = front; |
| data->_firstPass = firstPass; |
| |
| TSCont cont = TSContCreate(contHandleFetch, TSMutexCreate()); |
| TSContDataSet(cont, static_cast<void *>(data)); |
| |
| TSHttpTxnHookAdd(txnp, TS_HTTP_POST_REMAP_HOOK, cont); |
| TSHttpTxnHookAdd(txnp, TS_HTTP_CACHE_LOOKUP_COMPLETE_HOOK, cont); |
| TSHttpTxnHookAdd(txnp, TS_HTTP_SEND_RESPONSE_HDR_HOOK, cont); |
| TSHttpTxnHookAdd(txnp, TS_HTTP_TXN_CLOSE_HOOK, cont); |
| } else { |
| PrefetchError("failed to allocate transaction data object"); |
| } |
| } |
| } else { |
| PrefetchDebug("not a GET method (%.*s), skipping", methodLen, method); |
| } |
| } else { |
| PrefetchError("could not get prefetch instance"); |
| } |
| |
| return TSREMAP_NO_REMAP; |
| } |