-
Notifications
You must be signed in to change notification settings - Fork 857
cachekey: capture cache key elements from headers #3995
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -437,6 +437,61 @@ CacheKey::appendPath(Pattern &pathCapture, Pattern &pathCaptureUri) | |
| } | ||
| } | ||
|
|
||
| template <class T> | ||
| void | ||
| CacheKey::processHeader(const String &name, const ConfigHeaders &config, T &dst, | ||
| void (*fun)(const ConfigHeaders &config, const String &name_s, const String &value_s, T &captures)) | ||
| { | ||
| TSMLoc field; | ||
|
|
||
| for (field = TSMimeHdrFieldFind(_buf, _hdrs, name.c_str(), name.size()); field != TS_NULL_MLOC; | ||
| field = ::nextDuplicate(_buf, _hdrs, field)) { | ||
| const char *value; | ||
| int vlen; | ||
| int count = TSMimeHdrFieldValuesCount(_buf, _hdrs, field); | ||
|
|
||
| for (int i = 0; i < count; ++i) { | ||
| value = TSMimeHdrFieldValueStringGet(_buf, _hdrs, field, i, &vlen); | ||
| if (value == nullptr || vlen == 0) { | ||
| CacheKeyDebug("missing value %d for header %s", i, name.c_str()); | ||
| continue; | ||
| } | ||
|
|
||
| String value_s(value, vlen); | ||
| fun(config, name, value_s, dst); | ||
| } | ||
| } | ||
| } | ||
|
|
||
| template <class T> | ||
| void | ||
| captureWholeHeaders(const ConfigHeaders &config, const String &name, const String &value, T &captures) | ||
| { | ||
| CacheKeyDebug("processing header %s", name.c_str()); | ||
| if (config.toBeAdded(name)) { | ||
| String header; | ||
| header.append(name).append(":").append(value); | ||
| captures.insert(header); | ||
| CacheKeyDebug("adding header '%s: %s'", name.c_str(), value.c_str()); | ||
| } else { | ||
| CacheKeyDebug("failed to find header '%s'", name.c_str()); | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. How noisy will this be? Will this print every time there is a header that is not captured? Maybe it helps enough in debugging that it is worth it. Just don't want to roll logs too frequently if we turn on debugging here.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think it should be ok, let's leave it (we can always trim). Here is why I think it should be ok... When one wants a header to be included or captured from the request into the cache key one would most likely expect the header or the capture to result in non-empty string on all requests, otherwise the cache key would become somehow unstructured (missing elements based on the traffic). So headers specified in For efficiency reason the plugin does not iterate over all headers in the request (at least when the plugin was developed it was not efficient to iterate over headers in ATS, as far as I know it is still the case, see the comments in the code for more details). The plugin iterates over the So the noise is not expected to be huge and the thinking is that while debugging the operator should know that an expected header or header capture was not found.
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. OK sounds good. Agree we can trim it later if it is an issue. |
||
| } | ||
| } | ||
|
|
||
| template <class T> | ||
| void | ||
| captureFromHeaders(const ConfigHeaders &config, const String &name, const String &value, T &captures) | ||
| { | ||
| CacheKeyDebug("processing capture from header %s", name.c_str()); | ||
| auto itMp = config.getCaptures().find(name); | ||
| if (config.getCaptures().end() != itMp) { | ||
| itMp->second->process(value, captures); | ||
| CacheKeyDebug("found capture pattern for header '%s'", name.c_str()); | ||
| } else { | ||
| CacheKeyDebug("failed to find header '%s'", name.c_str()); | ||
| } | ||
| } | ||
|
|
||
| /** | ||
| * @brief Append headers by following the rules specified in the header configuration object. | ||
| * @param config header-related configuration containing information about which headers need to be appended to the key. | ||
|
|
@@ -445,49 +500,35 @@ CacheKey::appendPath(Pattern &pathCapture, Pattern &pathCaptureUri) | |
| void | ||
| CacheKey::appendHeaders(const ConfigHeaders &config) | ||
| { | ||
| if (config.toBeRemoved() || config.toBeSkipped()) { | ||
| // Don't add any headers to the cache key. | ||
| return; | ||
| } | ||
|
|
||
| TSMLoc field; | ||
| StringSet hset; /* Sort and uniquify the header list in the cache key. */ | ||
|
|
||
| /* Iterating header by header is not efficient according to comments inside traffic server API, | ||
| * Iterate over an 'include'-kind of list to avoid header by header iteration. | ||
| * @todo: revisit this when (if?) adding regex matching for headers. */ | ||
| for (StringSet::iterator it = config.getInclude().begin(); it != config.getInclude().end(); ++it) { | ||
| String name_s = *it; | ||
|
|
||
| for (field = TSMimeHdrFieldFind(_buf, _hdrs, name_s.c_str(), name_s.size()); field != TS_NULL_MLOC; | ||
| field = ::nextDuplicate(_buf, _hdrs, field)) { | ||
| const char *value; | ||
| int vlen; | ||
| int count = TSMimeHdrFieldValuesCount(_buf, _hdrs, field); | ||
|
|
||
| for (int i = 0; i < count; ++i) { | ||
| value = TSMimeHdrFieldValueStringGet(_buf, _hdrs, field, i, &vlen); | ||
| if (value == nullptr || vlen == 0) { | ||
| CacheKeyDebug("missing value %d for header %s", i, name_s.c_str()); | ||
| continue; | ||
| } | ||
|
|
||
| String value_s(value, vlen); | ||
| if (!config.toBeRemoved() && !config.toBeSkipped()) { | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why was the original logic reversed and the early return removed here?
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Before adding Beforehand considered various ways to add the new
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This makes sense to me. Thanks. |
||
| /* Iterating header by header is not efficient according to comments inside traffic server API, | ||
| * Iterate over an 'include'-kind of list or the capture definitions to avoid header by header iteration. | ||
| * @todo: revisit this when (if?) adding regex matching for headers. */ | ||
|
|
||
| /* Adding whole headers, iterate over "--include-header" list */ | ||
| StringSet hdrSet; /* Sort and uniquify the header list in the cache key. */ | ||
| for (auto it = config.getInclude().begin(); it != config.getInclude().end(); ++it) { | ||
| processHeader(*it, config, hdrSet, captureWholeHeaders); | ||
| } | ||
|
|
||
| if (config.toBeAdded(name_s)) { | ||
| String header; | ||
| header.append(name_s).append(":").append(value_s); | ||
| hset.insert(header); | ||
| CacheKeyDebug("adding header => '%s: %s'", name_s.c_str(), value_s.c_str()); | ||
| } | ||
| } | ||
| /* Append to the cache key. It doesn't make sense to have the headers unordered in the cache key. */ | ||
| String headers_key = containerToString<StringSet, StringSet::const_iterator>(hdrSet, "", _separator); | ||
| if (!headers_key.empty()) { | ||
| append(headers_key); | ||
| } | ||
| } | ||
|
|
||
| /* It doesn't make sense to have the headers unordered in the cache key. */ | ||
| String headers_key = containerToString<StringSet, StringSet::const_iterator>(hset, "", _separator); | ||
| if (!headers_key.empty()) { | ||
| append(headers_key); | ||
| if (!config.getCaptures().empty()) { | ||
| /* Adding captures from headers, iterate over "--capture-header" definitions */ | ||
| StringVector hdrCaptures; | ||
| for (auto it = config.getCaptures().begin(); it != config.getCaptures().end(); ++it) { | ||
| processHeader(it->first, config, hdrCaptures, captureFromHeaders); | ||
| } | ||
|
|
||
| /* Append to the cache key. Add the captures in the order capture definitions are captured / specified */ | ||
| for (auto &capture : hdrCaptures) { | ||
| append(capture); | ||
| } | ||
| } | ||
| } | ||
|
|
||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -73,6 +73,10 @@ class CacheKey | |
| private: | ||
| CacheKey(); // disallow | ||
|
|
||
| template <class T> | ||
| void processHeader(const String &name_s, const ConfigHeaders &config, T &dst, | ||
| void (*fun)(const ConfigHeaders &config, const String &name_s, const String &value_s, T &captures)); | ||
|
|
||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If this is only a helper for the implementation, maybe we don't need to declare it here?
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. IMHO there is nothing wrong in having helper functions as
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yeah I agree. Just wondering why we need to add its declaration to a header if no one else is using it. We can leave it if there we want. |
||
| /* Information from the request */ | ||
| TSHttpTxn _txn; /**< @brief transaction handle */ | ||
| TSMBuffer _buf; /**< @brief marshal buffer */ | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think
Tis anystd::container<std:string>. Can you clarify that in comments or param name.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
With this code this is true, I can see the benefit of the clarification but I am not sure we should limit things. Also this is a local template which helped me process the headers + taking 2 different actions w/o duplicating code and it is not meant to be reused a lot (not part of the "public" interface of the class, etc). If you look through out the plugin code you can see I like lots of comments but it feels overkill in this case.