Browse Source

Brotli support on client

yhirose 5 years ago
parent
commit
12540fe8d3
4 changed files with 287 additions and 89 deletions
  1. 15 10
      example/Makefile
  2. 201 77
      httplib.h
  3. 7 2
      test/Makefile
  4. 64 0
      test/test.cc

+ 15 - 10
example/Makefile

@@ -1,41 +1,46 @@
 
 
 #CXX = clang++
 #CXX = clang++
 CXXFLAGS = -std=c++14 -I.. -Wall -Wextra -pthread
 CXXFLAGS = -std=c++14 -I.. -Wall -Wextra -pthread
+
 OPENSSL_DIR = /usr/local/opt/openssl
 OPENSSL_DIR = /usr/local/opt/openssl
 OPENSSL_SUPPORT = -DCPPHTTPLIB_OPENSSL_SUPPORT -I$(OPENSSL_DIR)/include -L$(OPENSSL_DIR)/lib -lssl -lcrypto
 OPENSSL_SUPPORT = -DCPPHTTPLIB_OPENSSL_SUPPORT -I$(OPENSSL_DIR)/include -L$(OPENSSL_DIR)/lib -lssl -lcrypto
+
 ZLIB_SUPPORT = -DCPPHTTPLIB_ZLIB_SUPPORT -lz
 ZLIB_SUPPORT = -DCPPHTTPLIB_ZLIB_SUPPORT -lz
 
 
+BROTLI_DIR = /usr/local/opt/brotli
+# BROTLI_SUPPORT = -DCPPHTTPLIB_BROTLI_SUPPORT -I$(BROTLI_DIR)/include -L$(BROTLI_DIR)/lib -lbrotlicommon-static -lbrotlienc-static -lbrotlidec-static
+
 all: server client hello simplecli simplesvr upload redirect ssesvr ssecli benchmark
 all: server client hello simplecli simplesvr upload redirect ssesvr ssecli benchmark
 
 
 server : server.cc ../httplib.h Makefile
 server : server.cc ../httplib.h Makefile
-	$(CXX) -o server $(CXXFLAGS) server.cc $(OPENSSL_SUPPORT) $(ZLIB_SUPPORT)
+	$(CXX) -o server $(CXXFLAGS) server.cc $(OPENSSL_SUPPORT) $(ZLIB_SUPPORT) $(BROTLI_SUPPORT)
 
 
 client : client.cc ../httplib.h Makefile
 client : client.cc ../httplib.h Makefile
-	$(CXX) -o client $(CXXFLAGS) client.cc $(OPENSSL_SUPPORT) $(ZLIB_SUPPORT)
+	$(CXX) -o client $(CXXFLAGS) client.cc $(OPENSSL_SUPPORT) $(ZLIB_SUPPORT) $(BROTLI_SUPPORT)
 
 
 hello : hello.cc ../httplib.h Makefile
 hello : hello.cc ../httplib.h Makefile
-	$(CXX) -o hello $(CXXFLAGS) hello.cc $(OPENSSL_SUPPORT) $(ZLIB_SUPPORT)
+	$(CXX) -o hello $(CXXFLAGS) hello.cc $(OPENSSL_SUPPORT) $(ZLIB_SUPPORT) $(BROTLI_SUPPORT)
 
 
 simplecli : simplecli.cc ../httplib.h Makefile
 simplecli : simplecli.cc ../httplib.h Makefile
-	$(CXX) -o simplecli $(CXXFLAGS) simplecli.cc $(OPENSSL_SUPPORT) $(ZLIB_SUPPORT)
+	$(CXX) -o simplecli $(CXXFLAGS) simplecli.cc $(OPENSSL_SUPPORT) $(ZLIB_SUPPORT) $(BROTLI_SUPPORT)
 
 
 simplesvr : simplesvr.cc ../httplib.h Makefile
 simplesvr : simplesvr.cc ../httplib.h Makefile
-	$(CXX) -o simplesvr $(CXXFLAGS) simplesvr.cc $(OPENSSL_SUPPORT) $(ZLIB_SUPPORT)
+	$(CXX) -o simplesvr $(CXXFLAGS) simplesvr.cc $(OPENSSL_SUPPORT) $(ZLIB_SUPPORT) $(BROTLI_SUPPORT)
 
 
 upload : upload.cc ../httplib.h Makefile
 upload : upload.cc ../httplib.h Makefile
