Skip to content

Commit fd23191

Browse files
authored
[compress] Add range request control option (#11975)
* [compress] Add range request control option * Release headers * Format
1 parent 6f3d036 commit fd23191

File tree

8 files changed

+334
-30
lines changed

8 files changed

+334
-30
lines changed

doc/admin-guide/plugins/compress.en.rst

+15-2
Original file line numberDiff line numberDiff line change
@@ -106,8 +106,21 @@ by the origin. Enabled by default.
106106
range-request
107107
-------------
108108

109-
When set to ``true``, causes |TS| to compress responses to Range Requests.
110-
Disabled by default. Setting this to true while setting cache to false leads to delivering corrupted content.
109+
This config controls behavior of this plugin when a client send ``Range`` header and ``Accept-Encoding`` header in the same time.
110+
111+
============== =================================================================
112+
Value Description
113+
============== =================================================================
114+
ignore-range Remove ``Range`` header if the request has both headers (Default)
115+
false Same as ``ignore-range`` for compatiblity
116+
no-compression Remove ``Accept-Encoding`` header if the request has both headers
117+
none Do nothing
118+
true Same as ``none`` for compatibility
119+
============== =================================================================
120+
121+
.. important::
122+
123+
Do NOT set this to ``none`` (or ``true``) if the cache config is set to ``false``. This combination will deliver corrupted content.
111124

112125
compressible-content-type
113126
-------------------------

plugins/compress/compress.cc

+60-13
Original file line numberDiff line numberDiff line change
@@ -24,8 +24,11 @@
2424
#include <cstring>
2525
#include <zlib.h>
2626

27+
#include "ts/apidefs.h"
2728
#include "tscore/ink_config.h"
2829

30+
#include <tsutil/PostScript.h>
31+
2932
#if HAVE_BROTLI_ENCODE_H
3033
#include <brotli/encode.h>
3134
#endif
@@ -76,6 +79,59 @@ static TSMutex compress_config_mutex = nullptr;
7679
Configuration *cur_config = nullptr;
7780
Configuration *prev_config = nullptr;
7881

82+
namespace
83+
{
84+
/**
85+
If client request has both of Range and Accept-Encoding header, follow range-request config.
86+
*/
87+
void
88+
handle_range_request(TSMBuffer req_buf, TSMLoc req_loc, HostConfiguration *hc)
89+
{
90+
TSMLoc accept_encoding_hdr_field =
91+
TSMimeHdrFieldFind(req_buf, req_loc, TS_MIME_FIELD_ACCEPT_ENCODING, TS_MIME_LEN_ACCEPT_ENCODING);
92+
ts::PostScript accept_encoding_defer([&]() -> void { TSHandleMLocRelease(req_buf, req_loc, accept_encoding_hdr_field); });
93+
if (accept_encoding_hdr_field == TS_NULL_MLOC) {
94+
return;
95+
}
96+
97+
TSMLoc range_hdr_field = TSMimeHdrFieldFind(req_buf, req_loc, TS_MIME_FIELD_RANGE, TS_MIME_LEN_RANGE);
98+
ts::PostScript range_defer([&]() -> void { TSHandleMLocRelease(req_buf, req_loc, range_hdr_field); });
99+
if (range_hdr_field == TS_NULL_MLOC) {
100+
return;
101+
}
102+
103+
debug("Both of Accept-Encoding and Range header are found in the request");
104+
105+
switch (hc->range_request_ctl()) {
106+
case RangeRequestCtrl::IGNORE_RANGE: {
107+
debug("Remove the Range header by ignore-range config");
108+
while (range_hdr_field) {
109+
TSMLoc next_dup = TSMimeHdrFieldNextDup(req_buf, req_loc, range_hdr_field);
110+
TSMimeHdrFieldDestroy(req_buf, req_loc, range_hdr_field);
111+
TSHandleMLocRelease(req_buf, req_loc, range_hdr_field);
112+
range_hdr_field = next_dup;
113+
}
114+
break;
115+
}
116+
case RangeRequestCtrl::NO_COMPRESSION: {
117+
debug("Remove the Accept-Encoding header by no-compression config");
118+
while (accept_encoding_hdr_field) {
119+
TSMLoc next_dup = TSMimeHdrFieldNextDup(req_buf, req_loc, accept_encoding_hdr_field);
120+
TSMimeHdrFieldDestroy(req_buf, req_loc, accept_encoding_hdr_field);
121+
TSHandleMLocRelease(req_buf, req_loc, accept_encoding_hdr_field);
122+
accept_encoding_hdr_field = next_dup;
123+
}
124+
break;
125+
}
126+
case RangeRequestCtrl::NONE:
127+
[[fallthrough]];
128+
default:
129+
debug("Do nothing by none config");
130+
break;
131+
}
132+
}
133+
} // namespace
134+
79135
static Data *
80136
data_alloc(int compression_type, int compression_algorithms)
81137
{
@@ -635,7 +691,7 @@ transformable(TSHttpTxn txnp, bool server, HostConfiguration *host_configuration
635691
/* Client request header */
636692
TSMBuffer cbuf;
637693
TSMLoc chdr;
638-
TSMLoc cfield, rfield;
694+
TSMLoc cfield;
639695

640696
const char *value;
641697
int len;
@@ -677,17 +733,6 @@ transformable(TSHttpTxn txnp, bool server, HostConfiguration *host_configuration
677733
return 0;
678734
}
679735

680-
// check if Range Requests are cacheable
681-
bool range_request = host_configuration->range_request();
682-
rfield = TSMimeHdrFieldFind(cbuf, chdr, TS_MIME_FIELD_RANGE, TS_MIME_LEN_RANGE);
683-
if (rfield != TS_NULL_MLOC && !range_request) {
684-
debug("Range header found in the request and range_request is configured as false, not compressible");
685-
TSHandleMLocRelease(cbuf, chdr, rfield);
686-
TSHandleMLocRelease(cbuf, TS_NULL_MLOC, chdr);
687-
TSHandleMLocRelease(bufp, TS_NULL_MLOC, hdr_loc);
688-
return 0;
689-
}
690-
691736
// the only compressible method is currently GET.
692737
int method_length;
693738
const char *method = TSHttpHdrMethodGet(cbuf, chdr, &method_length);
@@ -922,7 +967,8 @@ transform_plugin(TSCont contp, TSEvent event, void *edata)
922967
* 2. For global plugin, get host configuration from global config
923968
* For remap plugin, get host configuration from configs populated through remap
924969
* 3. Check for Accept encoding
925-
* 4. Schedules TS_HTTP_CACHE_LOOKUP_COMPLETE_HOOK and TS_HTTP_TXN_CLOSE_HOOK for
970+
* 4. Remove Range header
971+
* 5. Schedules TS_HTTP_CACHE_LOOKUP_COMPLETE_HOOK and TS_HTTP_TXN_CLOSE_HOOK for
926972
* further processing
927973
*/
928974
static void
@@ -957,6 +1003,7 @@ handle_request(TSHttpTxn txnp, Configuration *config)
9571003

9581004
info("Kicking off compress plugin for request");
9591005
normalize_accept_encoding(txnp, req_buf, req_loc);
1006+
handle_range_request(req_buf, req_loc, hc);
9601007
TSHttpTxnHookAdd(txnp, TS_HTTP_CACHE_LOOKUP_COMPLETE_HOOK, transform_contp);
9611008
TSHttpTxnHookAdd(txnp, TS_HTTP_TXN_CLOSE_HOOK, transform_contp); // To release the config
9621009
}

plugins/compress/configuration.cc

+16-3
Original file line numberDiff line numberDiff line change
@@ -270,6 +270,19 @@ HostConfiguration::compression_algorithms()
270270
return compression_algorithms_;
271271
}
272272

273+
void
274+
HostConfiguration::set_range_request(const std::string &token)
275+
{
276+
// "true" and "false" are compatibility with old version, will be removed
277+
if (token == "false" || token == "ignore-range") {
278+
range_request_ctl_ = RangeRequestCtrl::IGNORE_RANGE;
279+
} else if (token == "true" || token == "none") {
280+
range_request_ctl_ = RangeRequestCtrl::NONE;
281+
} else if (token == "no-compression") {
282+
range_request_ctl_ = RangeRequestCtrl::NO_COMPRESSION;
283+
}
284+
}
285+
273286
Configuration *
274287
Configuration::Parse(const char *path)
275288
{
@@ -383,7 +396,7 @@ Configuration::Parse(const char *path)
383396
state = kParseStart;
384397
break;
385398
case kParseRangeRequest:
386-
current_host_configuration->set_range_request(token == "true");
399+
current_host_configuration->set_range_request(token);
387400
state = kParseStart;
388401
break;
389402
case kParseFlush:
@@ -406,8 +419,8 @@ Configuration::Parse(const char *path)
406419
current_host_configuration->update_defaults();
407420

408421
// Check combination of configs
409-
if (!current_host_configuration->cache() && current_host_configuration->range_request()) {
410-
warning("Combination of 'cache false' and 'range-request true' might deliver corrupted content");
422+
if (!current_host_configuration->cache() && current_host_configuration->range_request_ctl() == RangeRequestCtrl::NONE) {
423+
warning("Combination of 'cache false' and 'range-request none' might deliver corrupted content");
411424
}
412425

413426
if (state != kParseStart) {

plugins/compress/configuration.h

+13-12
Original file line numberDiff line numberDiff line change
@@ -41,14 +41,19 @@ enum CompressionAlgorithm {
4141
ALGORITHM_BROTLI = 4 // For bit manipulations
4242
};
4343

44+
enum class RangeRequestCtrl : int {
45+
IGNORE_RANGE = 0, ///< Ignore Range Header (default)
46+
NO_COMPRESSION = 1, ///< Do NOT compress if it's a range request
47+
NONE = 2, ///< Do nothing
48+
};
49+
4450
class HostConfiguration : private atscppapi::noncopyable
4551
{
4652
public:
4753
explicit HostConfiguration(const std::string &host)
4854
: host_(host),
4955
enabled_(true),
5056
cache_(true),
51-
range_request_(false),
5257
remove_accept_encoding_(false),
5358
flush_(false),
5459
compression_algorithms_(ALGORITHM_GZIP),
@@ -66,15 +71,10 @@ class HostConfiguration : private atscppapi::noncopyable
6671
{
6772
enabled_ = x;
6873
}
69-
bool
70-
range_request()
71-
{
72-
return range_request_;
73-
}
74-
void
75-
set_range_request(bool x)
74+
RangeRequestCtrl
75+
range_request_ctl()
7676
{
77-
range_request_ = x;
77+
return range_request_ctl_;
7878
}
7979
bool
8080
cache()
@@ -137,19 +137,20 @@ class HostConfiguration : private atscppapi::noncopyable
137137
bool is_status_code_compressible(const TSHttpStatus status_code) const;
138138
void add_compression_algorithms(std::string &algorithms);
139139
int compression_algorithms();
140+
void set_range_request(const std::string &token);
140141

141142
private:
142143
std::string host_;
143144
bool enabled_;
144145
bool cache_;
145-
bool range_request_;
146146
bool remove_accept_encoding_;
147147
bool flush_;
148148
int compression_algorithms_;
149149
unsigned int minimum_content_length_;
150150

151-
StringContainer compressible_content_types_;
152-
StringContainer allows_;
151+
RangeRequestCtrl range_request_ctl_;
152+
StringContainer compressible_content_types_;
153+
StringContainer allows_;
153154
// maintain backwards compatibility/usability out of the box
154155
std::set<TSHttpStatus> compressible_status_codes_ = {TS_HTTP_STATUS_OK, TS_HTTP_STATUS_PARTIAL_CONTENT,
155156
TS_HTTP_STATUS_NOT_MODIFIED};
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
'''
2+
Test compress plugin with range request
3+
'''
4+
# Licensed to the Apache Software Foundation (ASF) under one
5+
# or more contributor license agreements. See the NOTICE file
6+
# distributed with this work for additional information
7+
# regarding copyright ownership. The ASF licenses this file
8+
# to you under the Apache License, Version 2.0 (the # "License"); you may not use this file except in compliance
9+
# with the License. You may obtain a copy of the License at
10+
#
11+
# http://www.apache.org/licenses/LICENSE-2.0
12+
#
13+
# Unless required by applicable law or agreed to in writing, software
14+
# distributed under the License is distributed on an "AS IS" BASIS,
15+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16+
# See the License for the specific language governing permissions and
17+
# limitations under the License.
18+
19+
Test.Summary = '''
20+
Test compress plugin with range request
21+
'''
22+
23+
Test.SkipUnless(Condition.PluginExists('compress.so'))
24+
25+
26+
class CompressPluginTest:
27+
replayFile = "replay/compress-and-range.replay.yaml"
28+
29+
def __init__(self):
30+
self.setupOriginServer()
31+
self.setupTS()
32+
33+
def setupOriginServer(self):
34+
self.server = Test.MakeVerifierServerProcess("verifier-server", self.replayFile)
35+
36+
def setupTS(self):
37+
self.ts = Test.MakeATSProcess("ts")
38+
self.ts.Disk.records_config.update(
39+
{
40+
"proxy.config.diags.debug.enabled": 1,
41+
"proxy.config.diags.debug.tags": "http|compress",
42+
"proxy.config.http.insert_response_via_str": 2,
43+
})
44+
45+
self.ts.Setup.Copy("etc/cache-true-ignore-range.config")
46+
self.ts.Setup.Copy("etc/cache-true-no-compression.config")
47+
48+
self.ts.Disk.remap_config.AddLines(
49+
{
50+
f"map /cache-true-ignore-range/ http://127.0.0.1:{self.server.Variables.http_port}/ @plugin=compress.so @pparam={Test.RunDirectory}/cache-true-ignore-range.config",
51+
f"map /cache-true-no-compression/ http://127.0.0.1:{self.server.Variables.http_port}/ @plugin=compress.so @pparam={Test.RunDirectory}/cache-true-no-compression.config",
52+
})
53+
54+
def run(self):
55+
tr = Test.AddTestRun()
56+
tr.AddVerifierClientProcess(
57+
"verifier-client", self.replayFile, http_ports=[self.ts.Variables.port], other_args='--thread-limit 1')
58+
tr.Processes.Default.StartBefore(self.ts)
59+
tr.Processes.Default.StartBefore(self.server)
60+
tr.StillRunningAfter = self.ts
61+
tr.StillRunningAfter = self.server
62+
63+
64+
CompressPluginTest().run()
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
cache true
2+
range-request ignore-range
3+
compressible-content-type application/json
4+
supported-algorithms gzip
5+
minimum-content-length 0
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
cache true
2+
range-request no-compression
3+
compressible-content-type application/json
4+
supported-algorithms gzip
5+
minimum-content-length 0

0 commit comments

Comments
 (0)