Brotli: add support for Brotli compression.
This commit adds initial versions of two modules:
- ngx_brotli filter module - used to compress responses on-the-fly,
- ngx_brotli static module - used to serve pre-compressed files.
Signed-off-by: Piotr Sikora <piotrsikora@google.com>
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
new file mode 100644
index 0000000..a9eabff
--- /dev/null
+++ b/CONTRIBUTING.md
@@ -0,0 +1,27 @@
+# Contributing
+
+Want to contribute? Great! First, read this page (including the small print at the end).
+
+### Before you contribute
+Before we can use your code, you must sign the
+[Google Individual Contributor License Agreement](https://cla.developers.google.com/about/google-individual)
+(CLA), which you can do online. The CLA is necessary mainly because you own the
+copyright to your changes, even after your contribution becomes part of our
+codebase, so we need your permission to use and distribute your code. We also
+need to be sure of various other things—for instance that you'll tell us if you
+know that your code infringes on other people's patents. You don't have to sign
+the CLA until after you've submitted your code for review and a member has
+approved it, but you must do it before we can put your code into our codebase.
+Before you start working on a larger contribution, you should get in touch with
+us first through the issue tracker with your idea so that we can help out and
+possibly guide you. Coordinating up front makes it much easier to avoid
+frustration later on.
+
+### Code reviews
+All submissions, including submissions by project members, require review. We
+use Github pull requests for this purpose.
+
+### The small print
+Contributions made by corporations are covered by a different agreement than
+the one above, the
+[Software Grant and Corporate Contributor License Agreement](https://cla.developers.google.com/about/google-corporate).
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..6003634
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2002-2015 Igor Sysoev
+ * Copyright (C) 2011-2015 Nginx, Inc.
+ * Copyright (C) 2015 Google Inc.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..cbef5c9
--- /dev/null
+++ b/README.md
@@ -0,0 +1,140 @@
+# ngx_brotli
+
+Brotli is a generic-purpose lossless compression algorithm that compresses data
+using a combination of a modern variant of the LZ77 algorithm, Huffman coding
+and 2nd order context modeling, with a compression ratio comparable to the best
+currently available general-purpose compression methods. It is similar in speed
+with deflate but offers more dense compression.
+
+ngx_brotli is a set of two nginx modules:
+
+- ngx_brotli filter module - used to compress responses on-the-fly,
+- ngx_brotli static module - used to serve pre-compressed files.
+
+## Status
+
+Both Brotli library and nginx module are under active development.
+
+## Installation
+
+ $ cd nginx-1.x.x
+ $ ./configure --add-module=/path/to/ngx_brotli
+ $ make && make install
+
+ngx_brotli filter module depends on
+[libbroti](https://github.com/bagder/libbrotli),
+which must be installed on the target system for it to work.
+
+Alternatively, ngx_brotli static module can be compiled into nginx by itself
+with `NGX_BROTLI_STATIC_MODULE_ONLY=1` defined in the environment:
+
+ $ cd nginx-1.x.x
+ $ export NGX_BROTLI_STATIC_MODULE_ONLY=1
+ $ ./configure --add-module=/path/to/ngx_brotli
+ $ make && make install
+
+## Configuration directives
+
+### `brotli_static`
+
+- **syntax**: `brotli_static on|off|always`
+- **default**: `off`
+- **context**: `http`, `server`, `location`
+
+Enables or disables checking of the existence of pre-compressed files with`.br`
+extension. With the `always` value, pre-compressed file is used in all cases,
+without checking if the client supports it.
+
+### `brotli`
+
+- **syntax**: `brotli on|off`
+- **default**: `off`
+- **context**: `http`, `server`, `location`, `if`
+
+Enables or disables on-the-fly compression of responses.
+
+### `brotli_types`
+
+- **syntax**: `brotli_types <mime_type> [..]`
+- **default**: `text/html`
+- **context**: `http`, `server`, `location`
+
+Enables on-the-fly compression of responses for the specified MIME types
+in addition to `text/html`. The special value `*` matches any MIME type.
+Responses with the `text/html` MIME type are always compressed.
+
+### `brotli_buffers`
+
+- **syntax**: `brotli_buffers <number> <size>`
+- **default**: `32 4k|16 8k`
+- **context**: `http`, `server`, `location`
+
+Sets the `number` and `size` of buffers used to compress a response.
+By default, the buffer size is equal to one memory page.
+This is either `4k` or `8k`, depending on a platform.
+
+### `brotli_comp_level`
+
+- **syntax**: `brotli_comp_level <level>`
+- **default**: `6`
+- **context**: `http`, `server`, `location`
+
+Sets Brotli quality (compression) `level`.
+Acceptable values are in the range from `0` to `11`.
+
+### `brotli_window`
+
+- **syntax**: `brotli_window <size>`
+- **default**: `512k`
+- **context**: `http`, `server`, `location`
+
+Sets Brotli window `size`. Acceptable values are `1k`, `2k`, `4k`, `8k`, `16k`,
+`32k`, `64k`, `128k`, `256k`, `512k`, `1m`, `2m`, `4m`, `8m` and `16m`.
+
+### `brotli_min_length`
+
+- **syntax**: `brotli_min_length <length>`
+- **default**: `20`
+- **context**: `http`, `server`, `location`
+
+Sets the minimum `length` of a response that will be compressed.
+The length is determined only from the `Content-Length` response header field.
+
+## Variables
+
+### `$brotli_ratio`
+
+Achieved compression ratio, computed as the ratio between the original
+and compressed response sizes.
+
+## Contributing
+
+See [Contributing](CONTRIBUTING.md).
+
+## License
+
+ Copyright (C) 2002-2015 Igor Sysoev
+ Copyright (C) 2011-2015 Nginx, Inc.
+ Copyright (C) 2015 Google Inc.
+ All rights reserved.
+
+ Redistribution and use in source and binary forms, with or without
+ modification, are permitted provided that the following conditions
+ are met:
+ 1. Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+ 2. Redistributions in binary form must reproduce the above copyright
+ notice, this list of conditions and the following disclaimer in the
+ documentation and/or other materials provided with the distribution.
+
+ THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ SUCH DAMAGE.
diff --git a/auto/feature b/auto/feature
new file mode 100644
index 0000000..92294b7
--- /dev/null
+++ b/auto/feature
@@ -0,0 +1,131 @@
+
+# Copyright (C) Igor Sysoev
+# Copyright (C) Nginx, Inc.
+
+
+echo $ngx_n "checking for $ngx_feature ...$ngx_c"
+
+cat << END >> $NGX_AUTOCONF_ERR
+
+----------------------------------------
+checking for $ngx_feature
+
+END
+
+ngx_found=no
+
+if test -n "$ngx_feature_name"; then
+ ngx_have_feature=`echo $ngx_feature_name \
+ | tr abcdefghijklmnopqrstuvwxyz ABCDEFGHIJKLMNOPQRSTUVWXYZ`
+fi
+
+if test -n "$ngx_feature_path"; then
+ for ngx_temp in $ngx_feature_path; do
+ ngx_feature_inc_path="$ngx_feature_inc_path -I $ngx_temp"
+ done
+fi
+
+if [ "$ngx_feature_cpp" == "yes" ]; then
+ NGX_AUTOTEST_EXT=cc
+else
+ NGX_AUTOTEST_EXT=c
+fi
+
+cat << END > $NGX_AUTOTEST.$NGX_AUTOTEST_EXT
+
+#include <sys/types.h>
+$NGX_INCLUDE_UNISTD_H
+$ngx_feature_incs
+
+int main() {
+ $ngx_feature_test;
+ return 0;
+}
+
+END
+
+
+ngx_test="$CC $CC_TEST_FLAGS $CC_AUX_FLAGS $ngx_feature_inc_path \
+ -o $NGX_AUTOTEST $NGX_AUTOTEST.$NGX_AUTOTEST_EXT \
+ $NGX_TEST_LD_OPT $ngx_feature_libs"
+
+ngx_feature_inc_path=
+ngx_feature_cpp=
+
+eval "/bin/sh -c \"$ngx_test\" >> $NGX_AUTOCONF_ERR 2>&1"
+
+
+if [ -x $NGX_AUTOTEST ]; then
+
+ case "$ngx_feature_run" in
+
+ yes)
+ # /bin/sh is used to intercept "Killed" or "Abort trap" messages
+ if /bin/sh -c $NGX_AUTOTEST >> $NGX_AUTOCONF_ERR 2>&1; then
+ echo " found"
+ ngx_found=yes
+
+ if test -n "$ngx_feature_name"; then
+ have=$ngx_have_feature . auto/have
+ fi
+
+ else
+ echo " found but is not working"
+ fi
+ ;;
+
+ value)
+ # /bin/sh is used to intercept "Killed" or "Abort trap" messages
+ if /bin/sh -c $NGX_AUTOTEST >> $NGX_AUTOCONF_ERR 2>&1; then
+ echo " found"
+ ngx_found=yes
+
+ cat << END >> $NGX_AUTO_CONFIG_H
+
+#ifndef $ngx_feature_name
+#define $ngx_feature_name `$NGX_AUTOTEST`
+#endif
+
+END
+ else
+ echo " found but is not working"
+ fi
+ ;;
+
+ bug)
+ # /bin/sh is used to intercept "Killed" or "Abort trap" messages
+ if /bin/sh -c $NGX_AUTOTEST >> $NGX_AUTOCONF_ERR 2>&1; then
+ echo " not found"
+
+ else
+ echo " found"
+ ngx_found=yes
+
+ if test -n "$ngx_feature_name"; then
+ have=$ngx_have_feature . auto/have
+ fi
+ fi
+ ;;
+
+ *)
+ echo " found"
+ ngx_found=yes
+
+ if test -n "$ngx_feature_name"; then
+ have=$ngx_have_feature . auto/have
+ fi
+ ;;
+
+ esac
+
+else
+ echo " not found"
+
+ echo "----------" >> $NGX_AUTOCONF_ERR
+ cat $NGX_AUTOTEST.$NGX_AUTOTEST_EXT >> $NGX_AUTOCONF_ERR
+ echo "----------" >> $NGX_AUTOCONF_ERR
+ echo $ngx_test >> $NGX_AUTOCONF_ERR
+ echo "----------" >> $NGX_AUTOCONF_ERR
+fi
+
+rm -rf $NGX_AUTOTEST*
diff --git a/config b/config
new file mode 100644
index 0000000..aea0e9f
--- /dev/null
+++ b/config
@@ -0,0 +1,114 @@
+# Copyright (C) 2015 Google Inc.
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+# 1. Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+# ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+# SUCH DAMAGE.
+
+ngx_addon_name=ngx_brotli
+
+#
+# static module (no dependencies)
+#
+
+HTTP_MODULES="$HTTP_MODULES \
+ ngx_http_brotli_static_module"
+
+NGX_ADDON_SRCS="$NGX_ADDON_SRCS \
+ $ngx_addon_dir/src/ngx_http_brotli_static_module.c"
+
+have=NGX_HTTP_GZIP . auto/have
+have=NGX_HTTP_BROTLI_STATIC_MODULE . auto/have
+
+if [ -n "$NGX_BROTLI_STATIC_MODULE_ONLY" ]; then
+ return
+fi
+
+#
+# filter module (depends on Brotli library)
+#
+
+ngx_feature_run=no
+ngx_feature_cpp=yes
+ngx_feature_incs="#include <brotli/enc/encode.h>"
+ngx_feature_test="brotli::BrotliParams params;
+ params.quality = 11"
+
+# auto-discovery
+ngx_feature="Brotli library"
+ngx_feature_cpp=yes
+ngx_feature_path=
+ngx_feature_libs="-lbrotlienc -lstdc++"
+. $ngx_addon_dir/auto/feature
+
+if [ $ngx_found = no ]; then
+ # FreeBSD, OpenBSD
+ ngx_feature="Brotli library in /usr/local/"
+ ngx_feature_cpp=yes
+ ngx_feature_path="/usr/local/include"
+ if [ $NGX_RPATH = YES ]; then
+ ngx_feature_libs="-R/usr/local/lib -L/usr/local/lib -lbrotlienc -lstdc++"
+ else
+ ngx_feature_libs="-L/usr/local/lib -lbrotlienc -lstdc++"
+ fi
+ . $ngx_addon_dir/auto/feature
+fi
+
+if [ $ngx_found = no ]; then
+ # NetBSD
+ ngx_feature="Brotli library in /usr/pkg/"
+ ngx_feature_cpp=yes
+ ngx_feature_path="/usr/pkg/include"
+ if [ $NGX_RPATH = YES ]; then
+ ngx_feature_libs="-R/usr/pkg/lib -L/usr/pkg/lib -lbrotlienc -lstdc++"
+ else
+ ngx_feature_libs="-L/usr/pkg/lib -lbrotlienc -lstdc++"
+ fi
+ . $ngx_addon_dir/auto/feature
+fi
+
+if [ $ngx_found = no ]; then
+ # MacPorts
+ ngx_feature="Brotli library in /opt/local/"
+ ngx_feature_cpp=yes
+ ngx_feature_path="/opt/local/include"
+ if [ $NGX_RPATH = YES ]; then
+ ngx_feature_libs="-R/opt/local/lib -L/opt/local/lib -lbrotlienc -lstdc++"
+ else
+ ngx_feature_libs="-L/opt/local/lib -lbrotlienc -lstdc++"
+ fi
+ . $ngx_addon_dir/auto/feature
+fi
+
+if [ $ngx_found = no ]; then
+ echo "$0: error: ngx_brotli filter module requires Brotli library."
+ exit 1
+fi
+
+CORE_INCS="$CORE_INCS $ngx_feature_path"
+CORE_LIBS="$CORE_LIBS $ngx_feature_libs"
+
+HTTP_AUX_FILTER_MODULES="$HTTP_AUX_FILTER_MODULES \
+ ngx_http_brotli_filter_module"
+
+NGX_ADDON_SRCS="$NGX_ADDON_SRCS \
+ $ngx_addon_dir/src/ngx_http_brotli_filter_module.cc"
+
+have=NGX_HTTP_BROTLI_FILTER_MODULE . auto/have
diff --git a/src/ngx_http_brotli_filter_module.cc b/src/ngx_http_brotli_filter_module.cc
new file mode 100644
index 0000000..1202d75
--- /dev/null
+++ b/src/ngx_http_brotli_filter_module.cc
@@ -0,0 +1,831 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ * Copyright (C) Nginx, Inc.
+ * Copyright (C) Google Inc.
+ */
+
+
+extern "C" {
+ #include <ngx_config.h>
+ #include <ngx_core.h>
+ #include <ngx_http.h>
+}
+
+#include <brotli/enc/encode.h>
+
+
+typedef struct {
+ ngx_flag_t enable;
+
+ ngx_hash_t types;
+
+ ngx_bufs_t bufs;
+
+ ngx_int_t quality;
+ size_t win_bits;
+ ssize_t min_length;
+
+ ngx_array_t *types_keys;
+} ngx_http_brotli_conf_t;
+
+
+typedef struct {
+ brotli::BrotliCompressor *compressor;
+ brotli::BrotliParams params;
+
+ size_t brotli_ring;
+ size_t brotli_in;
+ u_char *brotli_out;
+ u_char *brotli_last;
+
+ size_t bytes_in;
+ size_t bytes_out;
+
+ ngx_chain_t *in;
+ ngx_chain_t *free;
+ ngx_chain_t *busy;
+ ngx_chain_t *out;
+ ngx_chain_t **last_out;
+
+ ngx_buf_t *out_buf;
+ ngx_int_t bufs;
+
+ unsigned done:1;
+ unsigned sent:1;
+ unsigned last:1;
+ unsigned flush:1;
+ unsigned nomem:1;
+} ngx_http_brotli_ctx_t;
+
+
+static void ngx_http_brotli_filter_params(ngx_http_request_t *r,
+ ngx_http_brotli_ctx_t *ctx);
+static ngx_int_t ngx_http_brotli_filter_add_data(ngx_http_request_t *r,
+ ngx_http_brotli_ctx_t *ctx);
+static ngx_int_t ngx_http_brotli_filter_process(ngx_http_request_t *r,
+ ngx_http_brotli_ctx_t *ctx);
+static ngx_int_t ngx_http_brotli_filter_output(ngx_http_request_t *r,
+ ngx_http_brotli_ctx_t *ctx);
+static ngx_int_t ngx_http_brotli_filter_get_buf(ngx_http_request_t *r,
+ ngx_http_brotli_ctx_t *ctx);
+
+static ngx_int_t ngx_http_brotli_ok(ngx_http_request_t *r);
+static ngx_int_t ngx_http_brotli_accept_encoding(ngx_str_t *ae);
+
+static ngx_int_t ngx_http_brotli_add_variables(ngx_conf_t *cf);
+static ngx_int_t ngx_http_brotli_ratio_variable(ngx_http_request_t *r,
+ ngx_http_variable_value_t *v, uintptr_t data);
+
+static void *ngx_http_brotli_create_conf(ngx_conf_t *cf);
+static char *ngx_http_brotli_merge_conf(ngx_conf_t *cf,
+ void *parent, void *child);
+static ngx_int_t ngx_http_brotli_filter_init(ngx_conf_t *cf);
+
+static char *ngx_http_brotli_window(ngx_conf_t *cf, void *post, void *data);
+
+
+static ngx_conf_num_bounds_t ngx_http_brotli_comp_level_bounds = {
+ ngx_conf_check_num_bounds, 0, 11
+};
+
+static ngx_conf_post_handler_pt ngx_http_brotli_window_p =
+ ngx_http_brotli_window;
+
+
+static ngx_command_t ngx_http_brotli_filter_commands[] = {
+
+ { ngx_string("brotli"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_HTTP_LIF_CONF
+ |NGX_CONF_FLAG,
+ ngx_conf_set_flag_slot,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ offsetof(ngx_http_brotli_conf_t, enable),
+ NULL },
+
+ { ngx_string("brotli_buffers"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE2,
+ ngx_conf_set_bufs_slot,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ offsetof(ngx_http_brotli_conf_t, bufs),
+ NULL },
+
+ { ngx_string("brotli_types"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_1MORE,
+ ngx_http_types_slot,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ offsetof(ngx_http_brotli_conf_t, types_keys),
+ &ngx_http_html_default_types[0] },
+
+ { ngx_string("brotli_comp_level"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
+ ngx_conf_set_num_slot,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ offsetof(ngx_http_brotli_conf_t, quality),
+ &ngx_http_brotli_comp_level_bounds },
+
+ { ngx_string("brotli_window"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
+ ngx_conf_set_size_slot,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ offsetof(ngx_http_brotli_conf_t, win_bits),
+ &ngx_http_brotli_window_p },
+
+ { ngx_string("brotli_min_length"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
+ ngx_conf_set_size_slot,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ offsetof(ngx_http_brotli_conf_t, min_length),
+ NULL },
+
+ ngx_null_command
+};
+
+
+static ngx_http_module_t ngx_http_brotli_filter_module_ctx = {
+ ngx_http_brotli_add_variables, /* preconfiguration */
+ ngx_http_brotli_filter_init, /* postconfiguration */
+
+ NULL, /* create main configuration */
+ NULL, /* init main configuration */
+
+ NULL, /* create server configuration */
+ NULL, /* merge server configuration */
+
+ ngx_http_brotli_create_conf, /* create location configuration */
+ ngx_http_brotli_merge_conf /* merge location configuration */
+};
+
+
+ngx_module_t ngx_http_brotli_filter_module = {
+ NGX_MODULE_V1,
+ &ngx_http_brotli_filter_module_ctx, /* module context */
+ ngx_http_brotli_filter_commands, /* module directives */
+ NGX_HTTP_MODULE, /* module type */
+ NULL, /* init master */
+ NULL, /* init module */
+ NULL, /* init process */
+ NULL, /* init thread */
+ NULL, /* exit thread */
+ NULL, /* exit process */
+ NULL, /* exit master */
+ NGX_MODULE_V1_PADDING
+};
+
+
+static ngx_str_t ngx_http_brotli_ratio = ngx_string("brotli_ratio");
+
+static ngx_http_output_header_filter_pt ngx_http_next_header_filter;
+static ngx_http_output_body_filter_pt ngx_http_next_body_filter;
+
+
+static ngx_int_t
+ngx_http_brotli_header_filter(ngx_http_request_t *r)
+{
+ ngx_table_elt_t *h;
+ ngx_http_brotli_ctx_t *ctx;
+ ngx_http_brotli_conf_t *conf;
+
+ conf = reinterpret_cast<ngx_http_brotli_conf_t *>(
+ ngx_http_get_module_loc_conf(r, ngx_http_brotli_filter_module));
+
+ if (!conf->enable
+ || (r->headers_out.status != NGX_HTTP_OK
+ && r->headers_out.status != NGX_HTTP_FORBIDDEN
+ && r->headers_out.status != NGX_HTTP_NOT_FOUND)
+ || (r->headers_out.content_encoding
+ && r->headers_out.content_encoding->value.len)
+ || (r->headers_out.content_length_n != -1
+ && r->headers_out.content_length_n < conf->min_length)
+ || ngx_http_test_content_type(r, &conf->types) == NULL
+ || r->header_only)
+ {
+ return ngx_http_next_header_filter(r);
+ }
+
+ r->gzip_vary = 1;
+
+ if (ngx_http_brotli_ok(r) != NGX_OK) {
+ return ngx_http_next_header_filter(r);
+ }
+
+ ctx = reinterpret_cast<ngx_http_brotli_ctx_t *>(
+ ngx_pcalloc(r->pool, sizeof(ngx_http_brotli_ctx_t)));
+ if (ctx == NULL) {
+ return NGX_ERROR;
+ }
+
+ ctx->last_out = &ctx->out;
+
+ ngx_http_brotli_filter_params(r, ctx);
+
+ ngx_http_set_ctx(r, ctx, ngx_http_brotli_filter_module);
+
+ h = reinterpret_cast<ngx_table_elt_t *>(
+ ngx_list_push(&r->headers_out.headers));
+ if (h == NULL) {
+ return NGX_ERROR;
+ }
+
+ h->hash = 1;
+ ngx_str_set(&h->key, "Content-Encoding");
+ ngx_str_set(&h->value, "br");
+ r->headers_out.content_encoding = h;
+
+ r->main_filter_need_in_memory = 1;
+
+ ngx_http_clear_content_length(r);
+ ngx_http_clear_accept_ranges(r);
+ ngx_http_weak_etag(r);
+
+ return ngx_http_next_header_filter(r);
+}
+
+
+static ngx_int_t
+ngx_http_brotli_body_filter(ngx_http_request_t *r, ngx_chain_t *in)
+{
+ int rc;
+ ngx_uint_t flush;
+ ngx_chain_t *cl;
+ ngx_http_brotli_ctx_t *ctx;
+
+ ctx = reinterpret_cast<ngx_http_brotli_ctx_t *>(
+ ngx_http_get_module_ctx(r, ngx_http_brotli_filter_module));
+
+ if (ctx == NULL || ctx->done || r->header_only) {
+ return ngx_http_next_body_filter(r, in);
+ }
+
+ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+ "http brotli filter");
+
+ if (ctx->compressor == NULL) {
+ ctx->compressor = new brotli::BrotliCompressor(ctx->params);
+ if (ctx->compressor == NULL) {
+ goto failed;
+ }
+
+ ctx->brotli_ring = ctx->compressor->input_block_size();
+
+ ngx_log_debug3(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+ "brotli compressor: lvl:%d win:%d blk:%uz",
+ ctx->params.quality, (1 << ctx->params.lgwin),
+ ctx->brotli_ring);
+ }
+
+ if (in) {
+ if (ngx_chain_add_copy(r->pool, &ctx->in, in) != NGX_OK) {
+ goto failed;
+ }
+
+ r->connection->buffered |= NGX_HTTP_GZIP_BUFFERED;
+ }
+
+ if (ctx->nomem) {
+
+ /* flush busy buffers */
+
+ if (ngx_http_next_body_filter(r, NULL) == NGX_ERROR) {
+ goto failed;
+ }
+
+ cl = NULL;
+
+ ngx_chain_update_chains(r->pool, &ctx->free, &ctx->busy, &cl,
+ (ngx_buf_tag_t) &ngx_http_brotli_filter_module);
+ ctx->nomem = 0;
+ flush = 0;
+
+ } else {
+ flush = ctx->busy ? 1 : 0;
+ }
+
+ for ( ;; ) {
+
+ /* cycle while we can write to a client */
+
+ for ( ;; ) {
+
+ rc = ngx_http_brotli_filter_add_data(r, ctx);
+
+ if (rc == NGX_DECLINED) {
+ break;
+ }
+
+ if (rc == NGX_AGAIN) {
+ continue;
+ }
+
+ rc = ngx_http_brotli_filter_process(r, ctx);
+
+ if (rc == NGX_AGAIN) {
+ continue;
+ }
+
+ if (rc == NGX_ERROR) {
+ goto failed;
+ }
+
+ rc = ngx_http_brotli_filter_output(r, ctx);
+
+ if (rc == NGX_OK) {
+ break;
+ }
+
+ if (rc == NGX_ERROR) {
+ goto failed;
+ }
+
+ /* rc == NGX_AGAIN */
+ }
+
+ if (ctx->out == NULL && !flush) {
+ return ctx->busy ? NGX_AGAIN : NGX_OK;
+ }
+
+ rc = ngx_http_next_body_filter(r, ctx->out);
+
+ if (rc == NGX_ERROR) {
+ goto failed;
+ }
+
+ ngx_chain_update_chains(r->pool, &ctx->free, &ctx->busy, &ctx->out,
+ (ngx_buf_tag_t) &ngx_http_brotli_filter_module);
+ ctx->last_out = &ctx->out;
+
+ ctx->nomem = 0;
+ flush = 0;
+
+ if (ctx->done) {
+ ctx->sent = 1;
+
+ if (ctx->compressor) {
+ delete ctx->compressor;
+ ctx->compressor = NULL;
+ }
+
+ return rc;
+ }
+ }
+
+ /* unreachable */
+
+failed:
+
+ ctx->done = 1;
+
+ if (ctx->compressor) {
+ delete ctx->compressor;
+ ctx->compressor = NULL;
+ }
+
+ return NGX_ERROR;
+}
+
+
+static void
+ngx_http_brotli_filter_params(ngx_http_request_t *r, ngx_http_brotli_ctx_t *ctx)
+{
+ int wbits;
+ ngx_http_brotli_conf_t *conf;
+
+ conf = reinterpret_cast<ngx_http_brotli_conf_t *>(
+ ngx_http_get_module_loc_conf(r, ngx_http_brotli_filter_module));
+
+ wbits = conf->win_bits;
+
+ if (r->headers_out.content_length_n > 0) {
+ while (r->headers_out.content_length_n < (1 << (wbits - 1))
+ && wbits > brotli::kMinWindowBits)
+ {
+ wbits--;
+ }
+ }
+
+ ctx->params.quality = conf->quality;
+ ctx->params.lgwin = wbits;
+}
+
+
+static ngx_int_t
+ngx_http_brotli_filter_add_data(ngx_http_request_t *r,
+ ngx_http_brotli_ctx_t *ctx)
+{
+ size_t size, ring;
+ ngx_buf_t *b;
+
+ if (ctx->brotli_out || ctx->last || ctx->flush) {
+ return NGX_OK;
+ }
+
+ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+ "brotli in: %p", ctx->in);
+
+ if (ctx->in == NULL) {
+ return NGX_DECLINED;
+ }
+
+ b = ctx->in->buf;
+
+ size = ngx_buf_size(b);
+ ring = ctx->brotli_ring - ctx->brotli_in;
+
+ if (size > ring) {
+ size = ring;
+
+ } else {
+ if (b->last_buf) {
+ ctx->last = 1;
+
+ } else if (b->flush) {
+ ctx->flush = 1;
+ }
+ }
+
+ if (size == ring) {
+ ctx->flush = 1;
+ }
+
+ if (size > 0) {
+ ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+ "brotli copy: %p, size:%uz", b, size);
+
+ ctx->compressor->CopyInputToRingBuffer(size, b->pos);
+
+ ctx->brotli_in += size;
+ ctx->bytes_in += size;
+ b->pos += size;
+ }
+
+ if (ngx_buf_size(b) == 0) {
+ ctx->in = ctx->in->next;
+ size = 0;
+ }
+
+ if (size == 0 && !ctx->flush && !ctx->last) {
+ return NGX_AGAIN;
+ }
+
+ return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_brotli_filter_process(ngx_http_request_t *r,
+ ngx_http_brotli_ctx_t *ctx)
+{
+ size_t size;
+ u_char *out;
+
+ if (ctx->brotli_out) {
+ return NGX_OK;
+ }
+
+ ngx_log_debug3(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+ "brotli process: size:%uz l:%d f:%d",
+ ctx->brotli_in, ctx->last, ctx->flush);
+
+ if (!ctx->compressor->WriteBrotliData(ctx->last, ctx->flush, &size, &out)) {
+ ngx_log_error(NGX_LOG_ALERT, r->connection->log, 0,
+ "brotli failed: size:%uz l:%d f:%d",
+ ctx->brotli_in, ctx->last, ctx->flush);
+ return NGX_ERROR;
+ }
+
+ ctx->brotli_in = 0;
+
+ ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+ "brotli data: %p, size:%uz", out, size);
+
+ if (size == 0) {
+ return NGX_AGAIN;
+ }
+
+ ctx->brotli_out = out;
+ ctx->brotli_last = out + size;
+ ctx->bytes_out += size;
+
+ return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_brotli_filter_output(ngx_http_request_t *r, ngx_http_brotli_ctx_t *ctx)
+{
+ int rc;
+ size_t size;
+ ngx_chain_t *cl;
+
+ if (ctx->brotli_out == NULL) {
+ return NGX_AGAIN;
+ }
+
+#if (NGX_SUPPRESS_WARN)
+ cl = NULL;
+#endif
+
+ while (ctx->brotli_out < ctx->brotli_last) {
+
+ rc = ngx_http_brotli_filter_get_buf(r, ctx);
+
+ if (rc == NGX_DECLINED) {
+ return NGX_OK;
+ }
+
+ if (rc == NGX_ERROR) {
+ return NGX_ERROR;
+ }
+
+ size = ngx_min(ctx->out_buf->end - ctx->out_buf->last,
+ ctx->brotli_last - ctx->brotli_out);
+
+ ngx_memcpy(ctx->out_buf->last, ctx->brotli_out, size);
+
+ ctx->out_buf->last += size;
+ ctx->brotli_out += size;
+
+ ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+ "brotli out: %p, size:%uz",
+ ctx->out_buf, ngx_buf_size(ctx->out_buf));
+
+ cl = ngx_alloc_chain_link(r->pool);
+ if (cl == NULL) {
+ return NGX_ERROR;
+ }
+
+ cl->buf = ctx->out_buf;
+ cl->next = NULL;
+ *ctx->last_out = cl;
+ ctx->last_out = &cl->next;
+ }
+
+ ctx->brotli_out = NULL;
+ ctx->brotli_last = NULL;
+
+ if (ctx->last || ctx->flush) {
+
+ if (ctx->last) {
+ ctx->done = 1;
+ cl->buf->last_buf = 1;
+
+ } else if (ctx->flush) {
+ ctx->flush = 0;
+ cl->buf->flush = 1;
+ }
+
+ r->connection->buffered &= ~NGX_HTTP_GZIP_BUFFERED;
+
+ return NGX_OK;
+ }
+
+ return NGX_AGAIN;
+}
+
+
+static ngx_int_t
+ngx_http_brotli_filter_get_buf(ngx_http_request_t *r,
+ ngx_http_brotli_ctx_t *ctx)
+{
+ ngx_http_brotli_conf_t *conf;
+
+ if (ctx->free) {
+ ctx->out_buf = ctx->free->buf;
+ ctx->free = ctx->free->next;
+ return NGX_OK;
+ }
+
+ conf = reinterpret_cast<ngx_http_brotli_conf_t *>(
+ ngx_http_get_module_loc_conf(r, ngx_http_brotli_filter_module));
+
+ if (ctx->bufs < conf->bufs.num) {
+ ctx->out_buf = ngx_create_temp_buf(r->pool, conf->bufs.size);
+ if (ctx->out_buf == NULL) {
+ return NGX_ERROR;
+ }
+
+ ctx->out_buf->tag = (ngx_buf_tag_t) &ngx_http_brotli_filter_module;
+ ctx->out_buf->recycled = 1;
+ ctx->bufs++;
+
+ } else {
+ ctx->nomem = 1;
+ return NGX_DECLINED;
+ }
+
+ return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_brotli_ok(ngx_http_request_t *r)
+{
+ ngx_table_elt_t *ae;
+
+ if (r != r->main) {
+ return NGX_DECLINED;
+ }
+
+ ae = r->headers_in.accept_encoding;
+ if (ae == NULL) {
+ return NGX_DECLINED;
+ }
+
+ if (ae->value.len < sizeof("br") - 1) {
+ return NGX_DECLINED;
+ }
+
+ if (ngx_memcmp(ae->value.data, "br,", sizeof("br,") - 1) != 0
+ && ngx_http_brotli_accept_encoding(&ae->value) != NGX_OK)
+ {
+ return NGX_DECLINED;
+ }
+
+ r->gzip_tested = 1;
+ r->gzip_ok = 0;
+
+ return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_brotli_accept_encoding(ngx_str_t *ae)
+{
+ u_char *p;
+
+ p = ngx_strcasestrn(ae->data, const_cast<char *>("br"), sizeof("br") - 2);
+ if (p == NULL) {
+ return NGX_DECLINED;
+ }
+
+ if (p == ae->data || (*(p - 1) == ',' || *(p - 1) == ' ')) {
+
+ p += sizeof("br") - 1;
+
+ if (p == ae->data + ae->len || *p == ',' || *p == ';' || *p == ' ') {
+ return NGX_OK;
+ }
+ }
+
+ return NGX_DECLINED;
+}
+
+
+static ngx_int_t
+ngx_http_brotli_add_variables(ngx_conf_t *cf)
+{
+ ngx_http_variable_t *var;
+
+ var = ngx_http_add_variable(cf, &ngx_http_brotli_ratio, 0);
+ if (var == NULL) {
+ return NGX_ERROR;
+ }
+
+ var->get_handler = ngx_http_brotli_ratio_variable;
+
+ return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_brotli_ratio_variable(ngx_http_request_t *r,
+ ngx_http_variable_value_t *v, uintptr_t data)
+{
+ ngx_uint_t ratio_int, ratio_frac;
+ ngx_http_brotli_ctx_t *ctx;
+
+ v->valid = 1;
+ v->no_cacheable = 0;
+ v->not_found = 0;
+
+ ctx = reinterpret_cast<ngx_http_brotli_ctx_t *>(
+ ngx_http_get_module_ctx(r, ngx_http_brotli_filter_module));
+
+ if (ctx == NULL || ctx->bytes_out == 0 || !ctx->sent) {
+ v->not_found = 1;
+ return NGX_OK;
+ }
+
+ v->data = reinterpret_cast<u_char *>(
+ ngx_pnalloc(r->pool, NGX_INT32_LEN + 3));
+ if (v->data == NULL) {
+ return NGX_ERROR;
+ }
+
+ ratio_int = (ngx_uint_t) (ctx->bytes_in / ctx->bytes_out);
+ ratio_frac = (ngx_uint_t) ((ctx->bytes_in * 100 / ctx->bytes_out) % 100);
+
+ if ((ctx->bytes_in * 1000 / ctx->bytes_out) % 10 > 4) {
+
+ /* the rounding, e.g., 2.125 to 2.13 */
+
+ ratio_frac++;
+
+ if (ratio_frac > 99) {
+ ratio_int++;
+ ratio_frac = 0;
+ }
+ }
+
+ v->len = ngx_sprintf(v->data, "%ui.%02ui", ratio_int, ratio_frac) - v->data;
+
+ return NGX_OK;
+}
+
+
+static void *
+ngx_http_brotli_create_conf(ngx_conf_t *cf)
+{
+ ngx_http_brotli_conf_t *conf;
+
+ conf = reinterpret_cast<ngx_http_brotli_conf_t *>(
+ ngx_pcalloc(cf->pool, sizeof(ngx_http_brotli_conf_t)));
+ if (conf == NULL) {
+ return NULL;
+ }
+
+ /*
+ * set by ngx_pcalloc():
+ *
+ * conf->bufs.num = 0;
+ * conf->types = { NULL };
+ * conf->types_keys = NULL;
+ */
+
+ conf->enable = NGX_CONF_UNSET;
+
+ conf->quality = NGX_CONF_UNSET;
+ conf->win_bits = NGX_CONF_UNSET_SIZE;
+ conf->min_length = NGX_CONF_UNSET;
+
+ return conf;
+}
+
+
+static char *
+ngx_http_brotli_merge_conf(ngx_conf_t *cf, void *parent, void *child)
+{
+ ngx_http_brotli_conf_t *prev;
+ ngx_http_brotli_conf_t *conf;
+
+ prev = reinterpret_cast<ngx_http_brotli_conf_t *>(parent);
+ conf = reinterpret_cast<ngx_http_brotli_conf_t *>(child);
+
+ ngx_conf_merge_value(conf->enable, prev->enable, 0);
+
+ ngx_conf_merge_bufs_value(conf->bufs, prev->bufs,
+ (128 * 1024) / ngx_pagesize, ngx_pagesize);
+
+ ngx_conf_merge_value(conf->quality, prev->quality, 6);
+ ngx_conf_merge_size_value(conf->win_bits, prev->win_bits, 19);
+ ngx_conf_merge_value(conf->min_length, prev->min_length, 20);
+
+ if (ngx_http_merge_types(cf, &conf->types_keys, &conf->types,
+ &prev->types_keys, &prev->types,
+ ngx_http_html_default_types)
+ != NGX_OK)
+ {
+ return reinterpret_cast<char *>(NGX_CONF_ERROR);
+ }
+
+ return reinterpret_cast<char *>(NGX_CONF_OK);
+}
+
+
+static ngx_int_t
+ngx_http_brotli_filter_init(ngx_conf_t *cf)
+{
+ ngx_http_next_header_filter = ngx_http_top_header_filter;
+ ngx_http_top_header_filter = ngx_http_brotli_header_filter;
+
+ ngx_http_next_body_filter = ngx_http_top_body_filter;
+ ngx_http_top_body_filter = ngx_http_brotli_body_filter;
+
+ return NGX_OK;
+}
+
+
+static char *
+ngx_http_brotli_window(ngx_conf_t *cf, void *post, void *data)
+{
+ size_t *np = reinterpret_cast<size_t *>(data);
+
+ size_t wbits, wsize, mbits;
+
+ wbits = brotli::kMaxWindowBits;
+ mbits = brotli::kMinWindowBits;
+
+ for (wsize = (1 << wbits); wsize >= (1U << mbits); wsize >>= 1) {
+
+ if (wsize == *np) {
+ *np = wbits;
+
+ return reinterpret_cast<char *>(NGX_CONF_OK);
+ }
+
+ wbits--;
+ }
+
+ return const_cast<char *>("must be 1k, 2k, 4k, 8k, 16k, 32k, 64k, "
+ "128k, 256k, 512k, 1m, 2m, 4m, 8m or 16m");
+}
diff --git a/src/ngx_http_brotli_static_module.c b/src/ngx_http_brotli_static_module.c
new file mode 100644
index 0000000..15b5bb8
--- /dev/null
+++ b/src/ngx_http_brotli_static_module.c
@@ -0,0 +1,389 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ * Copyright (C) Nginx, Inc.
+ * Copyright (C) Google Inc.
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_http.h>
+
+
+#define NGX_HTTP_BROTLI_STATIC_OFF 0
+#define NGX_HTTP_BROTLI_STATIC_ON 1
+#define NGX_HTTP_BROTLI_STATIC_ALWAYS 2
+
+
+typedef struct {
+ ngx_uint_t enable;
+} ngx_http_brotli_static_conf_t;
+
+
+static ngx_int_t ngx_http_brotli_static_handler(ngx_http_request_t *r);
+
+static ngx_int_t ngx_http_brotli_ok(ngx_http_request_t *r);
+static ngx_int_t ngx_http_brotli_accept_encoding(ngx_str_t *ae);
+
+static void *ngx_http_brotli_static_create_conf(ngx_conf_t *cf);
+static char *ngx_http_brotli_static_merge_conf(ngx_conf_t *cf,
+ void *parent, void *child);
+static ngx_int_t ngx_http_brotli_static_init(ngx_conf_t *cf);
+
+
+static ngx_conf_enum_t ngx_http_brotli_static[] = {
+ { ngx_string("off"), NGX_HTTP_BROTLI_STATIC_OFF },
+ { ngx_string("on"), NGX_HTTP_BROTLI_STATIC_ON },
+ { ngx_string("always"), NGX_HTTP_BROTLI_STATIC_ALWAYS },
+ { ngx_null_string, 0 }
+};
+
+
+static ngx_command_t ngx_http_brotli_static_commands[] = {
+
+ { ngx_string("brotli_static"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
+ ngx_conf_set_enum_slot,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ offsetof(ngx_http_brotli_static_conf_t, enable),
+ &ngx_http_brotli_static },
+
+ ngx_null_command
+};
+
+
+ngx_http_module_t ngx_http_brotli_static_module_ctx = {
+ NULL, /* preconfiguration */
+ ngx_http_brotli_static_init, /* postconfiguration */
+
+ NULL, /* create main configuration */
+ NULL, /* init main configuration */
+
+ NULL, /* create server configuration */
+ NULL, /* merge server configuration */
+
+ ngx_http_brotli_static_create_conf, /* create location configuration */
+ ngx_http_brotli_static_merge_conf /* merge location configuration */
+};
+
+
+ngx_module_t ngx_http_brotli_static_module = {
+ NGX_MODULE_V1,
+ &ngx_http_brotli_static_module_ctx, /* module context */
+ ngx_http_brotli_static_commands, /* module directives */
+ NGX_HTTP_MODULE, /* module type */
+ NULL, /* init master */
+ NULL, /* init module */
+ NULL, /* init process */
+ NULL, /* init thread */
+ NULL, /* exit thread */
+ NULL, /* exit process */
+ NULL, /* exit master */
+ NGX_MODULE_V1_PADDING
+};
+
+
+static ngx_int_t
+ngx_http_brotli_static_handler(ngx_http_request_t *r)
+{
+ u_char *p;
+ size_t root;
+ ngx_str_t path;
+ ngx_int_t rc;
+ ngx_uint_t level;
+ ngx_log_t *log;
+ ngx_buf_t *b;
+ ngx_chain_t out;
+ ngx_table_elt_t *h;
+ ngx_open_file_info_t of;
+ ngx_http_core_loc_conf_t *clcf;
+ ngx_http_brotli_static_conf_t *bscf;
+
+ if (!(r->method & (NGX_HTTP_GET|NGX_HTTP_HEAD))) {
+ return NGX_DECLINED;
+ }
+
+ if (r->uri.data[r->uri.len - 1] == '/') {
+ return NGX_DECLINED;
+ }
+
+ bscf = ngx_http_get_module_loc_conf(r, ngx_http_brotli_static_module);
+
+ if (bscf->enable == NGX_HTTP_BROTLI_STATIC_OFF) {
+ return NGX_DECLINED;
+ }
+
+ if (bscf->enable == NGX_HTTP_BROTLI_STATIC_ON) {
+ rc = ngx_http_brotli_ok(r);
+
+ } else { /* NGX_HTTP_BROTLI_STATIC_ALWAYS */
+ rc = NGX_OK;
+ }
+
+ clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module);
+
+ if (!clcf->gzip_vary && rc != NGX_OK) {
+ return NGX_DECLINED;
+ }
+
+ log = r->connection->log;
+
+ p = ngx_http_map_uri_to_path(r, &path, &root, sizeof(".br") - 1);
+ if (p == NULL) {
+ return NGX_HTTP_INTERNAL_SERVER_ERROR;
+ }
+
+ *p++ = '.';
+ *p++ = 'b';
+ *p++ = 'r';
+ *p = '\0';
+
+ path.len = p - path.data;
+
+ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, log, 0,
+ "http filename: \"%s\"", path.data);
+
+ ngx_memzero(&of, sizeof(ngx_open_file_info_t));
+
+ of.read_ahead = clcf->read_ahead;
+ of.directio = clcf->directio;
+ of.valid = clcf->open_file_cache_valid;
+ of.min_uses = clcf->open_file_cache_min_uses;
+ of.errors = clcf->open_file_cache_errors;
+ of.events = clcf->open_file_cache_events;
+
+ if (ngx_http_set_disable_symlinks(r, clcf, &path, &of) != NGX_OK) {
+ return NGX_HTTP_INTERNAL_SERVER_ERROR;
+ }
+
+ if (ngx_open_cached_file(clcf->open_file_cache, &path, &of, r->pool)
+ != NGX_OK)
+ {
+ switch (of.err) {
+
+ case 0:
+ return NGX_HTTP_INTERNAL_SERVER_ERROR;
+
+ case NGX_ENOENT:
+ case NGX_ENOTDIR:
+ case NGX_ENAMETOOLONG:
+
+ return NGX_DECLINED;
+
+ case NGX_EACCES:
+#if (NGX_HAVE_OPENAT)
+ case NGX_EMLINK:
+ case NGX_ELOOP:
+#endif
+
+ level = NGX_LOG_ERR;
+ break;
+
+ default:
+
+ level = NGX_LOG_CRIT;
+ break;
+ }
+
+ ngx_log_error(level, log, of.err,
+ "%s \"%s\" failed", of.failed, path.data);
+
+ return NGX_DECLINED;
+ }
+
+ if (bscf->enable == NGX_HTTP_BROTLI_STATIC_ON) {
+ r->gzip_vary = 1;
+
+ if (rc != NGX_OK) {
+ return NGX_DECLINED;
+ }
+ }
+
+ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, log, 0, "http static fd: %d", of.fd);
+
+ if (of.is_dir) {
+ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, log, 0, "http dir");
+ return NGX_DECLINED;
+ }
+
+#if !(NGX_WIN32) /* the not regular files are probably Unix specific */
+
+ if (!of.is_file) {
+ ngx_log_error(NGX_LOG_CRIT, log, 0,
+ "\"%s\" is not a regular file", path.data);
+
+ return NGX_HTTP_NOT_FOUND;
+ }
+
+#endif
+
+ r->root_tested = !r->error_page;
+
+ rc = ngx_http_discard_request_body(r);
+
+ if (rc != NGX_OK) {
+ return rc;
+ }
+
+ log->action = "sending response to client";
+
+ r->headers_out.status = NGX_HTTP_OK;
+ r->headers_out.content_length_n = of.size;
+ r->headers_out.last_modified_time = of.mtime;
+
+ if (ngx_http_set_etag(r) != NGX_OK) {
+ return NGX_HTTP_INTERNAL_SERVER_ERROR;
+ }
+
+ if (ngx_http_set_content_type(r) != NGX_OK) {
+ return NGX_HTTP_INTERNAL_SERVER_ERROR;
+ }
+
+ h = ngx_list_push(&r->headers_out.headers);
+ if (h == NULL) {
+ return NGX_HTTP_INTERNAL_SERVER_ERROR;
+ }
+
+ h->hash = 1;
+ ngx_str_set(&h->key, "Content-Encoding");
+ ngx_str_set(&h->value, "br");
+ r->headers_out.content_encoding = h;
+
+ /* we need to allocate all before the header would be sent */
+
+ b = ngx_pcalloc(r->pool, sizeof(ngx_buf_t));
+ if (b == NULL) {
+ return NGX_HTTP_INTERNAL_SERVER_ERROR;
+ }
+
+ b->file = ngx_pcalloc(r->pool, sizeof(ngx_file_t));
+ if (b->file == NULL) {
+ return NGX_HTTP_INTERNAL_SERVER_ERROR;
+ }
+
+ rc = ngx_http_send_header(r);
+
+ if (rc == NGX_ERROR || rc > NGX_OK || r->header_only) {
+ return rc;
+ }
+
+ b->file_pos = 0;
+ b->file_last = of.size;
+
+ b->in_file = b->file_last ? 1 : 0;
+ b->last_buf = (r == r->main) ? 1 : 0;
+ b->last_in_chain = 1;
+
+ b->file->fd = of.fd;
+ b->file->name = path;
+ b->file->log = log;
+ b->file->directio = of.is_directio;
+
+ out.buf = b;
+ out.next = NULL;
+
+ return ngx_http_output_filter(r, &out);
+}
+
+
+static ngx_int_t
+ngx_http_brotli_ok(ngx_http_request_t *r)
+{
+ ngx_table_elt_t *ae;
+
+ if (r != r->main) {
+ return NGX_DECLINED;
+ }
+
+ ae = r->headers_in.accept_encoding;
+ if (ae == NULL) {
+ return NGX_DECLINED;
+ }
+
+ if (ae->value.len < sizeof("br") - 1) {
+ return NGX_DECLINED;
+ }
+
+ if (ngx_memcmp(ae->value.data, "br,", sizeof("br,") - 1) != 0
+ && ngx_http_brotli_accept_encoding(&ae->value) != NGX_OK)
+ {
+ return NGX_DECLINED;
+ }
+
+ r->gzip_tested = 1;
+ r->gzip_ok = 0;
+
+ return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_brotli_accept_encoding(ngx_str_t *ae)
+{
+ u_char *p;
+
+ p = ngx_strcasestrn(ae->data, "br", sizeof("br") - 2);
+ if (p == NULL) {
+ return NGX_DECLINED;
+ }
+
+ if (p == ae->data || (*(p - 1) == ',' || *(p - 1) == ' ')) {
+
+ p += sizeof("br") - 1;
+
+ if (p == ae->data + ae->len || *p == ',' || *p == ';' || *p == ' ') {
+ return NGX_OK;
+ }
+ }
+
+ return NGX_DECLINED;
+}
+
+
+static void *
+ngx_http_brotli_static_create_conf(ngx_conf_t *cf)
+{
+ ngx_http_brotli_static_conf_t *conf;
+
+ conf = ngx_palloc(cf->pool, sizeof(ngx_http_brotli_static_conf_t));
+ if (conf == NULL) {
+ return NULL;
+ }
+
+ conf->enable = NGX_CONF_UNSET_UINT;
+
+ return conf;
+}
+
+
+static char *
+ngx_http_brotli_static_merge_conf(ngx_conf_t *cf, void *parent, void *child)
+{
+ ngx_http_brotli_static_conf_t *prev = parent;
+ ngx_http_brotli_static_conf_t *conf = child;
+
+ ngx_conf_merge_uint_value(conf->enable, prev->enable,
+ NGX_HTTP_BROTLI_STATIC_OFF);
+
+ return NGX_CONF_OK;
+}
+
+
+static ngx_int_t
+ngx_http_brotli_static_init(ngx_conf_t *cf)
+{
+ ngx_http_handler_pt *h;
+ ngx_http_core_main_conf_t *cmcf;
+
+ cmcf = ngx_http_conf_get_module_main_conf(cf, ngx_http_core_module);
+
+ h = ngx_array_push(&cmcf->phases[NGX_HTTP_CONTENT_PHASE].handlers);
+ if (h == NULL) {
+ return NGX_ERROR;
+ }
+
+ *h = ngx_http_brotli_static_handler;
+
+ return NGX_OK;
+}