-	$(CXX) -o upload $(CXXFLAGS) upload.cc $(OPENSSL_SUPPORT) $(ZLIB_SUPPORT)
+	$(CXX) -o upload $(CXXFLAGS) upload.cc $(OPENSSL_SUPPORT) $(ZLIB_SUPPORT) $(BROTLI_SUPPORT)
 
 
 redirect : redirect.cc ../httplib.h Makefile
 redirect : redirect.cc ../httplib.h Makefile
-	$(CXX) -o redirect $(CXXFLAGS) redirect.cc $(OPENSSL_SUPPORT) $(ZLIB_SUPPORT)
+	$(CXX) -o redirect $(CXXFLAGS) redirect.cc $(OPENSSL_SUPPORT) $(ZLIB_SUPPORT) $(BROTLI_SUPPORT)
 
 
 ssesvr : ssesvr.cc ../httplib.h Makefile
 ssesvr : ssesvr.cc ../httplib.h Makefile
-	$(CXX) -o ssesvr $(CXXFLAGS) ssesvr.cc $(OPENSSL_SUPPORT) $(ZLIB_SUPPORT)
+	$(CXX) -o ssesvr $(CXXFLAGS) ssesvr.cc $(OPENSSL_SUPPORT) $(ZLIB_SUPPORT) $(BROTLI_SUPPORT)
 
 
 ssecli : ssecli.cc ../httplib.h Makefile
 ssecli : ssecli.cc ../httplib.h Makefile
-	$(CXX) -o ssecli $(CXXFLAGS) ssecli.cc $(OPENSSL_SUPPORT) $(ZLIB_SUPPORT)
+	$(CXX) -o ssecli $(CXXFLAGS) ssecli.cc $(OPENSSL_SUPPORT) $(ZLIB_SUPPORT) $(BROTLI_SUPPORT)
 
 
 benchmark : benchmark.cc ../httplib.h Makefile
 benchmark : benchmark.cc ../httplib.h Makefile
-	$(CXX) -o benchmark $(CXXFLAGS) benchmark.cc $(OPENSSL_SUPPORT) $(ZLIB_SUPPORT)
+	$(CXX) -o benchmark $(CXXFLAGS) benchmark.cc $(OPENSSL_SUPPORT) $(ZLIB_SUPPORT) $(BROTLI_SUPPORT)
 
 
 pem:
 pem:
 	openssl genrsa 2048 > key.pem
 	openssl genrsa 2048 > key.pem

+ 201 - 77
httplib.h

@@ -215,6 +215,11 @@ inline const unsigned char *ASN1_STRING_get0_data(const ASN1_STRING *asn1) {
 #ifdef CPPHTTPLIB_ZLIB_SUPPORT
 #ifdef CPPHTTPLIB_ZLIB_SUPPORT
 #include <zlib.h>
 #include <zlib.h>
 #endif
 #endif
+
+#ifdef CPPHTTPLIB_BROTLI_SUPPORT
+#include <brotli/decode.h>
+#endif
+
 /*
 /*
  * Declaration
  * Declaration
  */
  */
@@ -1668,19 +1673,34 @@ inline std::string file_extension(const std::string &path) {
   return std::string();
   return std::string();
 }
 }
 
 
+inline std::pair<int, int> trim(const char *b, const char *e, int left,
+                                int right) {
+  while (b + left < e && b[left] == ' ') {
+    left++;
+  }
+  while (right - 1 >= 0 && b[right - 1] == ' ') {
+    right--;
+  }
+  return std::make_pair(left, right);
+}
+
 template <class Fn> void split(const char *b, const char *e, char d, Fn fn) {
 template <class Fn> void split(const char *b, const char *e, char d, Fn fn) {
   int i = 0;
   int i = 0;
   int beg = 0;
   int beg = 0;
 
 
   while (e ? (b + i != e) : (b[i] != '\0')) {
   while (e ? (b + i != e) : (b[i] != '\0')) {
     if (b[i] == d) {
     if (b[i] == d) {
-      fn(&b[beg], &b[i]);
+      auto r = trim(b, e, beg, i);
+      fn(&b[r.first], &b[r.second]);
       beg = i + 1;
       beg = i + 1;
     }
     }
     i++;
     i++;
   }
   }
 
 
-  if (i) { fn(&b[beg], &b[i]); }
+  if (i) {
+    auto r = trim(b, e, beg, i);
+    fn(&b[r.first], &b[r.second]);
+  }
 }
 }
 
 
 // NOTE: until the read size reaches `fixed_buffer_size`, use `fixed_buffer`
 // NOTE: until the read size reaches `fixed_buffer_size`, use `fixed_buffer`
