proxygen
ZlibServerFilter.h
Go to the documentation of this file.
1 /*
2  * Copyright (c) 2015-present, Facebook, Inc.
3  * All rights reserved.
4  *
5  * This source code is licensed under the BSD-style license found in the
6  * LICENSE file in the root directory of this source tree. An additional grant
7  * of patent rights can be found in the PATENTS file in the same directory.
8  *
9  */
10 #pragma once
11 
12 #include <folly/Memory.h>
13 
18 
19 namespace proxygen {
20 
25 class ZlibServerFilter : public Filter {
26  public:
27  explicit ZlibServerFilter(
28  RequestHandler* downstream,
29  int32_t compressionLevel,
30  uint32_t minimumCompressionSize,
31  const std::shared_ptr<std::set<std::string>> compressibleContentTypes)
32  : Filter(downstream),
33  compressionLevel_(compressionLevel),
34  minimumCompressionSize_(minimumCompressionSize),
35  compressibleContentTypes_(compressibleContentTypes) {}
36 
37  void sendHeaders(HTTPMessage& msg) noexcept override {
38  DCHECK(compressor_ == nullptr);
39  DCHECK(header_ == false);
40 
41  chunked_ = msg.getIsChunked();
42 
43  // Make final determination of whether to compress
46 
47  // Add the gzip header
48  if (compress_) {
49  auto& headers = msg.getHeaders();
50  headers.set(HTTP_HEADER_CONTENT_ENCODING, "gzip");
51  }
52 
53  // Initialize compressor
54  compressor_ = std::make_unique<ZlibStreamCompressor>(
56  if (!compressor_ || compressor_->hasError()) {
57  fail();
58  return;
59  }
60 
61  // If it's chunked or not being compressed then the headers can be sent
62  // if it's compressed and one body, then need to calculate content length.
63  if (chunked_ || !compress_) {
65  header_ = true;
66  } else {
67  responseMessage_ = std::make_unique<HTTPMessage>(msg);
68  }
69  }
70 
71  void sendChunkHeader(size_t len) noexcept override {
72  // The headers should have always been sent since the message is chunked
73  DCHECK_EQ(header_, true) << "Headers should have already been sent.";
74 
75  // If not compressing, pass downstream, otherwise "swallow" it
76  // to send after compressing the body.
77  if (!compress_) {
79  }
80 
81  // Return without sending the chunk header.
82  return;
83  }
84 
85  // Compress the body, if chunked may be called multiple times
86  void sendBody(std::unique_ptr<folly::IOBuf> body) noexcept override {
87  // If not compressing, pass the body through
88  if (!compress_) {
89  DCHECK(header_ == true);
91  return;
92  }
93 
94  CHECK(compressor_ && !compressor_->hasError());
95 
96  // If it's chunked, never write the trailer, it will be written on EOM
97  auto compressed = compressor_->compress(body.get(), !chunked_);
98  if (compressor_->hasError()) {
99  return fail();
100  }
101 
102  auto compressedBodyLength = compressed->computeChainDataLength();
103 
104  if (chunked_) {
105  // Send on the swallowed chunk header.
106  Filter::sendChunkHeader(compressedBodyLength);
107  } else {
108  //Send the content length on compressed, non-chunked messages
109  DCHECK(header_ == false);
110  DCHECK(compress_ == true);
111  auto& headers = responseMessage_->getHeaders();
112  headers.set(HTTP_HEADER_CONTENT_LENGTH,
113  folly::to<std::string>(compressedBodyLength));
114 
116  header_ = true;
117  }
118 
119  Filter::sendBody(std::move(compressed));
120  }
121 
122  void sendEOM() noexcept override {
123 
124  // Need to send the gzip trailer for compressed chunked messages
125  if (compress_ && chunked_) {
126 
127  auto emptyBuffer = folly::IOBuf::copyBuffer("");
128  CHECK(compressor_ && !compressor_->hasError());
129  auto compressed = compressor_->compress(emptyBuffer.get(), true);
130 
131  if (compressor_->hasError()) {
132  fail();
133  return;
134  }
135 
136  // "Inject" a chunk with the gzip trailer.
137  Filter::sendChunkHeader(compressed->computeChainDataLength());
138  Filter::sendBody(std::move(compressed));
140  }
141 
142  Filter::sendEOM();
143  }
144 
145  protected:
146 
147  void fail() {
149  }
150 
151  //Verify the response is large enough to compress
153  auto contentLengthHeader =
154  msg.getHeaders().getSingleOrEmpty(HTTP_HEADER_CONTENT_LENGTH);
155 
156  uint32_t contentLength = 0;
157  if (!contentLengthHeader.empty()) {
158  contentLength = folly::to<uint32_t>(contentLengthHeader);
159  }
160 
161  return contentLength >= minimumCompressionSize_;
162  }
163 
164  // Check the response's content type against a list of compressible types
166 
167  auto responseContentType =
168  msg.getHeaders().getSingleOrEmpty(HTTP_HEADER_CONTENT_TYPE);
169  folly::toLowerAscii(responseContentType);
170 
171  // Handle text/html; encoding=utf-8 case
172  auto parameter_idx = responseContentType.find(';');
173  if (parameter_idx != std::string::npos) {
174  responseContentType = responseContentType.substr(0, parameter_idx);
175  }
176 
177  auto idx = compressibleContentTypes_->find(responseContentType);
178 
179  if (idx != compressibleContentTypes_->end()) {
180  return true;
181  }
182 
183  return false;
184  }
185 
186  std::unique_ptr<HTTPMessage> responseMessage_;
187  std::unique_ptr<ZlibStreamCompressor> compressor_{nullptr};
190  const std::shared_ptr<std::set<std::string>> compressibleContentTypes_;
191  bool header_{false};
192  bool chunked_{false};
193  bool compress_{false};
194 };
195 
197  public:
199  int32_t compressionLevel,
200  uint32_t minimumCompressionSize,
201  const std::set<std::string> compressibleContentTypes)
202  : compressionLevel_(compressionLevel),
203  minimumCompressionSize_(minimumCompressionSize),
205  std::make_shared<std::set<std::string>>(compressibleContentTypes)) {
206  }
207 
208  void onServerStart(folly::EventBase* /*evb*/) noexcept override {}
209 
210  void onServerStop() noexcept override {}
211 
213  HTTPMessage* msg) noexcept override {
214 
215  if (acceptsSupportedCompressionType(msg)) {
216  auto zlibServerFilter =
217  new ZlibServerFilter(h,
221  return zlibServerFilter;
222  }
223 
224  // No compression
225  return h;
226  }
227 
228  protected:
229 
230  // Check whether the client supports a compression type we support
232 
233  std::vector<RFC2616::TokenQPair> output;
234 
235  //Accept encoding header could have qvalues (gzip; q=5.0)
236  auto acceptEncodingHeader =
237  msg->getHeaders().getSingleOrEmpty(HTTP_HEADER_ACCEPT_ENCODING);
238 
239  if (RFC2616::parseQvalues(acceptEncodingHeader, output)) {
240  std::vector<RFC2616::TokenQPair>::iterator it = std::find_if(
241  output.begin(), output.end(), [](RFC2616::TokenQPair elem) {
242  return elem.first.compare(folly::StringPiece("gzip")) == 0;
243  });
244 
245  return (it != output.end());
246  }
247 
248  return false;
249  }
250 
253  const std::shared_ptr<std::set<std::string>> compressibleContentTypes_;
254 };
255 }
void sendChunkHeader(size_t len) noexceptoverride
Definition: Filters.h:92
*than *hazptr_holder h
Definition: Hazptr.h:116
void sendAbort() noexceptoverride
Definition: Filters.h:108
void sendHeaders(HTTPMessage &msg) noexceptoverride
constexpr detail::Map< Move > move
Definition: Base-inl.h:2567
void sendEOM() noexceptoverride
const std::shared_ptr< std::set< std::string > > compressibleContentTypes_
STL namespace.
void sendBody(std::unique_ptr< folly::IOBuf > body) noexceptoverride
requires E e noexcept(noexcept(s.error(std::move(e))))
std::unique_ptr< ZlibStreamCompressor > compressor_
bool isCompressibleContentType(const HTTPMessage &msg) const noexcept
std::unique_ptr< HTTPMessage > responseMessage_
void sendChunkTerminator() noexceptoverride
Definition: Filters.h:100
void sendEOM() noexceptoverride
Definition: Filters.h:104
void sendHeaders(HTTPMessage &msg) noexceptoverride
Definition: Filters.h:88
std::pair< folly::StringPiece, double > TokenQPair
Definition: RFC2616.h:69
RequestHandler * onRequest(RequestHandler *h, HTTPMessage *msg) noexceptoverride
void toLowerAscii(char *str, size_t length)
Definition: String.cpp:601
ZlibServerFilterFactory(int32_t compressionLevel, uint32_t minimumCompressionSize, const std::set< std::string > compressibleContentTypes)
const char * string
Definition: Conv.cpp:212
void onServerStop() noexceptoverride
void sendChunkHeader(size_t len) noexceptoverride
const std::shared_ptr< std::set< std::string > > compressibleContentTypes_
bool acceptsSupportedCompressionType(HTTPMessage *msg) noexcept
bool isMinimumCompressibleSize(const HTTPMessage &msg) const noexcept
ZlibServerFilter(RequestHandler *downstream, int32_t compressionLevel, uint32_t minimumCompressionSize, const std::shared_ptr< std::set< std::string >> compressibleContentTypes)
static std::unique_ptr< IOBuf > copyBuffer(const void *buf, std::size_t size, std::size_t headroom=0, std::size_t minTailroom=0)
Definition: IOBuf.h:1587
void sendBody(std::unique_ptr< folly::IOBuf > body) noexceptoverride
Definition: Filters.h:96
Definition: Traits.h:592
bool parseQvalues(folly::StringPiece value, std::vector< TokenQPair > &output)
Definition: RFC2616.cpp:64
void onServerStart(folly::EventBase *) noexceptoverride