Skip to content
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

[WIP] HTTP Compression Support #1368

Draft
wants to merge 10 commits into
base: master
Choose a base branch
from
Draft

[WIP] HTTP Compression Support #1368

wants to merge 10 commits into from

Conversation

ac000
Copy link
Member

@ac000 ac000 commented Jul 23, 2024

First things first, as the title suggests, this is very much a 'Work In Progress'.

The code is in a mixture of Unit and Kernel coding style, full of debugging,
subject to change and it's all in one big patch.

I'm posting this in its current form to show current progress and the overall
approach taken.

This has gone through several iterations up to this point

V1

Initial version which worked similarly to @alejandro-colomar's version by
doing the compression from nxt_http_request_send()

This has some issues

  1. We can't set the Content-Length, it's too late at this point, so
    compressed responses are sent chunked.

  2. Creates a new buffer (via malloc(3)) to store the compressed data, then
    copies it over to the response buffer (overwriting what was there).

  3. We have an issue if we are compressing data which then takes up more
    space than we have in the response buffer, this would likely hit for
    small data sizes where the compressed data + meta data would be larger
    than the original data. This can be handled as @alejandro-colomar did by
    allocating a new buffer and swapping it with the current one.

  4. Slightly convoluted logic to determine if we are on the last buffer,
    so the compressor can know to properly finish up.

This approach did have the advantage of also handling application responses.

V2

In this version the compression is done in nxt_http_static_buf_completion().

This has the advantage that we can allocate large enough output buffers in
nxt_http_static_body_handler() to allow for the maximum compressed size of
the data.

It is also easier to tell if we are on the last buffer.

  1. This still has issue (1) above.

  2. This uses a static buffer of NXT_HTTP_STATIC_BUF_SIZE (128KiB) as a
    temporary buffer to read in chunks of the file to be compressed.

V3

This version uses mmap(2) to map the file to be compressed into memory, we
can then read from this mapping directly without the need to read the file
into a temporary buffer.

  1. This still has issue (1) above.

V4 (this version)

Like V3 above we mmap(2) the file to be compressed. However we do this directly
in nxt_http_static_send_ready() where we we read the file to be compressed in.

We also mmap(2) a temporary output file (created via mkstemp(3)) initially sized
to the maximum compressed size of the file.

We then compress the input mmap'd file into the output mmap'd file, negating the
need for any intermediate buffering, saving extraneous copies and stack space (or
the overhead of heap allocations) for said buffer.

Finally, we ftruncate(2) this new output file to its actual size.

This file is what is then used in nxt_http_static_body_handler() to do the output
buffer allocations and so we don't need to make any modifications to that function.

This also allows us to correctly set the Content-Length header.

Other than V1, these don't handle application responses which will need
handling separately.

What works

This supports; deflate, gzip, zstd & brotli compression. This is all opt-in, e.g

$ ./configure ... --zlib --zstd --brotli
...
checking for getgrouplist() ... found
checking for zlib ... found
 + zlib version: 1.3.1.zlib-ng
checking for zstd ... found
 + zstd version: 1.5.6
checking for brotli ... found
 + brotli version: 1.1.0
checking for PCRE2 library ... found
...
  TLS support: ............... NO
  zlib support: .............. YES
  zstd support: .............. YES
  brotli support: ............ YES
  Regex support: ............. YES
...

The compressors src/nxt_{zlib,zstd,brotli}.c themselves are nicely isolated from the Unit core and are
just the bare minimum required to do the actual compression.

Configuration may look like

{
    "listeners": {
        "[::1]:8080": {
            "pass": "routes"
        }
    },

    "settings": {
        "http": {
            "static": {
                "mime_types": {
                    "text/x-c": [
                        ".c",
                        ".h"
                    ]
                }
            },
            "compression": {
                "types": [
                        "text/*"
                ],
                "compressors": [
                    {
                        "encoding": "gzip",
                        "level": 3,
                        "min_length": 2048
                    },
                    {
                        "encoding": "deflate",
                        "min_length": 1024
                    },
                    {
                        "encoding": "zstd",
                        "min_length": 2048
                    },
                    {
                        "encoding": "br",
                        "min_length": 256
                    }
                ]
            }
        }
    },

    "routes": [
        {
            "match": {
                "uri": "*"
            },

            "action": {
                "share": "/srv/unit-share$uri"
            }
        }
    ]
}

This adds a new settings.http.compression config option. Under here we can define the mime-types we want to compress and what compressors we want to use. For each compressor we can set the compression level and the minimum length of content to compress.

This could be extended for example to allow for per-compressor mime-type overrides.

Todo

As mentioned above this currently only handles compressing static share content. Compressing application responses needs to be handled separately.

While this tries to handle more complex Accept-Encoding headers e.g gzip;q=1.0, identity;q=0.5, *;q=0 this no doubt requires a little more work.

@ac000 ac000 linked an issue Jul 23, 2024 that may be closed by this pull request
@ac000
Copy link
Member Author

ac000 commented Jul 23, 2024

Properly handle no compressors enabled

$ git range-diff 1cc7236b...92f215d1
1:  1cc7236b ! 1:  92f215d1 [WIP] HTTP Compression Support
    @@ src/nxt_http_compression.c (new)
     +
     +    compressor_ctx = (nxt_http_comp_ctx_t){ .resp_clen = -1 };
     +
    ++    if (nr_enabled_compressors == 0) {
    ++        return NXT_OK;
    ++    }
    ++
     +    if (r->resp.content_length_n == 0) {
     +        return NXT_OK;
     +    }

nxt_str_t enc;
nxt_http_comp_scheme_t scheme;

tkn = strtok_r(str, ", ", &saveptr);
Copy link
Contributor

@alejandro-colomar alejandro-colomar Jul 23, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi @ac000,

strtok_r(3) isn't very good for this, since it will also split on .

Let's take the following example, taken literally from RFC 9110:

Accept-Encoding: gzip;q=1.0, identity; q=0.5, *;q=0

One of the tokens would be q=0.5.

What you want is to separate on ,, and after that trim whitespace.

Another problem of strtok_r(3) is that it merges adjacent delimiters, which would be bogus input and should be rejected.

strsep(3) doesn't have that issue.

You probably want:

#define stpspn(s, accept)  (s + strspn(s, accept))

c = stpspn(strsep(&str, ","), " ");
w = stpsep(c, ";");
stpsep(c, " ");
if (w != NULL) {
        w = stpspn(w, " ");
        stpsep(w, " ");
}

With stpsep() being a function of mine similar to strsep(3).

Bad part: strsep(3) is a GNU extension.

break;
}

qptr = strstr(tkn, ";q=");
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is brittle. RFC 9110 accepts whitespace after ;.

@ac000
Copy link
Member Author

ac000 commented Jul 23, 2024 via email

@ac000
Copy link
Member Author

ac000 commented Jul 23, 2024 via email

@ac000
Copy link
Member Author

ac000 commented Jul 23, 2024

On Tue, 23 Jul 2024 08:03:33 -0700 Alejandro Colomar @.***> wrote: With stpsep() being a function of mine similar to strsep(3). Bad part: strsep(3) is a GNU extension.
Which is why I didn't use it.

However if it's available on macOS (seems so) the BSD's (looks like it probably is) and musl libc, then it may be a goer...

@alejandro-colomar
Copy link
Contributor

alejandro-colomar commented Jul 23, 2024 via email

@ac000
Copy link
Member Author

ac000 commented Sep 24, 2024

Rebased with master...