@@ -2324,21 +2344,34 @@ inline bool can_compress_content_type(const std::string &content_type) {
          content_type == "application/xhtml+xml";
          content_type == "application/xhtml+xml";
 }
 }
 
 
-inline bool can_compress_content(const Request &req, const Response &res) {
+enum class EncodingType { None = 0, Gzip, Brotli };
+
+inline EncodingType encoding_type(const Request &req, const Response &res) {
+  auto ret =
+      detail::can_compress_content_type(res.get_header_value("Content-Type"));
+  if (!ret) { return EncodingType::None; }
+
+  const auto &s = req.get_header_value("Accept-Encoding");
+
+#ifdef CPPHTTPLIB_BROTLI_SUPPORT
+  // TODO: 'Accept-Encoding' has br, not br;q=0
+  ret = s.find("br") != std::string::npos;
+  if (ret) { return EncodingType::Brotli; }
+#endif
+
 #ifdef CPPHTTPLIB_ZLIB_SUPPORT
 #ifdef CPPHTTPLIB_ZLIB_SUPPORT
-  const auto &encodings = req.get_header_value("Accept-Encoding");
-  return encodings.find("gzip") != std::string::npos &&
-         detail::can_compress_content_type(
-             res.get_header_value("Content-Type"));
-#else
-  return false;
+  // TODO: 'Accept-Encoding' has gzip, not gzip;q=0
+  ret = s.find("gzip") != std::string::npos;
+  if (ret) { return EncodingType::Gzip; }
 #endif
 #endif
+
+  return EncodingType::None;
 }
 }
 
 
 #ifdef CPPHTTPLIB_ZLIB_SUPPORT
 #ifdef CPPHTTPLIB_ZLIB_SUPPORT
-class compressor {
+class gzip_compressor {
 public:
 public:
-  compressor() {
+  gzip_compressor() {
     std::memset(&strm_, 0, sizeof(strm_));
     std::memset(&strm_, 0, sizeof(strm_));
     strm_.zalloc = Z_NULL;
     strm_.zalloc = Z_NULL;
     strm_.zfree = Z_NULL;
     strm_.zfree = Z_NULL;
@@ -2348,7 +2381,7 @@ public:
                              Z_DEFAULT_STRATEGY) == Z_OK;
                              Z_DEFAULT_STRATEGY) == Z_OK;
   }
   }
 
 
-  ~compressor() { deflateEnd(&strm_); }
+  ~gzip_compressor() { deflateEnd(&strm_); }
 
 
   template <typename T>
   template <typename T>
   bool compress(const char *data, size_t data_length, bool last, T callback) {
   bool compress(const char *data, size_t data_length, bool last, T callback) {
@@ -2384,9 +2417,9 @@ private:
   z_stream strm_;
   z_stream strm_;
 };
 };
 
 
-class decompressor {
+class gzip_decompressor {
 public:
 public:
-  decompressor() {
+  gzip_decompressor() {
     std::memset(&strm_, 0, sizeof(strm_));
     std::memset(&strm_, 0, sizeof(strm_));
     strm_.zalloc = Z_NULL;
     strm_.zalloc = Z_NULL;
     strm_.zfree = Z_NULL;
     strm_.zfree = Z_NULL;
@@ -2399,7 +2432,7 @@ public:
     is_valid_ = inflateInit2(&strm_, 32 + 15) == Z_OK;
     is_valid_ = inflateInit2(&strm_, 32 + 15) == Z_OK;
   }
   }
 
 
-  ~decompressor() { inflateEnd(&strm_); }
+  ~gzip_decompressor() { inflateEnd(&strm_); }
 
 
   bool is_valid() const { return is_valid_; }
   bool is_valid() const { return is_valid_; }
 
 
@@ -2439,6 +2472,59 @@ private:
 };
 };
 #endif
 #endif
 
 
+#ifdef CPPHTTPLIB_BROTLI_SUPPORT
+class brotli_decompressor {
+public:
+  brotli_decompressor() {
+    decoder_s = BrotliDecoderCreateInstance(0, 0, 0);
+    decoder_r = decoder_s ? BROTLI_DECODER_RESULT_NEEDS_MORE_INPUT
+                          : BROTLI_DECODER_RESULT_ERROR;
+  }
+
+  ~brotli_decompressor() {
+    if (decoder_s) { BrotliDecoderDestroyInstance(decoder_s); }
+  }
+
+  bool is_valid() const { return decoder_s; }
+
+  template <typename T>
+  bool decompress(const char *data, size_t data_length, T callback) {
+    if (decoder_r == BROTLI_DECODER_RESULT_SUCCESS ||
+        decoder_r == BROTLI_DECODER_RESULT_ERROR)
+      return 0;
+
+    const uint8_t *next_in = (const uint8_t *)data;
+    size_t avail_in = data_length;
+    size_t total_out;
+
+    decoder_r = BROTLI_DECODER_RESULT_NEEDS_MORE_OUTPUT;
+
+    while (decoder_r == BROTLI_DECODER_RESULT_NEEDS_MORE_OUTPUT) {
+      char output[1024];
+      char *next_out = output;
+      size_t avail_out = sizeof(output);
+
+      decoder_r = BrotliDecoderDecompressStream(
+          decoder_s, &avail_in, &next_in, &avail_out,
+          reinterpret_cast<unsigned char **>(&next_out), &total_out);
+
+      if (decoder_r == BROTLI_DECODER_RESULT_ERROR) { return false; }
+
+      if (!callback((const char *)output, sizeof(output) - avail_out)) {
+        return false;
+      }
+    }
+
+    return decoder_r == BROTLI_DECODER_RESULT_SUCCESS ||
+           decoder_r == BROTLI_DECODER_RESULT_NEEDS_MORE_INPUT;
+  }
+
+private:
+  BrotliDecoderResult decoder_r;
+  BrotliDecoderState *decoder_s = nullptr;
+};
+#endif
+
 inline bool has_header(const Headers &headers, const char *key) {
 inline bool has_header(const Headers &headers, const char *key) {
   return headers.find(key) != headers.end();
   return headers.find(key) != headers.end();
 }
 }
@@ -2611,63 +2697,86 @@ inline bool is_chunked_transfer_encoding(const Headers &headers) {
                      "chunked");
                      "chunked");
 }
 }
 
 