$ git range-diff 92f215d1...30df48f3
 -:  -------- >  1:  57a75ea0 build(deps): bump openssl from 0.10.64 to 0.10.66 in /tools/unitctl
 -:  -------- >  2:  b892e994 tools/unitctl: update readme
 -:  -------- >  3:  1b484303 tools/unitctl: update readme
 -:  -------- >  4:  e743b6ce tools/unitctl: remove (default) from option text
 -:  -------- >  5:  2a243740 tools/unitctl: make json-pretty default output fmt
 -:  -------- >  6:  43faf99d tools/unitctl: reword freeform message for output
 -:  -------- >  7:  a91b961d tools/unitctl: make application directory configurable
 -:  -------- >  8:  e56c4ede fuzzing: code cleanup
 -:  -------- >  9:  900d25c3 fuzzing: fixed harness bug
 -:  -------- > 10:  bc49274d fuzzing: updated JSON target
 -:  -------- > 11:  3667c3e2 fuzzing: added new basic targets
 -:  -------- > 12:  06c4ea1f Add a basic .editorconfig file
 -:  -------- > 13:  0f313e2b CONTRIBUTING.md: Re-flow text
 -:  -------- > 14:  20224279 CONTRIBUTING.md: Update the 'Git Style Guide' section
 -:  -------- > 15:  5d32e500 Packaging: fix build-depends on multiarch debian systems
 -:  -------- > 16:  12c376ab README: Update number of supported languages
 -:  -------- > 17:  11a70a32 docs/openapi: Update the /status endpoint URL
 -:  -------- > 18:  ae4795aa docs/openapi: Add entries for the new /status/modules endpoint
 -:  -------- > 19:  f38201c2 auto: Add a check for Linux's sched_getaffinity(2)
 -:  -------- > 20:  2444d45e lib: Better available cpu count determination on Linux
 -:  -------- > 21:  57c88fd4 router: Make the number of router threads configurable
 -:  -------- > 22:  97c15fa3 socket: Use a default listen backlog of -1 on Linux
 -:  -------- > 23:  76489fb7 conf, router: Make the listen(2) backlog configurable
 -:  -------- > 24:  2eecee75 var: Restrict nxt_tstr_query() to only support synchronous operation
 -:  -------- > 25:  76a255b2 http: Refactor return action
 -:  -------- > 26:  08a23272 http: Refactor route pass query
 -:  -------- > 27:  9d19e7e0 http: Refactor static action
 -:  -------- > 28:  ecb3f86c http: Refactor access log write
 -:  -------- > 29:  5f6ae1a1 var: Remove unused functions and structure fields
 -:  -------- > 30:  82e168fe http: Refactor out nxt_tstr_cond_t from the access log module
 -:  -------- > 31:  57f93956 http: Get rid of nxt_http_request_access_log()
 -:  -------- > 32:  debd61c3 http: Add "if" option to the "match" object
 -:  -------- > 33:  43c4bfdc tests: "if" option in http route match
 -:  -------- > 34:  593564fd ci/unitctl: Update paths
 -:  -------- > 35:  cad6aed5 Tests: initial "wasm-wasi-component" test
 -:  -------- > 36:  05b1c769 docs/openapi: Fix brokenness
 -:  -------- > 37:  cff18f89 docs/openapi: Add new config options
 -:  -------- > 38:  71920769 fuzzing: fixed harness bug
 -:  -------- > 39:  932b9146 socket: Prevent buffer under-read in nxt_inet_addr()
 -:  -------- > 40:  1a685084 Docker: bump Go versions
 -:  -------- > 41:  5b47542e Docker: update Rust version
 -:  -------- > 42:  8eb5d128 Docker: introduce "slim" python images
 -:  -------- > 43:  4778099b Docker: leave artifacts when build targets succeed
 -:  -------- > 44:  6e3152c0 Remove .hgtags
 -:  -------- > 45:  778d81cc Remove .hgignore files
 -:  -------- > 46:  0951778d Added .gitignore for pkg/contrib/tarballs
 -:  -------- > 47:  f4298f94 tests: Fix `/status' endpoint to cater for lists
 -:  -------- > 48:  264f4af4 test/wasm-wc: Target wasm32-wasip1
 -:  -------- > 49:  19cd88ef test/wasm-wc: Rename test_wasm_component.py
 -:  -------- > 50:  337cba43 ci: Enable the wasm-wasi-component tests
 -:  -------- > 51:  011071aa wasm-wc: bump wasmtime to v24
 -:  -------- > 52:  56c237b3 wasm-wc: Enable environment inheritance
 -:  -------- > 53:  27d3a5c7 java: Update third-party components
 -:  -------- > 54:  5c58f9d0 ci: Fix tags on ad hoc unitctl releases
 -:  -------- > 55:  9998918d Packages: bump wasmtime to 24.0.0 and wasi-sysroot to 24.0.
 -:  -------- > 56:  c5846ba3 ci: Fix wasmtime paths in ci.yml
 -:  -------- > 57:  46ddb010 ci: Trigger ci.yml for changes under pkg/contrib
 -:  -------- > 58:  6976a614 tests: Fix routing tests in the no njs case
 -:  -------- > 59:  cff5e092 tests: Suppress cargo-component output
 -:  -------- > 60:  5fde2ff7 http: Fix router process crash whilst using proxy
 -:  -------- > 61:  50b1aca3 python: Don't decrement a reference to a borrowed object
 -:  -------- > 62:  3c563849 unitctl: Don't track unit-openapi/.openapi-generator/
 -:  -------- > 63:  63148a31 tools/unitctl: whitespace fixes
 -:  -------- > 64:  5e8a6893 tools/unitctl: rename app -> apps, fix readme
 -:  -------- > 65:  f2e05bc7 docs: remove security.txt file
 -:  -------- > 66:  cc863e15 docs: add SECURITY.md
 -:  -------- > 67:  0dcd3a91 tools/unitctl: rename UNIT -> Unit
 -:  -------- > 68:  9e5f961b tools/unitctl: add export subcommand to readme
 -:  -------- > 69:  7c48546a tools/unitctl: adjust readme for socket addresses
 -:  -------- > 70:  15f76506 tools/unitctl: change reload to restart
 -:  -------- > 71:  f7771378 pkg/docker: Update dockerfiles for 1.33.0
 -:  -------- > 72:  3144710f tools/unitctl: Update for version 1.33.0
 -:  -------- > 73:  c3d6e540 docs/changes.xml: Add 1.33.0 changelog entries
 -:  -------- > 74:  24ed91f4 Add 1.33.0 CHANGES
 -:  -------- > 75:  4d627c8f docs/unit-openapi.yaml: Update version for 1.33.0
 -:  -------- > 76:  ba234b4d Version bump
 -:  -------- > 77:  355f038f Compile with -funsigned-char
 -:  -------- > 78:  b358e7fb Resolve unused assignment in nxt_term_parse()
 -:  -------- > 79:  a9d687e7 src/test: Add an extra test case to nxt_term_parse_test.c
 -:  -------- > 80:  1c75aab3 tools/unitctl: use hyper-rustls instead of hyper-tls
 -:  -------- > 81:  ac902548 tools/unitctl: bump bollard and clarify docker client error
 1:  92f215d1 ! 82:  30df48f3 [WIP] HTTP Compression Support
    @@ src/nxt_http_static.c
      
      
      typedef struct {
    -@@ src/nxt_http_static.c: nxt_http_static_send_ready(nxt_task_t *task, void *obj, void *data)
    -     nxt_http_static_ctx_t   *ctx;
    +@@ src/nxt_http_static.c: nxt_http_static_send(nxt_task_t *task, nxt_http_request_t *r,
    +     nxt_work_handler_t      body_handler;
          nxt_http_static_conf_t  *conf;
      
     +    printf("%s: \n", __func__);
     +
    -     r = obj;
    -     ctx = data;
          action = ctx->action;
    -@@ src/nxt_http_static.c: nxt_http_static_send_ready(nxt_task_t *task, void *obj, void *data)
    +     conf = action->u.conf;
    +     rtcf = r->conf->socket_conf->router_conf;
    +@@ src/nxt_http_static.c: nxt_http_static_send(nxt_task_t *task, nxt_http_request_t *r,
                  field->value_length = mtype->length;
              }
      
    @@ src/nxt_http_static.c: nxt_http_static_buf_completion(nxt_task_t *task, void *ob
          b = obj;
          r = data;
      
    -@@ src/nxt_http_static.c: nxt_http_static_buf_completion(nxt_task_t *task, void *obj, void *data)
    - 
    -     if (n == rest) {
    -         nxt_file_close(task, fb->file);
    -+
    -         r->out = NULL;
    - 
    -         b->next = nxt_http_buf_last(r);
     
      ## src/nxt_router.c ##
     @@

@ac000
Copy link
Member Author

ac000 commented Sep 24, 2024

Fix compilation (nxt_tstr_query_failed() is no longer a thing)

$ git range-diff 30df48f3...48980f9c
1:  30df48f3 ! 1:  48980f9c [WIP] HTTP Compression Support
    @@ src/nxt_http_compression.c (new)
     +        return NXT_ERROR;
     +    }
     +
    -+    nxt_tstr_query(task, r->tstr_query, accept_encoding_query,
    -+                   &accept_encoding);
    -+    if (nxt_slow_path(nxt_tstr_query_failed(r->tstr_query))) {
    ++    ret = nxt_tstr_query(task, r->tstr_query, accept_encoding_query,
    ++                         &accept_encoding);
    ++    if (nxt_slow_path(ret != NXT_OK)) {
     +        return NXT_ERROR;
     +    }
     +

@uhlhosting
Copy link

How close to merge?

@ac000
Copy link
Member Author

ac000 commented Oct 10, 2024

  • Some interface constification
  • Initial application response compression (basically doing it the same was as in V1)
$ git range-diff 48980f9c...628302a4
1:  48980f9c ! 1:  46bcc2c5 [WIP] HTTP Compression Support
    @@ src/nxt_brotli.c (new)
     +    return BrotliEncoderMaxCompressedSize(in_len);
     +}
     +
    -+static ssize_t nxt_brotli_compress(nxt_http_comp_compressor_ctx_t *ctx,
    ++static ssize_t nxt_brotli_compress(const nxt_http_comp_compressor_ctx_t *ctx,
     +                                   const uint8_t *in_buf, size_t in_len,
     +                                   uint8_t *out_buf, size_t out_len, bool last)
     +{
    @@ src/nxt_http_compression.c (new)
     +{
     +    nxt_http_comp_compressor_t        *compressor;
     +    const nxt_http_comp_operations_t  *cops;
    ++    const nxt_http_comp_ctx_t *ctx = &compressor_ctx;
     +
    -+    compressor = &enabled_compressors[compressor_ctx.idx];
    ++    compressor = &enabled_compressors[ctx->idx];
     +    cops = compressor->type->cops;
     +
    -+    return cops->bound(&compressor_ctx.ctx, size);
    ++    return cops->bound(&ctx->ctx, size);
     +}
     +
     +ssize_t
    @@ src/nxt_http_compression.c (new)
     +{
     +    nxt_http_comp_compressor_t        *compressor;
     +    const nxt_http_comp_operations_t  *cops;
    ++    const nxt_http_comp_ctx_t *ctx = &compressor_ctx;
     +
    -+    compressor = &enabled_compressors[compressor_ctx.idx];
    ++    compressor = &enabled_compressors[ctx->idx];
     +    cops = compressor->type->cops;
     +
    -+    return cops->deflate(&compressor_ctx.ctx, src, src_size, dst, dst_size,
    -+                         last);
    ++    return cops->deflate(&ctx->ctx, src, src_size, dst, dst_size, last);
     +}
     +
     +static nxt_uint_t
    @@ src/nxt_http_compression.c (new)
     +}
     +
     +static nxt_int_t
    -+nxt_http_comp_set_header(nxt_http_request_t *r, nxt_uint_t index)
    ++nxt_http_comp_set_header(nxt_http_request_t *r, nxt_uint_t comp_idx)
     +{
     +    const nxt_str_t *token;
     +    nxt_http_field_t *f;
    @@ src/nxt_http_compression.c (new)
     +        return NXT_ERROR;
     +    }
     +
    -+    token = &enabled_compressors[index].type->token;
    ++    token = &enabled_compressors[comp_idx].type->token;
     +
     +    *f = (nxt_http_field_t){ };
     +
    @@ src/nxt_http_compression.c (new)
     +
     +    min_len = compressor->opts.min_len;
     +
    -+    printf("%s: content_lewngth [%ld] min_len [%ld]\n", __func__,
    ++    printf("%s: content_length [%ld] min_len [%ld]\n", __func__,
     +           compressor_ctx.resp_clen, min_len);
     +    if (compressor_ctx.resp_clen > -1 && compressor_ctx.resp_clen < min_len) {
     +        printf("%s: %ld < %ld [skipping/clen]\n", __func__,
    @@ src/nxt_http_compression.h (new)
     +    void     (*init)(nxt_http_comp_compressor_ctx_t *ctx);
     +    size_t   (*bound)(const nxt_http_comp_compressor_ctx_t *ctx,
     +                      size_t in_len);
    -+    ssize_t  (*deflate)(nxt_http_comp_compressor_ctx_t *ctx,
    ++    ssize_t  (*deflate)(const nxt_http_comp_compressor_ctx_t *ctx,
     +                        const uint8_t *in_buf, size_t in_len,
     +                        uint8_t *out_buf, size_t out_len, bool last);
     +    void     (*free_ctx)(const nxt_http_comp_compressor_ctx_t *ctx);
    @@ src/nxt_zlib.c (new)
     +    return deflateBound(z, in_len);
     +}
     +
    -+static ssize_t nxt_zlib_deflate(nxt_http_comp_compressor_ctx_t *ctx,
    ++static ssize_t nxt_zlib_deflate(const nxt_http_comp_compressor_ctx_t *ctx,
     +                                const uint8_t *in_buf, size_t in_len,
     +                                uint8_t *out_buf, size_t out_len, bool last)
     +{
     +    int ret;
    -+    z_stream *z = &ctx->zlib_ctx;
    ++    z_stream *z = (z_stream *)&ctx->zlib_ctx;
     +    size_t compressed_bytes = z->total_out;
     +
     +    z->avail_in = in_len;
    @@ src/nxt_zstd.c (new)
     +    return ZSTD_compressBound(in_len);
     +}
     +
    -+static ssize_t nxt_zstd_compress(nxt_http_comp_compressor_ctx_t *ctx,
    ++static ssize_t nxt_zstd_compress(const nxt_http_comp_compressor_ctx_t *ctx,
     +                                 const uint8_t *in_buf, size_t in_len,
     +                                 uint8_t *out_buf, size_t out_len, bool last)
     +{
-:  -------- > 2:  628302a4 [WIP] Compress application responses

ac000 and others added 10 commits November 20, 2024 22:52
This is to store the MIME type of the response which will be used by the
HTTP compression patches as part of determining whether or not to
compress the response.

Signed-off-by: Andrew Clayton <[email protected]>
This is the initial step to enabling HTTP compression on both static and
application responses.

This code itself doesn't do any actual compression, that will come in
subsequent commits. It just contains the core functions for initialising
structures that describe the available compressors and functions for
checking if compression should be done depending on various criteria.

Signed-off-by: Andrew Clayton <[email protected]>
This adds support for both deflate & gzip compressors.

Signed-off-by: Andrew Clayton <[email protected]>
This allows to actually build unit with support fro zlib, zstd and
brotli compression.

Any or all can be specified. E.g.

  $ ./configure --zlib ...

  $ ./configure --zlib --zstd --brotli ...

During configure you will see if support for the requested compressions
has been found and what version of the library is being used.

E.g.

  ...
  checking for zlib ... found
   + zlib version: 1.3.1.zlib-ng
  checking for zstd ... found
   + zstd version: 1.5.6
  checking for brotli ... found
   + brotli version: 1.1.0
  ...
  Unit configuration summary:
  ...
    zlib support: .............. YES
    zstd support: .............. YES
    brotli support: ............ YES
  ...

Co-authored-by: Alejandro Colomar <[email protected]>
Signed-off-by: Alejandro Colomar <[email protected]>
Signed-off-by: Andrew Clayton <[email protected]>
This exposes a new "settings.http.compression" configuration object.

Under which are types & compressors objects.

types is used to specify what MIME types should be considered
compressible.

compressors is used to configure an array of compressors that are
available. For each of these, you specify the encoding, e.g gzip and
optional level and min_length parameters. Where level is what
compression level to use and min_length is the minimum length of data
that should be compressed.

By default the default compression level for the specified compressor is
used and there is no minimum data length considered for compression.

It may look something like

    "settings": {
        "http": {
            "server_version": true,
            "static": {
                "mime_types": {
                    "text/x-c": [
                        ".c",
                        ".h"
                    ]
                }
            },
            "compression": {
                "types": [
                    "text/*"
                ],
                "compressors": [
                    {
                        "encoding": "gzip",
                        "level": 3,
                        "min_length": 2048
                    },
                    {
                        "encoding": "deflate",
                        "min_length": 1024
                    },
                    {
                        "encoding": "zstd",
                        "min_length": 2048
                    },
                    {
                        "encoding": "br",
                        "min_length": 256
                    }
                ]
            }
        }
    },

Currently this is a global option that will effect both static and
application responses.

In future it should be possible to add per-application (and perhaps even
per-static) configuration.

Signed-off-by: Andrew Clayton <[email protected]>
Signed-off-by: Andrew Clayton <[email protected]>
Co-authored-by: Alejandro Colomar <[email protected]>
Signed-off-by: Alejandro Colomar <[email protected]>
Signed-off-by: Andrew Clayton <[email protected]>
@ac000
Copy link
Member Author

ac000 commented Nov 21, 2024

  • Rebased with master
  • Split up into more logical patches
  • Coding style fixes (should now be fully Unit coding syule, more or less)

TODO

  • Some more configuration validation
  • Make the Accept-Encoding header parsing more robust
  • More complete error handling

NOTE: It still contains a tonne of debugging and a few compiler warnings (build with E=0)

$ git range-diff 628302a4...2fd2cd6e
 -:  -------- >  1:  0e4342fa Re-work nxt_process_check_pid_status() slightly
 -:  -------- >  2:  cc2a1cc3 wasm-wc: Bump the wasmtime crate from 24.0.0 to 24.0.1
 -:  -------- >  3:  e03697ba ci: Fix disabling of the mono-xsp4.service
 -:  -------- >  4:  4601db64 ci: Install pytest via apt(8)
 -:  -------- >  5:  75b72318 ci: Drop PHP 8.1 from our tests
 -:  -------- >  6:  f6036bbc perl: Remove unused module constructor
 -:  -------- >  7:  de430eda Add flag for newline control in access log entries
 -:  -------- >  8:  76cc071a Fix missing newlines in access logs for JS configuration
 -:  -------- >  9:  ebd02c60 Some more variable constification
 -:  -------- > 10:  85f21b7c Use nxt_nitems() instead of sizeof() for strings (arrays)
 -:  -------- > 11:  9f6f4866 src/test: Fix missing parameter to nxt_log_alert() in nxt_base64_test()
 -:  -------- > 12:  1e345b34 ci: Add a clang-ast workflow
 -:  -------- > 13:  158322ec auto: Remove unused pthread spinlock checks
 -:  -------- > 14:  e6519b9d wasm-wc: Update to wasmtime v26.0.1
 -:  -------- > 15:  0391a3ca java: Update third-party components to their recent versions
 -:  -------- > 16:  8b697101 otel: add opentelemetry rust crate code
 -:  -------- > 17:  9d3dcb80 otel: add build tooling to include otel code
 -:  -------- > 18:  b9066210 otel: add header parsing and test call state
 -:  -------- > 19:  586c5b53 otel: configuration items and their validation
 -:  -------- > 20:  7f81464b .editorconfig: fix bracket balance of editorconfig file
 -:  -------- > 21:  e1fd14f7 docs/openapi: update OpenAPI references
 -:  -------- > 22:  2d4624f0 Make nxt_tstr_is_js() macro public in header
 -:  -------- > 23:  a92d8149 http: Refactor format field in nxt_router_access_log_conf_t
 -:  -------- > 24:  a760e24a http: Introduce nxt_router_access_log_format_t structure
 -:  -------- > 25:  bc838c5e http: Support JSON format in access log
 -:  -------- > 26:  a3551790 tests: Add tests for JSON format access log
 -:  -------- > 27:  4f041328 Decast nxt_cpymem()
 -:  -------- > 28:  dc638026 http: Add a mime_type member to nxt_http_response_t
 1:  46bcc2c5 ! 29:  b7802ce9 [WIP] HTTP Compression Support
    @@ Metadata
     Author: Andrew Clayton <[email protected]>
     
      ## Commit message ##
    -    [WIP] HTTP Compression Support
    +    http: Add core http compression code
     
    - ## auto/compression (new) ##
    -@@
    -+
    -+# Copyright (C) Alejandro Colomar
    -+# Copyright (C) Andrew Clayton
    -+# Copyright (C) NGINX, Inc.
    -+
    -+
    -+NXT_HAVE_ZLIB=no
    -+NXT_ZLIB_CFLAGS=
    -+NXT_ZLIB_LIBS=
    -+
    -+NXT_HAVE_ZSTD=no
    -+NXT_ZSTD_CFLAGS=
    -+NXT_ZSTD_LIBS=
    -+
    -+NXT_HAVE_BROTLI=no
    -+NXT_BROTLI_CFLAGS=
    -+NXT_BROTLI_LIBS=
    -+
    -+
    -+if [ $NXT_ZLIB = YES ]; then
    -+    NXT_ZLIB_CFLAGS="$(pkgconf --cflags-only-I zlib 2>/dev/null || echo "")"
    -+    NXT_ZLIB_LIBS="$(pkgconf --libs zlib 2>/dev/null || echo "-lz")"
    -+
    -+    nxt_feature="zlib"
    -+    nxt_feature_name=NXT_HAVE_ZLIB
    -+    nxt_feature_run=no
    -+    nxt_feature_incs=$NXT_ZLIB_CFLAGS
    -+    nxt_feature_libs=$NXT_ZLIB_LIBS
    -+    nxt_feature_test="#include <stdio.h>
    -+
    -+                      #include <zlib.h>
    -+
    -+                      int main(void) {
    -+                          puts(zlibVersion());
    -+                          return 0;
    -+                      }"
    -+    . auto/feature
    -+
    -+    if [ $nxt_found = yes ]; then
    -+        NXT_HAVE_ZLIB=YES
    -+        echo " + zlib version: $(pkgconf --modversion zlib)"
    -+    fi
    -+fi
    -+
    -+
    -+if [ $NXT_ZSTD = YES ]; then
    -+    NXT_ZSTD_CFLAGS="$(pkgconf --cflags-only-I libzstd 2>/dev/null || echo "")"
    -+    NXT_ZSTD_LIBS="$(pkgconf --libs libzstd 2>/dev/null || echo "-lzstd")"
    -+
    -+    nxt_feature="zstd"
    -+    nxt_feature_name=NXT_HAVE_ZSTD
    -+    nxt_feature_run=no
    -+    nxt_feature_incs=$NXT_ZSTD_CFLAGS
    -+    nxt_feature_libs=$NXT_ZSTD_LIBS
    -+    nxt_feature_test="#include <stdio.h>
    -+
    -+                      #include <zstd.h>
    -+
    -+                      int main(void) {
    -+                          printf(\"zstd version: %u\n\", ZSTD_versionNumber());
    -+                          return 0;
    -+                      }"
    -+    . auto/feature
    -+
    -+    if [ $nxt_found = yes ]; then
    -+        NXT_HAVE_ZSTD=YES
    -+        echo " + zstd version: $(pkgconf --modversion libzstd)"
    -+    fi
    -+fi
    -+
    -+
    -+if [ $NXT_BROTLI = YES ]; then
    -+    NXT_BROTLI_CFLAGS="$(pkgconf --cflags-only-I libbrotlienc 2>/dev/null || echo "")"
    -+    NXT_BROTLI_LIBS="$(pkgconf --libs libbrotlienc 2>/dev/null || echo "-lbrotlienc")"
    -+
    -+    nxt_feature="brotli"
    -+    nxt_feature_name=NXT_HAVE_BROTLI
    -+    nxt_feature_run=no
    -+    nxt_feature_incs=$NXT_BROTLI_CFLAGS
    -+    nxt_feature_libs=$NXT_BROTLI_LIBS
    -+    nxt_feature_test="#include <stdio.h>
    -+
    -+                      #include <brotli/encode.h>
    -+
    -+                      int main(void) {
    -+                          printf(\"brotli version: %d\n\",
    -+                                 BrotliEncoderVersion());
    -+                          return 0;
    -+                      }"
    -+    . auto/feature
    -+
    -+    if [ $nxt_found = yes ]; then
    -+        NXT_HAVE_BROTLI=YES
    -+        echo " + brotli version: $(pkgconf --modversion libbrotlienc)"
    -+    fi
    -+fi
    +    This is the initial step to enabling HTTP compression on both static and
    +    application responses.
     
    - ## auto/help ##
    -@@ auto/help: cat << END
    - 
    -   --openssl            enable OpenSSL library usage
    - 
    -+  --zlib               enable zlib compression
    -+  --zstd               enable zstd compression
    -+  --brotli             enable brotli compression
    -+
    -   --njs                enable njs library usage
    - 
    -   --debug              enable debug logging
    +    This code itself doesn't do any actual compression, that will come in
    +    subsequent commits. It just contains the core functions for initialising
    +    structures that describe the available compressors and functions for
    +    checking if compression should be done depending on various criteria.
     
    - ## auto/options ##
    -@@ auto/options: NXT_GNUTLS=NO
    - NXT_CYASSL=NO
    - NXT_POLARSSL=NO
    - 
    -+NXT_ZLIB=NO
    -+NXT_ZSTD=NO
    -+NXT_BROTLI=NO
    -+
    - NXT_NJS=NO
    - 
    - NXT_TEST_BUILD_EPOLL=NO
    -@@ auto/options: do
    -         --cyassl)                        NXT_CYASSL=YES                      ;;
    -         --polarssl)                      NXT_POLARSSL=YES                    ;;
    - 
    -+        --zlib)                          NXT_ZLIB=YES                        ;;
    -+        --zstd)                          NXT_ZSTD=YES                        ;;
    -+        --brotli)                        NXT_BROTLI=YES                      ;;
    -+
    -         --njs)                           NXT_NJS=YES                         ;;
    - 
    -         --test-build-epoll)              NXT_TEST_BUILD_EPOLL=YES            ;;
    -
    - ## auto/sources ##
    -@@ auto/sources: NXT_LIB_SRCS=" \
    -     src/nxt_http_websocket.c \
    -     src/nxt_h1proto_websocket.c \
    -     src/nxt_fs.c \
    -+    src/nxt_http_compression.c \
    - "
    - 
    - 
    -@@ auto/sources: if [ $NXT_POLARSSL = YES ]; then
    - fi
    - 
    - 
    -+if [ "$NXT_HAVE_ZLIB" = "YES" ]; then
    -+    NXT_LIB_SRCS="$NXT_LIB_SRCS src/nxt_zlib.c"
    -+fi
    -+
    -+
    -+if [ "$NXT_HAVE_ZSTD" = "YES" ]; then
    -+    NXT_LIB_SRCS="$NXT_LIB_SRCS src/nxt_zstd.c"
    -+fi
    -+
    -+
    -+if [ "$NXT_HAVE_BROTLI" = "YES" ]; then
    -+    NXT_LIB_SRCS="$NXT_LIB_SRCS src/nxt_brotli.c"
    -+fi
    -+
    -+
    - if [ "$NXT_REGEX" = "YES" ]; then
    -     if [ "$NXT_HAVE_PCRE2" = "YES" ]; then
    -         NXT_LIB_SRCS="$NXT_LIB_SRCS $NXT_LIB_PCRE2_SRCS"
    -
    - ## auto/summary ##
    -@@ auto/summary: Unit configuration summary:
    -   IPv6 support: .............. $NXT_INET6
    -   Unix domain sockets support: $NXT_UNIX_DOMAIN
    -   TLS support: ............... $NXT_OPENSSL
    -+  zlib support: .............. $NXT_ZLIB
    -+  zstd support: .............. $NXT_ZSTD
    -+  brotli support: ............ $NXT_BROTLI
    -   Regex support: ............. $NXT_REGEX
    -   njs support: ............... $NXT_NJS
    - 
    -
    - ## configure ##
    -@@ configure: NXT_LIBRT=
    - . auto/unix
    - . auto/os/conf
    - . auto/ssltls
    -+. auto/compression
    - 
    - if [ $NXT_REGEX = YES ]; then
    -     . auto/pcre
    -@@ configure: END
    - 
    - NXT_LIB_AUX_CFLAGS="$NXT_OPENSSL_CFLAGS $NXT_GNUTLS_CFLAGS \\
    -                     $NXT_CYASSL_CFLAGS $NXT_POLARSSL_CFLAGS \\
    --                    $NXT_PCRE_CFLAGS"
    -+                    $NXT_PCRE_CFLAGS $NXT_ZLIB_CFLAGS $NXT_ZSTD_CFLAGS \\
    -+                    $NXT_BROTLI_CFLAGS"
    - 
    - NXT_LIB_AUX_LIBS="$NXT_OPENSSL_LIBS $NXT_GNUTLS_LIBS \\
    -                     $NXT_CYASSL_LIBS $NXT_POLARSSL_LIBS \\
    --                    $NXT_PCRE_LIB"
    -+                    $NXT_PCRE_LIB $NXT_ZLIB_LIBS $NXT_ZSTD_LIBS \\
    -+                    $NXT_BROTLI_LIBS"
    - 
    - if [ $NXT_NJS != NO ]; then
    -     . auto/njs
    -
    - ## src/nxt_brotli.c (new) ##
    -@@
    -+/*
    -+ *
    -+ */
    -+
    -+/* XXX Remove */
    -+#define _GNU_SOURCE
    -+#include <unistd.h>
    -+
    -+
    -+#include <stddef.h>
    -+#include <stdint.h>
    -+#include <stdbool.h>
    -+
    -+#include <brotli/encode.h>
    -+
    -+#include <nxt_http_compression.h>
    -+
    -+static void nxt_brotli_free(const nxt_http_comp_compressor_ctx_t *ctx)
    -+{
    -+    BrotliEncoderState *brotli = ctx->brotli_ctx;
    -+
    -+    BrotliEncoderDestroyInstance(brotli);
    -+}
    -+
    -+static void nxt_brotli_init(nxt_http_comp_compressor_ctx_t *ctx)
    -+{
    -+    BrotliEncoderState **brotli = &ctx->brotli_ctx;
    -+
    -+    *brotli = BrotliEncoderCreateInstance(NULL, NULL, NULL);
    -+    BrotliEncoderSetParameter(*brotli, BROTLI_PARAM_QUALITY, ctx->level);
    -+
    -+    printf("%7d %s: brotli compression level [%d]\n", gettid(), __func__,
    -+           ctx->level);
    -+}
    -+
    -+static size_t nxt_brotli_bound(const nxt_http_comp_compressor_ctx_t *ctx,
    -+                               size_t in_len)
    -+{
    -+    return BrotliEncoderMaxCompressedSize(in_len);
    -+}
    -+
    -+static ssize_t nxt_brotli_compress(const nxt_http_comp_compressor_ctx_t *ctx,
    -+                                   const uint8_t *in_buf, size_t in_len,
    -+                                   uint8_t *out_buf, size_t out_len, bool last)
    -+{
    -+    bool ok;
    -+    size_t out_bytes;
    -+    uint8_t *outp;
    -+    BrotliEncoderState *brotli = ctx->brotli_ctx;
    -+
    -+    printf("%7d %s: last/%s\n", gettid(), __func__, last ? "true" : "false");
    -+    printf("%7d %s: in_len [%lu] out_len [%lu]\n", gettid(),  __func__,
    -+           in_len, out_len);
    -+
    -+    outp = out_buf;
    -+
    -+    ok = BrotliEncoderCompressStream(brotli, BROTLI_OPERATION_PROCESS,
    -+                                     &in_len, &in_buf, &out_bytes, &outp,
    -+                                     NULL);
    -+
    -+    ok = BrotliEncoderCompressStream(brotli, BROTLI_OPERATION_FLUSH,
    -+                                     &in_len, &in_buf, &out_bytes, &outp,
    -+                                     NULL);
    -+
    -+    printf("%7d %s: in_len [%lu] out_len [%lu] out_bytes [%lu]\n", gettid(),
    -+           __func__, in_len, out_len, out_bytes);
    -+    if (last) {
    -+        ok = BrotliEncoderCompressStream(brotli, BROTLI_OPERATION_FINISH,
    -+                                         &in_len, &in_buf, &out_bytes, &outp,
    -+                                         NULL);
    -+        nxt_brotli_free(ctx);
    -+    }
    -+
    -+    printf("%7d %s: in_len [%lu] out_len [%lu] out_bytes [%lu]\n", gettid(),
    -+           __func__, in_len, out_len, out_bytes);
    -+    printf("%7d %s: buf [%p] outp [%p]\n", gettid(), __func__, out_buf, outp);
    -+
    -+    return out_len - out_bytes;
    -+}
    -+
    -+const nxt_http_comp_operations_t  nxt_comp_brotli_ops = {
    -+    .init               = nxt_brotli_init,
    -+    .bound              = nxt_brotli_bound,
    -+    .deflate            = nxt_brotli_compress,
    -+    .free_ctx           = nxt_brotli_free,
    -+};
    -
    - ## src/nxt_conf_validation.c ##
    -@@
    - #include <nxt_http.h>
    - #include <nxt_sockaddr.h>
    - #include <nxt_http_route_addr.h>
    -+#include <nxt_http_compression.h>
    - #include <nxt_regex.h>
    - 
    - 
    -@@ src/nxt_conf_validation.c: static nxt_int_t nxt_conf_vldt_threads(nxt_conf_validation_t *vldt,
    -     nxt_conf_value_t *value, void *data);
    - static nxt_int_t nxt_conf_vldt_thread_stack_size(nxt_conf_validation_t *vldt,
    -     nxt_conf_value_t *value, void *data);
    -+static nxt_int_t nxt_conf_vldt_compressors(nxt_conf_validation_t *vldt,
    -+    nxt_conf_value_t *value, void *data);
    -+static nxt_int_t nxt_conf_vldt_compression(nxt_conf_validation_t *vldt,
    -+    nxt_conf_value_t *value);
    -+static nxt_int_t nxt_conf_vldt_compression_encoding(nxt_conf_validation_t *vldt,
    -+    nxt_conf_value_t *value, void *data);
    -+static nxt_int_t nxt_conf_vldt_compression_level(nxt_conf_validation_t *vldt,
    -+    nxt_conf_value_t *value, void *data);
    - static nxt_int_t nxt_conf_vldt_routes(nxt_conf_validation_t *vldt,
    -     nxt_conf_value_t *value, void *data);
    - static nxt_int_t nxt_conf_vldt_routes_member(nxt_conf_validation_t *vldt,
    -@@ src/nxt_conf_validation.c: static nxt_conf_vldt_object_t  nxt_conf_vldt_setting_members[];
    - static nxt_conf_vldt_object_t  nxt_conf_vldt_http_members[];
    - static nxt_conf_vldt_object_t  nxt_conf_vldt_websocket_members[];
    - static nxt_conf_vldt_object_t  nxt_conf_vldt_static_members[];
    -+static nxt_conf_vldt_object_t  nxt_conf_vldt_compression_members[];
    -+static nxt_conf_vldt_object_t  nxt_conf_vldt_compressor_members[];
    - static nxt_conf_vldt_object_t  nxt_conf_vldt_forwarded_members[];
    - static nxt_conf_vldt_object_t  nxt_conf_vldt_client_ip_members[];
    - #if (NXT_TLS)
    -@@ src/nxt_conf_validation.c: static nxt_conf_vldt_object_t  nxt_conf_vldt_http_members[] = {
    -     }, {
    -         .name       = nxt_string("chunked_transform"),
    -         .type       = NXT_CONF_VLDT_BOOLEAN,
    -+    }, {
    -+        .name       = nxt_string("compression"),
    -+        .type       = NXT_CONF_VLDT_OBJECT,
    -+        .validator  = nxt_conf_vldt_object,
    -+        .u.members  = nxt_conf_vldt_compression_members,
    -     },
    - 
    -     NXT_CONF_VLDT_END
    -@@ src/nxt_conf_validation.c: static nxt_conf_vldt_object_t  nxt_conf_vldt_static_members[] = {
    - };
    - 
    - 
    -+static nxt_conf_vldt_object_t  nxt_conf_vldt_compression_members[] = {
    -+    {
    -+        .name       = nxt_string("types"),
    -+        .type       = NXT_CONF_VLDT_STRING | NXT_CONF_VLDT_ARRAY,
    -+        .validator  = nxt_conf_vldt_match_patterns,
    -+    }, {
    -+        .name       = nxt_string("compressors"),
    -+        .type       = NXT_CONF_VLDT_OBJECT | NXT_CONF_VLDT_ARRAY,
    -+        .validator  = nxt_conf_vldt_compressors,
    -+    },
    -+
    -+    NXT_CONF_VLDT_END
    -+};
    -+
    -+
    -+static nxt_conf_vldt_object_t  nxt_conf_vldt_compressor_members[] = {
    -+    {
    -+        .name       = nxt_string("encoding"),
    -+        .type       = NXT_CONF_VLDT_STRING,
    -+        .flags      = NXT_CONF_VLDT_REQUIRED,
    -+        .validator  = nxt_conf_vldt_compression_encoding,
    -+    }, {
    -+        .name       = nxt_string("level"),
    -+        .type       = NXT_CONF_VLDT_INTEGER,
    -+        .validator  = nxt_conf_vldt_compression_level,
    -+    }, {
    -+        .name       = nxt_string("min_length"),
    -+        .type       = NXT_CONF_VLDT_INTEGER,
    -+    },
    -+
    -+    NXT_CONF_VLDT_END
    -+};
    -+
    -+
    - static nxt_conf_vldt_object_t  nxt_conf_vldt_listener_members[] = {
    -     {
    -         .name       = nxt_string("pass"),
    -@@ src/nxt_conf_validation.c: nxt_conf_vldt_thread_stack_size(nxt_conf_validation_t *vldt,
    - }
    - 
    - 
    -+static nxt_int_t
    -+nxt_conf_vldt_compressors(nxt_conf_validation_t *vldt, nxt_conf_value_t *value,
    -+    void *data)
    -+{
    -+    if (nxt_conf_type(value) == NXT_CONF_ARRAY) {
    -+        return nxt_conf_vldt_array_iterator(vldt, value,
    -+                                            &nxt_conf_vldt_compression);
    -+    }
    -+
    -+    /* NXT_CONF_OBJECT */
    -+
    -+    return nxt_conf_vldt_object_iterator(vldt, value,
    -+                                         &nxt_conf_vldt_compressor_members);
    -+}
    -+
    -+
    -+static nxt_int_t
    -+nxt_conf_vldt_compression(nxt_conf_validation_t *vldt, nxt_conf_value_t *value)
    -+{
    -+    if (nxt_conf_type(value) != NXT_CONF_OBJECT) {
    -+        return nxt_conf_vldt_error(vldt,
    -+                                   "The \"compressors\" array must contain "
    -+                                   "only object values.");
    -+    }
    -+
    -+    return nxt_conf_vldt_object(vldt, value, nxt_conf_vldt_compressor_members);
    -+}
    -+
    -+
    -+static nxt_int_t
    -+nxt_conf_vldt_compression_encoding(nxt_conf_validation_t *vldt,
    -+    nxt_conf_value_t *value, void *data)
    -+{
    -+    nxt_str_t  token;
    -+
    -+    nxt_conf_get_string(value, &token);
    -+
    -+    if (nxt_http_comp_compressor_is_valid(&token)) {
    -+        return NXT_OK;
    -+    }
    -+
    -+    return nxt_conf_vldt_error(vldt, "\"%V\" is not a supported compressor.",
    -+                               &token);
    -+}
    -+
    -+
    -+static nxt_int_t
    -+nxt_conf_vldt_compression_level(nxt_conf_validation_t *vldt,
    -+    nxt_conf_value_t *value, void *data)
    -+{
    -+    /* XXX Fill me in */
    -+
    -+    return NXT_OK;
    -+}
    -+
    -+
    - static nxt_int_t
    - nxt_conf_vldt_routes(nxt_conf_validation_t *vldt, nxt_conf_value_t *value,
    -     void *data)
    -
    - ## src/nxt_http.h ##
    -@@ src/nxt_http.h: typedef struct {
    -     nxt_http_field_t                *content_type;
    -     nxt_http_field_t                *content_length;
    -     nxt_off_t                       content_length_n;
    -+    const nxt_str_t                 *mime_type;
    - } nxt_http_response_t;
    - 
    - 
    +    Signed-off-by: Andrew Clayton <[email protected]>
     
      ## src/nxt_http_compression.c (new) ##
     @@
    @@ src/nxt_http_compression.c (new)
     +#include <nxt_conf.h>
     +#include <nxt_http_compression.h>
     +
    ++
     +#define NXT_COMP_LEVEL_UNSET               INT8_MIN
     +
    ++
     +typedef enum nxt_http_comp_scheme_e        nxt_http_comp_scheme_t;
     +typedef struct nxt_http_comp_type_s        nxt_http_comp_type_t;
     +typedef struct nxt_http_comp_opts_s        nxt_http_comp_opts_t;
    @@ src/nxt_http_compression.c (new)
     +
     +enum nxt_http_comp_scheme_e {
     +    NXT_HTTP_COMP_SCHEME_IDENTITY = 0,
    -+#if NXT_HAVE_ZLIB
    -+    NXT_HTTP_COMP_SCHEME_DEFLATE,
    -+    NXT_HTTP_COMP_SCHEME_GZIP,
    -+#endif
    -+#if NXT_HAVE_ZSTD
    -+    NXT_HTTP_COMP_SCHEME_ZSTD,
    -+#endif
    -+#if NXT_HAVE_BROTLI
    -+    NXT_HTTP_COMP_SCHEME_BROTLI,
    -+#endif
     +
     +    /* keep last */
     +    NXT_HTTP_COMP_SCHEME_UNKNOWN
    @@ src/nxt_http_compression.c (new)
     +    nxt_http_comp_compressor_ctx_t ctx;
     +};
     +
    ++
     +static nxt_tstr_t                  *accept_encoding_query;
     +static nxt_http_route_rule_t       *mime_types_rule;
     +static nxt_http_comp_compressor_t  *enabled_compressors;
    @@ src/nxt_http_compression.c (new)
     +        .token      = nxt_string("identity"),
     +        .scheme     = NXT_HTTP_COMP_SCHEME_IDENTITY,
     +    },
    -+#if NXT_HAVE_ZLIB
    -+    {
    -+        .token      = nxt_string("deflate"),
    -+        .scheme     = NXT_HTTP_COMP_SCHEME_DEFLATE,
    -+        .def_compr  = NXT_HTTP_COMP_ZLIB_DEFAULT_LEVEL,
    -+        .cops       = &nxt_comp_deflate_ops,
    -+    }, {
    -+        .token      = nxt_string("gzip"),
    -+        .scheme     = NXT_HTTP_COMP_SCHEME_GZIP,
    -+        .def_compr  = NXT_HTTP_COMP_ZLIB_DEFAULT_LEVEL,
    -+        .cops       = &nxt_comp_gzip_ops,
    -+    },
    -+#endif
    -+#if NXT_HAVE_ZSTD
    -+    {
    -+        .token      = nxt_string("zstd"),
    -+        .scheme     = NXT_HTTP_COMP_SCHEME_ZSTD,
    -+        .def_compr  = NXT_HTTP_COMP_ZSTD_DEFAULT_LEVEL,
    -+        .cops       = &nxt_comp_zstd_ops,
    -+    },
    -+#endif
    -+#if NXT_HAVE_BROTLI
    -+    {
    -+        .token      = nxt_string("br"),
    -+        .scheme     = NXT_HTTP_COMP_SCHEME_BROTLI,
    -+        .def_compr  = NXT_HTTP_COMP_BROTLI_DEFAULT_LEVEL,
    -+        .cops       = &nxt_comp_brotli_ops,
    -+    },
    -+#endif
     +};
     +
    ++
     +static void print_compressor(const nxt_http_comp_compressor_t *c)
     +{
     +    printf("token    : %s\n", c->type->token.start);
    @@ src/nxt_http_compression.c (new)
     +    return compressor_ctx.idx;
     +}
     +
    ++
     +size_t
     +nxt_http_comp_bound(size_t size)
     +{
    ++    nxt_http_comp_ctx_t               *ctx = &compressor_ctx;
     +    nxt_http_comp_compressor_t        *compressor;
     +    const nxt_http_comp_operations_t  *cops;
    -+    const nxt_http_comp_ctx_t *ctx = &compressor_ctx;
     +
     +    compressor = &enabled_compressors[ctx->idx];
     +    cops = compressor->type->cops;
    @@ src/nxt_http_compression.c (new)
     +    return cops->bound(&ctx->ctx, size);
     +}
     +
    ++
     +ssize_t
     +nxt_http_comp_compress(uint8_t *dst, size_t dst_size, const uint8_t *src,
     +                       size_t src_size, bool last)
     +{
    ++    nxt_http_comp_ctx_t               *ctx = &compressor_ctx;
     +    nxt_http_comp_compressor_t        *compressor;
     +    const nxt_http_comp_operations_t  *cops;
    -+    const nxt_http_comp_ctx_t *ctx = &compressor_ctx;
     +
     +    compressor = &enabled_compressors[ctx->idx];
     +    cops = compressor->type->cops;
    @@ src/nxt_http_compression.c (new)
     +    return cops->deflate(&ctx->ctx, src, src_size, dst, dst_size, last);
     +}
     +
    ++
     +static nxt_uint_t
     +nxt_http_comp_compressor_lookup_enabled(const nxt_str_t *token)
     +{
    @@ src/nxt_http_compression.c (new)
     +        return NXT_HTTP_COMP_SCHEME_IDENTITY;
     +    }
     +
    -+    for (size_t i = 0, n = nr_enabled_compressors; i < n; i++) {
    ++    for (nxt_uint_t i = 0, n = nr_enabled_compressors; i < n; i++) {
     +        if (nxt_strstr_eq(token, &enabled_compressors[i].type->token)) {
     +            return i;
     +        }
    @@ src/nxt_http_compression.c (new)
     +static nxt_int_t
     +nxt_http_comp_select_compressor(const nxt_str_t *token)
     +{
    -+    bool identity_allowed = true;
    -+    char *str;
    -+    char *saveptr;
    -+    double weight = 0.0;
    -+    nxt_int_t idx = 0;
    ++    bool       identity_allowed = true;
    ++    char       *str;
    ++    double     weight = 0.0;
    ++    nxt_int_t  idx = 0;
     +
     +    str = strndup((char *)token->start, token->length);
     +
     +    for ( ; ; str = NULL) {
    -+        char *tkn, *qptr;
    -+        double qval = -1.0;
    -+        nxt_uint_t ecidx;
    -+        nxt_str_t enc;
    -+        nxt_http_comp_scheme_t scheme;
    ++        char                    *tkn, *qptr;
    ++        double                  qval = -1.0;
    ++        nxt_str_t               enc;
    ++        nxt_uint_t              ecidx;
    ++        nxt_http_comp_scheme_t  scheme;
     +
    -+        tkn = strtok_r(str, ", ", &saveptr);
    ++        tkn = strsep(&str, ", ");
     +        if (tkn == NULL) {
     +            break;
     +        }
    @@ src/nxt_http_compression.c (new)
     +static nxt_int_t
     +nxt_http_comp_set_header(nxt_http_request_t *r, nxt_uint_t comp_idx)
     +{
    -+    const nxt_str_t *token;
    -+    nxt_http_field_t *f;
    ++    const nxt_str_t   *token;
    ++    nxt_http_field_t  *f;
     +
     +    static const nxt_str_t  content_encoding_str =
     +                                    nxt_string("Content-Encoding");
    @@ src/nxt_http_compression.c (new)
     +
     +    token = &enabled_compressors[comp_idx].type->token;
     +
    -+    *f = (nxt_http_field_t){ };
    ++    *f = (nxt_http_field_t){};
     +
     +    f->name = content_encoding_str.start;
     +    f->name_length = content_encoding_str.length;
    @@ src/nxt_http_compression.c (new)
     +static bool
     +nxt_http_comp_is_resp_content_encoded(const nxt_http_request_t *r)
     +{
    -+    nxt_http_field_t *f;
    ++    nxt_http_field_t  *f;
     +
     +    printf("%s: \n", __func__);
     +
    @@ src/nxt_http_compression.c (new)
     +nxt_int_t
     +nxt_http_comp_check_compression(nxt_task_t *task, nxt_http_request_t *r)
     +{
    -+    int8_t level;
    -+    nxt_str_t mime_type = { };
    -+    nxt_int_t ret, idx;
    -+    nxt_off_t min_len;
    -+    nxt_str_t accept_encoding;
    -+    nxt_router_conf_t *rtcf;
    -+    nxt_http_comp_compressor_t *compressor;
    ++    int8_t                      level;
    ++    nxt_int_t                   ret, idx;
    ++    nxt_off_t                   min_len;
    ++    nxt_str_t                   accept_encoding, mime_type = {};
    ++    nxt_router_conf_t           *rtcf;
    ++    nxt_http_comp_compressor_t  *compressor;
     +
     +    printf("%s: \n", __func__);
     +
    @@ src/nxt_http_compression.c (new)
     +    return NXT_OK;
     +}
     +
    ++
     +static nxt_uint_t
     +nxt_http_comp_compressor_token2idx(const nxt_str_t *token)
     +{
    @@ src/nxt_http_compression.c (new)
     +    return NXT_HTTP_COMP_SCHEME_UNKNOWN;
     +}
     +
    ++
     +bool
     +nxt_http_comp_compressor_is_valid(const nxt_str_t *token)
     +{
    @@ src/nxt_http_compression.c (new)
     +    return false;
     +}
     +
    ++
     +static nxt_int_t
     +nxt_http_comp_set_compressor(nxt_router_conf_t *rtcf,
     +                             const nxt_conf_value_t *comp, nxt_uint_t index)
     +{
    -+    nxt_int_t               ret;
    -+    nxt_str_t               token;
    -+    nxt_uint_t              cidx;
    -+    nxt_conf_value_t        *obj;
    ++    nxt_int_t         ret;
    ++    nxt_str_t         token;
    ++    nxt_uint_t        cidx;
    ++    nxt_conf_value_t  *obj;
     +
     +    static const nxt_str_t  token_str = nxt_string("encoding");
     +
    @@ src/nxt_http_compression.c (new)
     +    return NXT_OK;
     +}
     +
    ++
     +nxt_int_t
     +nxt_http_comp_compression_init(nxt_task_t *task, nxt_router_conf_t *rtcf,
     +                               const nxt_conf_value_t *comp_conf)
    @@ src/nxt_http_compression.h (new)
     +#include <stdint.h>
     +#include <stdbool.h>
     +
    -+#if NXT_HAVE_ZLIB
    -+#include <zlib.h>
    -+#endif
    -+
    -+#if NXT_HAVE_ZSTD
    -+#include <zstd.h>
    -+#endif
    -+
    -+#if NXT_HAVE_BROTLI
    -+#include <brotli/encode.h>
    -+#endif
    -+
     +#include <nxt_main.h>
     +#include <nxt_router.h>
     +#include <nxt_string.h>
     +#include <nxt_conf.h>
     +
    -+#if NXT_HAVE_ZLIB
    -+#define NXT_HTTP_COMP_ZLIB_DEFAULT_LEVEL       Z_DEFAULT_COMPRESSION
    -+#endif
    -+#if NXT_HAVE_ZSTD
    -+#define NXT_HTTP_COMP_ZSTD_DEFAULT_LEVEL       ZSTD_CLEVEL_DEFAULT
    -+#endif
    -+#if NXT_HAVE_BROTLI
    -+#define NXT_HTTP_COMP_BROTLI_DEFAULT_LEVEL     BROTLI_DEFAULT_QUALITY
    -+#endif
     +
     +typedef struct nxt_http_comp_compressor_ctx_s  nxt_http_comp_compressor_ctx_t;
     +typedef struct nxt_http_comp_operations_s      nxt_http_comp_operations_t;
    @@ src/nxt_http_compression.h (new)
     +    int8_t level;
     +
     +    union {
    -+#if NXT_HAVE_ZLIB
    -+        z_stream zlib_ctx;
    -+#endif
    -+#if NXT_HAVE_ZSTD
    -+        ZSTD_CStream *zstd_ctx;
    -+#endif
    -+#if NXT_HAVE_BROTLI
    -+        BrotliEncoderState *brotli_ctx;
    -+#endif
     +    };
     +};
     +
    @@ src/nxt_http_compression.h (new)
     +    void     (*init)(nxt_http_comp_compressor_ctx_t *ctx);
     +    size_t   (*bound)(const nxt_http_comp_compressor_ctx_t *ctx,
     +                      size_t in_len);
    -+    ssize_t  (*deflate)(const nxt_http_comp_compressor_ctx_t *ctx,
    ++    ssize_t  (*deflate)(nxt_http_comp_compressor_ctx_t *ctx,
     +                        const uint8_t *in_buf, size_t in_len,
     +                        uint8_t *out_buf, size_t out_len, bool last);
     +    void     (*free_ctx)(const nxt_http_comp_compressor_ctx_t *ctx);
     +};
     +
    -+#if NXT_HAVE_ZLIB
    -+extern const nxt_http_comp_operations_t  nxt_comp_deflate_ops;
    -+extern const nxt_http_comp_operations_t  nxt_comp_gzip_ops;
    -+#endif
    -+
    -+#if NXT_HAVE_ZSTD
    -+extern const nxt_http_comp_operations_t  nxt_comp_zstd_ops;
    -+#endif
    -+
    -+#if NXT_HAVE_BROTLI
    -+extern const nxt_http_comp_operations_t  nxt_comp_brotli_ops;
    -+#endif
     +
     +extern bool nxt_http_comp_wants_compression(void);
     +extern size_t nxt_http_comp_bound(size_t size);
    @@ src/nxt_http_compression.h (new)
     +    nxt_router_conf_t *rtcf, const nxt_conf_value_t *comp_conf);
     +
     +#endif  /* _NXT_COMPRESSION_H_INCLUDED_ */
    -
    - ## src/nxt_http_request.c ##
    -@@
    - 
    - #include <nxt_router.h>
    - #include <nxt_http.h>
    -+#include <nxt_http_compression.h>
    - 
    - 
    - static nxt_int_t nxt_http_validate_host(nxt_str_t *host, nxt_mp_t *mp);
    -@@ src/nxt_http_request.c: nxt_http_request_start(nxt_task_t *task, void *obj, void *data)
    -     nxt_socket_conf_t   *skcf;
    -     nxt_http_request_t  *r;
    - 
    -+    printf("%s: \n", __func__);
    -+
    -     r = obj;
    - 
    -     r->state = &nxt_http_request_body_state;
    -@@ src/nxt_http_request.c: nxt_http_request_header_send(nxt_task_t *task, nxt_http_request_t *r,
    -     nxt_http_field_t   *server, *date, *content_length;
    -     nxt_socket_conf_t  *skcf;
    - 
    -+    printf("%s: \n", __func__);
    -+
    -     ret = nxt_http_set_headers(r);
    -     if (nxt_slow_path(ret != NXT_OK)) {
    -         goto fail;
    -@@ src/nxt_http_request.c: nxt_http_request_ws_frame_start(nxt_task_t *task, nxt_http_request_t *r,
    - void
    - nxt_http_request_send(nxt_task_t *task, nxt_http_request_t *r, nxt_buf_t *out)
    - {
    -+    printf("%s: sending [%lu] bytes\n", __func__, nxt_buf_mem_size(&out->mem));
    -+
    -+//    nxt_http_comp_compress_response(out);
    -+
    -     if (nxt_fast_path(r->proto.any != NULL)) {
    -         nxt_http_proto[r->protocol].send(task, r, out);
    -     }
    -
    - ## src/nxt_http_route.c ##
    -@@ src/nxt_http_route.c: nxt_http_action_init(nxt_task_t *task, nxt_router_temp_conf_t *tmcf,
    -     nxt_router_conf_t       *rtcf;
    -     nxt_http_action_conf_t  acf;
    - 
    -+    printf("%s: \n", __func__);
    -+
    -     nxt_memzero(&acf, sizeof(acf));
    - 
    -     ret = nxt_conf_map_object(tmcf->mem_pool, cv, nxt_http_route_action_conf,
    -
    - ## src/nxt_http_static.c ##
    -@@
    - 
    - #include <nxt_router.h>
    - #include <nxt_http.h>
    -+#include <nxt_http_compression.h>
    - 
    - 
    - typedef struct {
    -@@ src/nxt_http_static.c: nxt_http_static_send(nxt_task_t *task, nxt_http_request_t *r,
    -     nxt_work_handler_t      body_handler;
    -     nxt_http_static_conf_t  *conf;
    - 
    -+    printf("%s: \n", __func__);
    -+
    -     action = ctx->action;
    -     conf = action->u.conf;
    -     rtcf = r->conf->socket_conf->router_conf;
    -@@ src/nxt_http_static.c: nxt_http_static_send(nxt_task_t *task, nxt_http_request_t *r,
    -             field->value_length = mtype->length;
    -         }
    - 
    -+        r->resp.mime_type = mtype;
    -+
    -         if (ctx->need_body && nxt_file_size(&fi) > 0) {
    -+            bool  compress;
    -+
    -+            ret = nxt_http_comp_check_compression(task, r);
    -+            if (nxt_slow_path(ret != NXT_OK)) {
    -+                goto fail;
    -+            }
    -+
    -+            compress = nxt_http_comp_wants_compression();
    -+            if (compress) {
    -+                char           tmp_path[NXT_MAX_PATH_LEN];
    -+                size_t         in_size, out_size, out_total = 0, rest;
    -+                u_char         *p;
    -+                uint8_t        *in, *out;
    -+                nxt_file_t     tfile;
    -+                nxt_runtime_t  *rt = task->thread->runtime;
    -+
    -+                static const char  *template = "unit-compr-XXXXXX";
    -+
    -+                if (nxt_slow_path(strlen(rt->tmp) + 1 + strlen(template) + 1
    -+                                  > NXT_MAX_PATH_LEN))
    -+                {
    -+                    goto fail;
    -+                }
    -+
    -+                p = nxt_cpymem(tmp_path, rt->tmp, strlen(rt->tmp));
    -+                *p++ = '/';
    -+                p = nxt_cpymem(tmp_path, template, strlen(template));
    -+                *p = '\0';
    -+
    -+                tfile.fd = mkstemp(tmp_path);
    -+                if (nxt_slow_path(tfile.fd == -1)) {
    -+                    nxt_alert(task, "mkstemp(%s) failed %E", tmp_path,
    -+                              nxt_errno);
    -+                    goto fail;
    -+                }
    -+
    -+                in_size = nxt_file_size(&fi);
    -+                out_size = nxt_http_comp_bound(in_size);
    -+
    -+                ret = ftruncate(tfile.fd, out_size);
    -+                if (nxt_slow_path(ret == -1)) {
    -+                    nxt_alert(task, "ftruncate(%d<%s>, %uz) failed %E",
    -+                              tfile.fd, tmp_path, out_size, nxt_errno);
    -+                    nxt_file_close(task, &tfile);
    -+                    goto fail;
    -+                }
    -+
    -+                in = nxt_mem_mmap(NULL, in_size, PROT_READ, MAP_SHARED, f->fd,
    -+                                  0);
    -+                if (nxt_slow_path(in == MAP_FAILED)) {
    -+                    nxt_file_close(task, &tfile);
    -+                    goto fail;
    -+                }
    -+
    -+                out = nxt_mem_mmap(NULL, out_size, PROT_READ|PROT_WRITE,
    -+                                   MAP_SHARED, tfile.fd, 0);
    -+                if (nxt_slow_path(out == MAP_FAILED)) {
    -+                    nxt_mem_munmap(in, in_size);
    -+                    nxt_file_close(task, &tfile);
    -+                    goto fail;
    -+                }
    -+
    -+                rest = in_size;
    -+
    -+                do {
    -+                    bool     last;
    -+                    size_t   n;
    -+                    ssize_t  cbytes;
    -+
    -+                    n = rest > NXT_HTTP_STATIC_BUF_SIZE
    -+                                        ? NXT_HTTP_STATIC_BUF_SIZE : rest;
    -+
    -+                    last = n == rest;
    -+
    -+                    printf("%s: out_off [%ld] in_off [%ld] last [%s]\n",
    -+                           __func__, out_total, in_size - rest,
    -+                           last ? "true" : "false");
    -+
    -+                    cbytes = nxt_http_comp_compress(out + out_total,
    -+                                                    out_size - out_total,
    -+                                                    in + in_size - rest, n,
    -+                                                    last);
    -+                    printf("%s: cbytes [%ld]\n", __func__, cbytes);
    -+
    -+                    out_total += cbytes;
    -+                    rest -= n;
    -+                } while (rest > 0);
    -+
    -+                nxt_mem_munmap(in, in_size);
    -+                msync(out, out_size, MS_ASYNC);
    -+                nxt_mem_munmap(out, out_size);
    -+
    -+                ret = ftruncate(tfile.fd, out_total);
    -+                if (nxt_slow_path(ret == -1)) {
    -+                    nxt_alert(task, "ftruncate(%d<%s>, %uz) failed %E",
    -+                              tfile.fd, tmp_path, out_total, nxt_errno);
    -+                    nxt_file_close(task, &tfile);
    -+                    goto fail;
    -+                }
    -+
    -+                nxt_file_close(task, f);
    -+
    -+                *f = tfile;
    -+
    -+                ret = nxt_file_info(f, &fi);
    -+                if (nxt_slow_path(ret != NXT_OK)) {
    -+                    goto fail;
    -+                }
    -+
    -+                r->resp.content_length_n = out_total;
    -+            }
    -+
    -             fb = nxt_mp_zget(r->mem_pool, NXT_BUF_FILE_SIZE);
    -             if (nxt_slow_path(fb == NULL)) {
    -                 goto fail;
    -@@ src/nxt_http_static.c: nxt_http_static_body_handler(nxt_task_t *task, void *obj, void *data)
    -     nxt_work_queue_t    *wq;
    -     nxt_http_request_t  *r;
    - 
    -+    printf("%s: \n", __func__);
    -+
    -     r = obj;
    -     fb = r->out;
    - 
    -@@ src/nxt_http_static.c: nxt_http_static_buf_completion(nxt_task_t *task, void *obj, void *data)
    -     nxt_off_t           rest;
    -     nxt_http_request_t  *r;
    - 
    -+    printf("%s: \n", __func__);
    -+
    -     b = obj;
    -     r = data;
    - 
    -
    - ## src/nxt_router.c ##
    -@@
    - #include <nxt_router_request.h>
    - #include <nxt_app_queue.h>
    - #include <nxt_port_queue.h>
    -+#include <nxt_http_compression.h>
    - 
    - #define NXT_SHARED_PORT_ID  0xFFFFu
    - 
    -@@ src/nxt_router.c: nxt_router_conf_create(nxt_task_t *task, nxt_router_temp_conf_t *tmcf,
    -     static const nxt_str_t  static_path = nxt_string("/settings/http/static");
    -     static const nxt_str_t  websocket_path =
    -                                 nxt_string("/settings/http/websocket");
    -+    static const nxt_str_t  compression_path =
    -+                                nxt_string("/settings/http/compression");
    -     static const nxt_str_t  forwarded_path = nxt_string("/forwarded");
    -     static const nxt_str_t  client_ip_path = nxt_string("/client_ip");
    - 
    -@@ src/nxt_router.c: nxt_router_conf_create(nxt_task_t *task, nxt_router_temp_conf_t *tmcf,
    -             nxt_str_null(&skcf->body_temp_path);
    - 
    -             if (http != NULL) {
    -+                nxt_conf_value_t  *comp;
    -+
    -                 ret = nxt_conf_map_object(mp, http, nxt_router_http_conf,
    -                                           nxt_nitems(nxt_router_http_conf),
    -                                           skcf);
    -@@ src/nxt_router.c: nxt_router_conf_create(nxt_task_t *task, nxt_router_temp_conf_t *tmcf,
    -                     nxt_alert(task, "http map error");
    -                     goto fail;
    -                 }
    -+
    -+                comp = nxt_conf_get_path(root, &compression_path);
    -+                if (comp != NULL) {
    -+                    nxt_http_comp_compression_init(task, rtcf, comp);
    -+                }
    -             }
    - 
    -             if (websocket != NULL) {
    -
    - ## src/nxt_zlib.c (new) ##
    -@@
    -+/*
    -+ *
    -+ */
    -+
    -+#include <stddef.h>
    -+#include <stdint.h>
    -+#include <stdbool.h>
    -+
    -+#include <zlib.h>
    -+
    -+#include <nxt_http_compression.h>
    -+
    -+static void nxt_zlib_gzip_init(nxt_http_comp_compressor_ctx_t *ctx)
    -+{
    -+    int ret;
    -+    z_stream *z = &ctx->zlib_ctx;
    -+
    -+    *z = (z_stream){ };
    -+
    -+    ret = deflateInit2(z, ctx->level, Z_DEFLATED, 9 + 16, 8,
    -+                       Z_DEFAULT_STRATEGY);
    -+}
    -+
    -+static void nxt_zlib_deflate_init(nxt_http_comp_compressor_ctx_t *ctx)
    -+{
    -+    int ret;
    -+    z_stream *z = &ctx->zlib_ctx;
    -+
    -+    *z = (z_stream){ };
    -+
    -+    ret = deflateInit2(z, ctx->level, Z_DEFLATED, 9, 8, Z_DEFAULT_STRATEGY);
    -+}
    -+
    -+static size_t nxt_zlib_bound(const nxt_http_comp_compressor_ctx_t *ctx,
    -+                             size_t in_len)
    -+{
    -+    z_stream *z = (z_stream *)&ctx->zlib_ctx;
    -+
    -+    return deflateBound(z, in_len);
    -+}
    -+
    -+static ssize_t nxt_zlib_deflate(const nxt_http_comp_compressor_ctx_t *ctx,
    -+                                const uint8_t *in_buf, size_t in_len,
    -+                                uint8_t *out_buf, size_t out_len, bool last)
    -+{
    -+    int ret;
    -+    z_stream *z = (z_stream *)&ctx->zlib_ctx;
    -+    size_t compressed_bytes = z->total_out;
    -+
    -+    z->avail_in = in_len;
    -+    z->next_in = (z_const Bytef *)in_buf;
    -+
    -+    z->avail_out = out_len;
    -+    z->next_out = out_buf;
    -+
    -+    ret = deflate(z, last ? Z_FINISH : Z_SYNC_FLUSH);
    -+    if (ret == Z_STREAM_ERROR || ret == Z_BUF_ERROR) {
    -+        deflateEnd(z);
    -+        printf("%s: ret = %d\n", __func__, ret);
    -+        return -1;
    -+    }
    -+
    -+    if (last)
    -+        deflateEnd(z);
    -+
    -+    return z->total_out - compressed_bytes;
    -+}
    -+
    -+const nxt_http_comp_operations_t  nxt_comp_deflate_ops = {
    -+    .init               = nxt_zlib_deflate_init,
    -+    .bound              = nxt_zlib_bound,
    -+    .deflate            = nxt_zlib_deflate,
    -+};
    -+
    -+const nxt_http_comp_operations_t  nxt_comp_gzip_ops = {
    -+    .init               = nxt_zlib_gzip_init,
    -+    .bound              = nxt_zlib_bound,
    -+    .deflate            = nxt_zlib_deflate,
    -+};
    -
    - ## src/nxt_zstd.c (new) ##
    -@@
    -+/*
    -+ *
    -+ */
    -+
    -+#include <stddef.h>
    -+#include <stdint.h>
    -+#include <stdbool.h>
    -+
    -+#include <zstd.h>
    -+
    -+#include <nxt_http_compression.h>
    -+
    -+static void nxt_zstd_free(const nxt_http_comp_compressor_ctx_t *ctx)
    -+{
    -+    ZSTD_CStream *zstd = ctx->zstd_ctx;
    -+
    -+    ZSTD_freeCStream(zstd);
    -+}
    -+
    -+static void nxt_zstd_init(nxt_http_comp_compressor_ctx_t *ctx)
    -+{
    -+    ZSTD_CStream **zstd = &ctx->zstd_ctx;
    -+
    -+    *zstd = ZSTD_createCStream();
    -+    ZSTD_initCStream(*zstd, ctx->level);
    -+
    -+    printf("%s: zstd compression level [%d]\n", __func__, ctx->level);
    -+}
    -+
    -+static size_t nxt_zstd_bound(const nxt_http_comp_compressor_ctx_t *ctx,
    -+                             size_t in_len)
    -+{
    -+    return ZSTD_compressBound(in_len);
    -+}
    -+
    -+static ssize_t nxt_zstd_compress(const nxt_http_comp_compressor_ctx_t *ctx,
    -+                                 const uint8_t *in_buf, size_t in_len,
    -+                                 uint8_t *out_buf, size_t out_len, bool last)
    -+{
    -+    size_t ret;
    -+    ZSTD_CStream *zstd = ctx->zstd_ctx;
    -+    ZSTD_inBuffer zinb = { .src = in_buf, .size = in_len };
    -+    ZSTD_outBuffer zoutb = { .dst = out_buf, .size = out_len };
    -+
    -+    printf("%s: in_len [%lu] out_len [%lu] last [%s]\n", __func__,
    -+           in_len, out_len, last ? "true" : "false");
    -+
    -+    ret = ZSTD_compressStream(zstd, &zoutb, &zinb);
    -+
    -+    if (zinb.pos < zinb.size) {
    -+        printf("%s: short by [%d]\n", __func__, zinb.pos < zinb.size);
    -+        ret = ZSTD_flushStream(zstd, &zoutb);
    -+    }
    -+
    -+    if (last) {
    -+        ret = ZSTD_endStream(zstd, &zoutb);
    -+        nxt_zstd_free(ctx);
    -+    }
    -+
    -+    printf("%s: ret [%lu]\n", __func__, ret);
    -+    if (ZSTD_isError(ret)) {
    -+        printf("%s: [%s]\n", __func__, ZSTD_getErrorName(ret));
    -+        return -1;
    -+    }
    -+
    -+    return zoutb.pos;
    -+}
    -+
    -+const nxt_http_comp_operations_t  nxt_comp_zstd_ops = {
    -+    .init               = nxt_zstd_init,
    -+    .bound              = nxt_zstd_bound,
    -+    .deflate            = nxt_zstd_compress,
    -+    .free_ctx           = nxt_zstd_free,
    -+};
 -:  -------- > 30:  421e57c6 http: Add zlib compression support
 -:  -------- > 31:  6d23513b http: Add support for zstd compression
 -:  -------- > 32:  f6c22d7a http: Add support for brotli compression
 -:  -------- > 33:  2f553dae http: Wire up HTTP compression to the build system
 -:  -------- > 34:  0df0bd52 http: Wire up HTTP compression support to the config system
 -:  -------- > 35:  69355125 ** DEBUG DO NOT MERGE **
 -:  -------- > 36:  7671844a http: Compress static responses
 2:  628302a4 ! 37:  2fd2cd6e [WIP] Compress application responses
    @@ Metadata
     Author: Andrew Clayton <[email protected]>
     
      ## Commit message ##
    -    [WIP] Compress application responses
    +    http: Compress application responses
    +
    +    Co-authored-by: Alejandro Colomar <[email protected]>
    +    Signed-off-by: Alejandro Colomar <[email protected]>
    +    Signed-off-by: Andrew Clayton <[email protected]>
     
      ## src/nxt_http_compression.c ##
     @@ src/nxt_http_compression.c: static void print_comp_config(size_t n)
    @@ src/nxt_http_compression.c: static void print_comp_config(size_t n)
      }
      
     +nxt_int_t
    -+nxt_http_comp_compress_response(nxt_http_request_t *r)
    ++nxt_http_comp_compress_app_response(nxt_http_request_t *r)
     +{
    -+    nxt_buf_t *b = r->out;
    -+    size_t in_len;
    -+    size_t buf_len;
    -+    ssize_t cbytes;
    -+    uint8_t *buf;
    -+    bool last = false;
    -+    nxt_http_comp_compressor_t  *compressor;
    ++    bool                              last;
    ++    size_t                            buf_len, in_len;
    ++    ssize_t                           cbytes;
    ++    nxt_buf_t                         *buf, *b = r->out;
    ++    nxt_http_comp_ctx_t               *ctx = &compressor_ctx;
    ++    nxt_http_comp_compressor_t        *compressor;
     +    const nxt_http_comp_operations_t  *cops;
    -+    const nxt_http_comp_ctx_t *ctx = &compressor_ctx;
     +
     +    printf("%s: \n", __func__);
     +
    @@ src/nxt_http_compression.c: static void print_comp_config(size_t n)
     +
     +    in_len = b->mem.free - b->mem.pos;
     +
    -+    last = !b->next || (b->next && b->next->is_last == 1);
    ++    last = !b->next || b->next->is_last == 1;
     +
     +    cops = compressor->type->cops;
     +
     +    buf_len = cops->bound(&ctx->ctx, in_len);
    -+    buf = nxt_malloc(buf_len);
    -+    cbytes = cops->deflate(&ctx->ctx, b->mem.pos, in_len, buf, buf_len, last);
    -+    printf("%s: cbytes = %ld\n", __func__, cbytes);
    -+    /* TODO handle new buffer is larger than original buffer */
    -+    if (cbytes != -1) {
    -+        b->mem.free = nxt_cpymem(b->mem.pos, buf, cbytes);
    ++
    ++    buf = nxt_buf_mem_alloc(r->mem_pool, buf_len, 0);
    ++    if (nxt_slow_path(buf == NULL)) {
    ++        return NXT_ERROR;
     +    }
    -+    nxt_free(buf);
    ++
    ++    nxt_memcpy(buf, b, offsetof(nxt_buf_t, mem));
    ++    buf->data = r->mem_pool;
    ++
    ++    cbytes = cops->deflate(&ctx->ctx, b->mem.pos, in_len, buf->mem.start,
    ++                           buf->mem.end - buf->mem.start, last);
    ++    printf("%s: cbytes = %ld\n", __func__, cbytes);
    ++    if (cbytes == -1) {
    ++        return NXT_ERROR;
    ++    }
    ++
    ++//    if (cbytes != -1) {
    ++//        b->mem.free = nxt_cpymem(b->mem.pos, tmp->mem.start, cbytes);
    ++//    }
    ++    b = buf;
     +
     +    return NXT_OK;
     +}
    @@ src/nxt_http_compression.c: nxt_http_comp_set_header(nxt_http_request_t *r, nxt_
     +         */
     +        nxt_list_each(f, r->resp.fields) {
     +            if (nxt_strcasecmp(f->name,
    -+                               (const u_char *)"Content-Length") != 0)
    ++                               (const u_char *)"Content-Length") == 0)
     +            {
    -+                continue;
    ++                printf("%s: Found (%s: %s), marking as 'skip'\n", __func__,
    ++                       f->name, f->value);
    ++                f->skip = true;
    ++                break;
     +            }
    -+
    -+            printf("%s: Found (%s: %s), marking as 'skip'\n", __func__,
    -+                   f->name, f->value);
    -+            f->skip = true;
    -+            break;
     +        } nxt_list_loop;
     +    }
     +
    @@ src/nxt_http_compression.c: nxt_http_comp_set_header(nxt_http_request_t *r, nxt_
      
     
      ## src/nxt_http_compression.h ##
    -@@ src/nxt_http_compression.h: extern const nxt_http_comp_operations_t  nxt_comp_zstd_ops;
    - extern const nxt_http_comp_operations_t  nxt_comp_brotli_ops;
    +@@ src/nxt_http_compression.h: extern const nxt_http_comp_operations_t  nxt_comp_brotli_ops;
      #endif
      
    -+extern nxt_int_t nxt_http_comp_compress_response(nxt_http_request_t *r);
    + 
    ++extern nxt_int_t nxt_http_comp_compress_app_response(nxt_http_request_t *r);
      extern bool nxt_http_comp_wants_compression(void);
      extern size_t nxt_http_comp_bound(size_t size);
      extern ssize_t nxt_http_comp_compress(uint8_t *dst, size_t dst_size,
    @@ src/nxt_router.c: nxt_router_response_ready_handler(nxt_task_t *task, nxt_port_r
     -        nxt_http_request_send_body(task, r, NULL);
      
     +        /* XXX Do compression here */
    -+        nxt_http_comp_compress_response(r);
    ++        nxt_http_comp_compress_app_response(r);
     +
     +        nxt_http_request_send_body(task, r, NULL);
          } else {
    @@ src/nxt_router.c: nxt_router_response_ready_handler(nxt_task_t *task, nxt_port_r
      
     +        /* XXX Check compression / modify headers here */
     +        ret = nxt_http_comp_check_compression(task, r);
    -+        if (nxt_slow_path(ret != NXT_OK)) {
    ++        if (ret != NXT_OK) {
     +            goto fail;
     +        }
     +

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Gzip/Brotli compressions on NGINX Unit
3 participants