-template <typename T>
-bool read_content(Stream &strm, T &x, size_t payload_max_length, int &status,
-                  Progress progress, ContentReceiver receiver,
-                  bool decompress) {
-
-  ContentReceiver out = [&](const char *buf, size_t n) {
-    return receiver(buf, n);
-  };
+template <typename T, typename U>
+bool prepare_content_receiver(T &x, int &status, ContentReceiver receiver,
+                              bool decompress, U callback) {
+  if (decompress) {
+    std::string encoding = x.get_header_value("Content-Encoding");
 
 
+    if (encoding.find("gzip") != std::string::npos ||
+        encoding.find("deflate") != std::string::npos) {
 #ifdef CPPHTTPLIB_ZLIB_SUPPORT
 #ifdef CPPHTTPLIB_ZLIB_SUPPORT
-  decompressor decompressor;
+      gzip_decompressor decompressor;
+      if (decompressor.is_valid()) {
+        ContentReceiver out = [&](const char *buf, size_t n) {
+          return decompressor.decompress(
+              buf, n,
+              [&](const char *buf, size_t n) { return receiver(buf, n); });
+        };
+        return callback(out);
+      } else {
+        status = 500;
+        return false;
+      }
+#else
+      status = 415;
+      return false;
 #endif
 #endif
-
-  if (decompress) {
-#ifdef CPPHTTPLIB_ZLIB_SUPPORT
-    std::string content_encoding = x.get_header_value("Content-Encoding");
-    if (content_encoding.find("gzip") != std::string::npos ||
-        content_encoding.find("deflate") != std::string::npos) {
-      if (!decompressor.is_valid()) {
+    } else if (encoding.find("br") != std::string::npos) {
+#ifdef CPPHTTPLIB_BROTLI_SUPPORT
+      brotli_decompressor decompressor;
+      if (decompressor.is_valid()) {
+        ContentReceiver out = [&](const char *buf, size_t n) {
+          return decompressor.decompress(
+              buf, n,
+              [&](const char *buf, size_t n) { return receiver(buf, n); });
+        };
+        return callback(out);
+      } else {
         status = 500;
         status = 500;
         return false;
         return false;
       }
       }
-
-      out = [&](const char *buf, size_t n) {
-        return decompressor.decompress(buf, n, [&](const char *buf, size_t n) {
-          return receiver(buf, n);
-        });
-      };
-    }
 #else
 #else
-    if (x.get_header_value("Content-Encoding") == "gzip") {
       status = 415;
       status = 415;
       return false;
       return false;
-    }
 #endif
 #endif
+    }
   }
   }
 
 
-  auto ret = true;
-  auto exceed_payload_max_length = false;
+  ContentReceiver out = [&](const char *buf, size_t n) {
+    return receiver(buf, n);
+  };
 
 
-  if (is_chunked_transfer_encoding(x.headers)) {
-    ret = read_content_chunked(strm, out);
-  } else if (!has_header(x.headers, "Content-Length")) {
-    ret = read_content_without_length(strm, out);
-  } else {
-    auto len = get_header_value<uint64_t>(x.headers, "Content-Length");
-    if (len > payload_max_length) {
-      exceed_payload_max_length = true;
-      skip_content_with_length(strm, len);
-      ret = false;
-    } else if (len > 0) {
-      ret = read_content_with_length(strm, len, progress, out);
-    }
-  }
+  return callback(out);
+}
 
 
-  if (!ret) { status = exceed_payload_max_length ? 413 : 400; }
-  return ret;
+template <typename T>
+bool read_content(Stream &strm, T &x, size_t payload_max_length, int &status,
+                  Progress progress, ContentReceiver receiver,
+                  bool decompress) {
+  return prepare_content_receiver(
+      x, status, receiver, decompress, [&](ContentReceiver &out) {
+        auto ret = true;
+        auto exceed_payload_max_length = false;
+
+        if (is_chunked_transfer_encoding(x.headers)) {
+          ret = read_content_chunked(strm, out);
+        } else if (!has_header(x.headers, "Content-Length")) {
+          ret = read_content_without_length(strm, out);
+        } else {
+          auto len = get_header_value<uint64_t>(x.headers, "Content-Length");
+          if (len > payload_max_length) {
+            exceed_payload_max_length = true;
+            skip_content_with_length(strm, len);
+            ret = false;
+          } else if (len > 0) {
+            ret = read_content_with_length(strm, len, progress, out);
+          }
+        }
+
+        if (!ret) { status = exceed_payload_max_length ? 413 : 400; }
+        return ret;
+      });
 }
 }
 
 
 template <typename T>
 template <typename T>
@@ -2733,7 +2842,7 @@ inline ssize_t write_content(Stream &strm, ContentProvider content_provider,
 template <typename T>
 template <typename T>
 inline ssize_t write_content_chunked(Stream &strm,
 inline ssize_t write_content_chunked(Stream &strm,
                                      ContentProvider content_provider,
                                      ContentProvider content_provider,
-                                     T is_shutting_down, bool compress) {
+                                     T is_shutting_down, EncodingType type) {
   size_t offset = 0;
   size_t offset = 0;
   auto data_available = true;
   auto data_available = true;
   ssize_t total_written_length = 0;
   ssize_t total_written_length = 0;
@@ -2742,7 +2851,7 @@ inline ssize_t write_content_chunked(Stream &strm,
   DataSink data_sink;
   DataSink data_sink;
 
 
 #ifdef CPPHTTPLIB_ZLIB_SUPPORT
 #ifdef CPPHTTPLIB_ZLIB_SUPPORT
-  detail::compressor compressor;
+  detail::gzip_compressor compressor;
 #endif
 #endif
 
 
   data_sink.write = [&](const char *d, size_t l) {
   data_sink.write = [&](const char *d, size_t l) {
@@ -2752,7 +2861,7 @@ inline ssize_t write_content_chunked(Stream &strm,
     offset += l;
     offset += l;
 
 
     std::string payload;
     std::string payload;
-    if (compress) {
+    if (type == EncodingType::Gzip) {
 #ifdef CPPHTTPLIB_ZLIB_SUPPORT
 #ifdef CPPHTTPLIB_ZLIB_SUPPORT
       if (!compressor.compress(d, l, false,
       if (!compressor.compress(d, l, false,
                                [&](const char *data, size_t data_len) {
                                [&](const char *data, size_t data_len) {
@@ -2762,6 +2871,9 @@ inline ssize_t write_content_chunked(Stream &strm,
         ok = false;
         ok = false;
         return;
         return;
       }
       }
+#endif
+    } else if (type == EncodingType::Brotli) {
+#ifdef CPPHTTPLIB_BROTLI_SUPPORT
 #endif
 #endif
     } else {
     } else {
       payload = std::string(d, l);
       payload = std::string(d, l);
@@ -2784,7 +2896,7 @@ inline ssize_t write_content_chunked(Stream &strm,
 
 
     data_available = false;
     data_available = false;
 
 
-    if (compress) {
+    if (type == EncodingType::Gzip) {
 #ifdef CPPHTTPLIB_ZLIB_SUPPORT
 #ifdef CPPHTTPLIB_ZLIB_SUPPORT
       std::string payload;
       std::string payload;
       if (!compressor.compress(nullptr, 0, true,
       if (!compressor.compress(nullptr, 0, true,
@@ -2806,6 +2918,9 @@ inline ssize_t write_content_chunked(Stream &strm,
           return;
           return;
         }
         }
       }
       }
+#endif
+    } else if (type == EncodingType::Brotli) {
+#ifdef CPPHTTPLIB_BROTLI_SUPPORT
 #endif
 #endif
     }
     }
 
 
@@ -3964,7 +4079,7 @@ inline bool Server::write_response(Stream &strm, bool close_connection,
                         "multipart/byteranges; boundary=" + boundary);
                         "multipart/byteranges; boundary=" + boundary);
   }
   }
 
 
-  bool compress = detail::can_compress_content(req, res);
+  auto type = detail::encoding_type(req, res);
 
 
   if (res.body.empty()) {
   if (res.body.empty()) {
     if (res.content_length_ > 0) {
     if (res.content_length_ > 0) {
@@ -3987,7 +4102,11 @@ inline bool Server::write_response(Stream &strm, bool close_connection,
     } else {
     } else {
       if (res.content_provider_) {
       if (res.content_provider_) {
         res.set_header("Transfer-Encoding", "chunked");
         res.set_header("Transfer-Encoding", "chunked");
-        if (compress) { res.set_header("Content-Encoding", "gzip"); }
+        if (type == detail::EncodingType::Gzip) {
+          res.set_header("Content-Encoding", "gzip");
+        } else if (type == detail::EncodingType::Brotli) {
+          res.set_header("Content-Encoding", "br");
+        }
       } else {
       } else {
         res.set_header("Content-Length", "0");
         res.set_header("Content-Length", "0");
       }
       }
@@ -4009,20 +4128,25 @@ inline bool Server::write_response(Stream &strm, bool close_connection,
           detail::make_multipart_ranges_data(req, res, boundary, content_type);
           detail::make_multipart_ranges_data(req, res, boundary, content_type);
     }
     }
 
 
-    // TODO: 'Accept-Encoding' has gzip, not gzip;q=0
-    if (compress) {
+    if (type != detail::EncodingType::None) {
 #ifdef CPPHTTPLIB_ZLIB_SUPPORT
 #ifdef CPPHTTPLIB_ZLIB_SUPPORT
       std::string compressed;
       std::string compressed;
-      detail::compressor compressor;
-      if (!compressor.compress(res.body.data(), res.body.size(), true,
-                               [&](const char *data, size_t data_len) {
-                                 compressed.append(data, data_len);
-                                 return true;
-                               })) {
-        return false;
+
+      if (type == detail::EncodingType::Gzip) {
+        detail::gzip_compressor compressor;
+        if (!compressor.compress(res.body.data(), res.body.size(), true,
+                                 [&](const char *data, size_t data_len) {
+                                   compressed.append(data, data_len);
+                                   return true;
+                                 })) {
+          return false;
+        }
+        res.set_header("Content-Encoding", "gzip");
+      } else if (type == detail::EncodingType::Brotli) {
+        // TODO:
       }
       }
+
       res.body.swap(compressed);
       res.body.swap(compressed);
-      res.set_header("Content-Encoding", "gzip");
 #endif
 #endif
     }
     }
 
 
@@ -4085,9 +4209,9 @@ Server::write_content_with_provider(Stream &strm, const Request &req,
       }
       }
     }
     }
   } else {
   } else {
-    auto compress = detail::can_compress_content(req, res);
+    auto type = detail::encoding_type(req, res);
     if (detail::write_content_chunked(strm, res.content_provider_,
     if (detail::write_content_chunked(strm, res.content_provider_,
-                                      is_shutting_down, compress) < 0) {
+                                      is_shutting_down, type) < 0) {
       return false;
       return false;
     }
     }
   }
   }
@@ -4827,7 +4951,7 @@ inline std::shared_ptr<Response> Client::send_with_content_provider(
 
 
 #ifdef CPPHTTPLIB_ZLIB_SUPPORT
 #ifdef CPPHTTPLIB_ZLIB_SUPPORT
   if (compress_) {
   if (compress_) {
-    detail::compressor compressor;
+    detail::gzip_compressor compressor;
 
 
     if (content_provider) {
     if (content_provider) {
       auto ok = true;
       auto ok = true;

+ 7 - 2
test/Makefile

@@ -1,10 +1,15 @@
 
 
 #CXX = clang++
 #CXX = clang++
 CXXFLAGS = -ggdb -O0 -std=c++11 -DGTEST_USE_OWN_TR1_TUPLE -I.. -I. -Wall -Wextra -Wtype-limits -Wconversion
 CXXFLAGS = -ggdb -O0 -std=c++11 -DGTEST_USE_OWN_TR1_TUPLE -I.. -I. -Wall -Wextra -Wtype-limits -Wconversion
+
 OPENSSL_DIR = /usr/local/opt/openssl
 OPENSSL_DIR = /usr/local/opt/openssl
 OPENSSL_SUPPORT = -DCPPHTTPLIB_OPENSSL_SUPPORT -I$(OPENSSL_DIR)/include -L$(OPENSSL_DIR)/lib -lssl -lcrypto
 OPENSSL_SUPPORT = -DCPPHTTPLIB_OPENSSL_SUPPORT -I$(OPENSSL_DIR)/include -L$(OPENSSL_DIR)/lib -lssl -lcrypto
+
 ZLIB_SUPPORT = -DCPPHTTPLIB_ZLIB_SUPPORT -lz
 ZLIB_SUPPORT = -DCPPHTTPLIB_ZLIB_SUPPORT -lz
 
 
+BROTLI_DIR = /usr/local/opt/brotli
+# BROTLI_SUPPORT = -DCPPHTTPLIB_BROTLI_SUPPORT -I$(BROTLI_DIR)/include -L$(BROTLI_DIR)/lib -lbrotlicommon-static -lbrotlienc-static -lbrotlidec-static
+
 all : test
 all : test
 	./test
 	./test
 
 
@@ -12,10 +17,10 @@ proxy : test_proxy
 	./test_proxy
 	./test_proxy
 
 
 test : test.cc ../httplib.h Makefile cert.pem
 test : test.cc ../httplib.h Makefile cert.pem
-	$(CXX) -o test $(CXXFLAGS) test.cc gtest/gtest-all.cc gtest/gtest_main.cc $(OPENSSL_SUPPORT) $(ZLIB_SUPPORT) -pthread
+	$(CXX) -o test $(CXXFLAGS) test.cc gtest/gtest-all.cc gtest/gtest_main.cc $(OPENSSL_SUPPORT) $(ZLIB_SUPPORT) $(BROTLI_SUPPORT) -pthread
 
 
 test_proxy : test_proxy.cc ../httplib.h Makefile cert.pem
 test_proxy : test_proxy.cc ../httplib.h Makefile cert.pem
-	$(CXX) -o test_proxy $(CXXFLAGS) test_proxy.cc gtest/gtest-all.cc gtest/gtest_main.cc $(OPENSSL_SUPPORT) $(ZLIB_SUPPORT) -pthread
+	$(CXX) -o test_proxy $(CXXFLAGS) test_proxy.cc gtest/gtest-all.cc gtest/gtest_main.cc $(OPENSSL_SUPPORT) $(ZLIB_SUPPORT) $(BROTLI_SUPPORT) -pthread
 
 
 cert.pem:
 cert.pem:
 	openssl genrsa 2048 > key.pem
 	openssl genrsa 2048 > key.pem

+ 64 - 0
test/test.cc

@@ -216,6 +216,58 @@ TEST(ParseHeaderValueTest, Range) {
   }
   }
 }
 }
 
 
+TEST(ParseAcceptEncoding1, AcceptEncoding) {
+  Request req;
+  req.set_header("Accept-Encoding", "gzip");
+
+  Response res;
+  res.set_header("Content-Type", "text/plain");
+
+  auto ret = detail::encoding_type(req, res);
+
+#ifdef CPPHTTPLIB_ZLIB_SUPPORT
+  EXPECT_TRUE(ret == detail::EncodingType::Gzip);
+#else
+  EXPECT_TRUE(ret == detail::EncodingType::None);
+#endif
+}
+
+TEST(ParseAcceptEncoding2, AcceptEncoding) {
+  Request req;
+  req.set_header("Accept-Encoding", "gzip, deflate, br");
+
+  Response res;
+  res.set_header("Content-Type", "text/plain");
+
+  auto ret = detail::encoding_type(req, res);
+
+#ifdef CPPHTTPLIB_BROTLI_SUPPORT
+  EXPECT_TRUE(ret == detail::EncodingType::Brotli);
+#elif CPPHTTPLIB_ZLIB_SUPPORT
+  EXPECT_TRUE(ret == detail::EncodingType::Gzip);
+#else
+  EXPECT_TRUE(ret == detail::EncodingType::None);
+#endif
+}
+
+TEST(ParseAcceptEncoding3, AcceptEncoding) {
+  Request req;
+  req.set_header("Accept-Encoding", "br;q=1.0, gzip;q=0.8, *;q=0.1");
+
+  Response res;
+  res.set_header("Content-Type", "text/plain");
+
+  auto ret = detail::encoding_type(req, res);
+
+#ifdef CPPHTTPLIB_BROTLI_SUPPORT
+  EXPECT_TRUE(ret == detail::EncodingType::Brotli);
+#elif CPPHTTPLIB_ZLIB_SUPPORT
+  EXPECT_TRUE(ret == detail::EncodingType::Gzip);
+#else
+  EXPECT_TRUE(ret == detail::EncodingType::None);
+#endif
+}
+
 TEST(BufferStreamTest, read) {
 TEST(BufferStreamTest, read) {
   detail::BufferStream strm1;
   detail::BufferStream strm1;
   Stream &strm = strm1;
   Stream &strm = strm1;
@@ -3050,6 +3102,18 @@ TEST(YahooRedirectTest3, SimpleInterface) {
   EXPECT_EQ(200, res->status);
   EXPECT_EQ(200, res->status);
 }
 }
 
 
+#ifdef CPPHTTPLIB_BROTLI_SUPPORT
+TEST(DecodeWithChunkedEncoding, BrotliEncoding) {
+  httplib::Client2 cli("https://cdnjs.cloudflare.com");
+  auto res = cli.Get("/ajax/libs/jquery/3.5.1/jquery.js", {{"Accept-Encoding", "brotli"}});
+
+  ASSERT_TRUE(res != nullptr);
+  EXPECT_EQ(200, res->status);
+  EXPECT_EQ(287630, res->body.size());
+  EXPECT_EQ("application/javascript; charset=utf-8", res->get_header_value("Content-Type"));
+}
+#endif
+
 #if 0
 #if 0
 TEST(HttpsToHttpRedirectTest2, SimpleInterface) {
 TEST(HttpsToHttpRedirectTest2, SimpleInterface) {
   auto res =
   auto res =