text: 
  /*
   * Copyright (C) Alex Zhang
   */
  
  
  #include <ngx_config.h>
  #include <ngx_core.h>
  #include <ngx_http.h>
  
  #include <zstd.h>
  
  
  #define NGX_HTTP_ZSTD_FILTER_COMPRESS       0
  #define NGX_HTTP_ZSTD_FILTER_FLUSH          1
  #define NGX_HTTP_ZSTD_FILTER_END            2
  
  
  typedef struct {
      ngx_str_t                    dict_file;
  } ngx_http_zstd_main_conf_t;
  
  
  typedef struct {
      ngx_flag_t                   enable;
      ngx_int_t                    level;
      ssize_t                      min_length;
  
      ngx_hash_t                   types;
  
      ngx_bufs_t                   bufs;
  
      ngx_array_t                 *types_keys;
  
      ZSTD_CDict                  *dict;
  } ngx_http_zstd_loc_conf_t;
  
  
  typedef struct {
      ngx_chain_t                 *in;
      ngx_chain_t                 *free;
      ngx_chain_t                 *busy;
      ngx_chain_t                 *out;
      ngx_chain_t                **last_out;
  
      ngx_buf_t                   *in_buf;
      ngx_buf_t                   *out_buf;
      ngx_int_t                    bufs;
  
      ZSTD_inBuffer                buffer_in;
      ZSTD_outBuffer               buffer_out;
  
      ZSTD_CStream                *cstream;
  
      ngx_http_request_t          *request;
  
      size_t                       bytes_in;
      size_t                       bytes_out;
  
      unsigned                     action:2;
      unsigned                     last:1;
      unsigned                     redo:1;
      unsigned                     flush:1;
      unsigned                     done:1;
      unsigned                     nomem:1;
  } ngx_http_zstd_ctx_t;
  
  
  typedef struct {
      ngx_conf_post_handler_pt  post_handler;
  } ngx_http_zstd_comp_level_bounds_t;
  
  
  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_str_t  ngx_http_zstd_ratio = ngx_string("zstd_ratio");
  
  
  static ngx_int_t ngx_http_zstd_header_filter(ngx_http_request_t *r);
  static ngx_int_t ngx_http_zstd_body_filter(ngx_http_request_t *r,
      ngx_chain_t *in);
  static ngx_int_t ngx_http_zstd_filter_add_data(ngx_http_request_t *r,
      ngx_http_zstd_ctx_t *ctx);
  static ngx_int_t ngx_http_zstd_filter_get_buf(ngx_http_request_t *r,
      ngx_http_zstd_ctx_t *ctx);
  static ZSTD_CStream *ngx_http_zstd_filter_create_cstream(ngx_http_request_t *r,
      ngx_http_zstd_ctx_t *ctx);
  static ngx_int_t ngx_http_zstd_filter_compress(ngx_http_request_t *r,
      ngx_http_zstd_ctx_t *ctx);
  static ngx_int_t ngx_http_zstd_accept_encoding(ngx_str_t *ae);
  static ngx_int_t ngx_http_zstd_ok(ngx_http_request_t *r);
  static ngx_int_t ngx_http_zstd_filter_init(ngx_conf_t *cf);
  static void * ngx_http_zstd_create_main_conf(ngx_conf_t *cf);
  static char *ngx_http_zstd_init_main_conf(ngx_conf_t *cf, void *conf);
  static void *ngx_http_zstd_create_loc_conf(ngx_conf_t *cf);
  static char *ngx_http_zstd_merge_loc_conf(ngx_conf_t *cf, void *parent,
      void *child);
  static ngx_int_t ngx_http_zstd_add_variables(ngx_conf_t *cf);
  static ngx_int_t ngx_http_zstd_ratio_variable(ngx_http_request_t *r,
      ngx_http_variable_value_t *vv, uintptr_t data);
  static void * ngx_http_zstd_filter_alloc(void *opaque, size_t size);
  static void ngx_http_zstd_filter_free(void *opaque, void *address);
  static char *ngx_http_zstd_comp_level(ngx_conf_t *cf, void *post, void *data);
  
  
  static ngx_http_zstd_comp_level_bounds_t  ngx_http_zstd_comp_level_bounds = {
      ngx_http_zstd_comp_level
  };
  
  
  static ngx_command_t  ngx_http_zstd_filter_commands[] = {
  
      { ngx_string("zstd"),
        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_zstd_loc_conf_t, enable),
        NULL },
  
      { ngx_string("zstd_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_zstd_loc_conf_t, level),
        &ngx_http_zstd_comp_level_bounds },
  
      { ngx_string("zstd_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_zstd_loc_conf_t, types_keys),
        NULL },
  
      { ngx_string("zstd_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_zstd_loc_conf_t, bufs),
        NULL },
  
      { ngx_string("zstd_min_length"),
        NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_1MORE,
        ngx_conf_set_size_slot,
        NGX_HTTP_LOC_CONF_OFFSET,
        offsetof(ngx_http_zstd_loc_conf_t, min_length),
        NULL },
  
      { ngx_string("zstd_dict_file"),
        NGX_HTTP_MAIN_CONF|NGX_CONF_TAKE1,
        ngx_conf_set_str_slot,
        NGX_HTTP_MAIN_CONF_OFFSET,
        offsetof(ngx_http_zstd_main_conf_t, dict_file),
        NULL },
  
      ngx_null_command
  };
  
  
  static ngx_http_module_t  ngx_http_zstd_filter_module_ctx = {
      ngx_http_zstd_add_variables,            /* preconfiguration */
      ngx_http_zstd_filter_init,              /* postconfiguration */
  
      ngx_http_zstd_create_main_conf,         /* create main configuration */
      ngx_http_zstd_init_main_conf,           /* init main configuration */
  
      NULL,                                   /* create server configuration */
      NULL,                                   /* merge server configuration */
  
      ngx_http_zstd_create_loc_conf,          /* create location configuration */
      ngx_http_zstd_merge_loc_conf,           /* merge location configuration */
  };
  
  
  ngx_module_t  ngx_http_zstd_filter_module = {
      NGX_MODULE_V1,
      &ngx_http_zstd_filter_module_ctx,       /* module context */
      ngx_http_zstd_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_int_t
  ngx_http_zstd_header_filter(ngx_http_request_t *r)
  {
      ngx_table_elt_t           *h;
      ngx_http_zstd_loc_conf_t  *zlcf;
      ngx_http_zstd_ctx_t       *ctx;
  
      zlcf = ngx_http_get_module_loc_conf(r, ngx_http_zstd_filter_module);
  
      if (!zlcf->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 < zlcf->min_length)
         || ngx_http_test_content_type(r, &zlcf->types) == NULL
         || r->header_only)
      {
          return ngx_http_next_header_filter(r);
      }
  
      r->gzip_vary = 1;
  
      if (!r->gzip_tested) {
          if (ngx_http_zstd_ok(r) != NGX_OK) {
              return ngx_http_next_header_filter(r);
          }
  
      } else if (!r->gzip_ok) {
          return ngx_http_next_header_filter(r);
      }
  
      ctx = ngx_pcalloc(r->pool, sizeof(ngx_http_zstd_ctx_t));
      if (ctx == NULL) {
          return NGX_ERROR;
      }
  
      ngx_http_set_ctx(r, ctx, ngx_http_zstd_filter_module);
  
      ctx->request = r;
      ctx->last_out = &ctx->out;
  
      h = 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, "zstd");
      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_zstd_body_filter(ngx_http_request_t *r, ngx_chain_t *in)
  {
      size_t                rv;
      ngx_int_t             flush, rc;
      ngx_chain_t          *cl;
      ngx_http_zstd_ctx_t  *ctx;
  
  
      ctx = ngx_http_get_module_ctx(r, ngx_http_zstd_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 zstd filter");
  
      if (ctx->cstream == NULL) {
          ctx->cstream = ngx_http_zstd_filter_create_cstream(r, ctx);
          if (ctx->cstream == NULL) {
              goto failed;
          }
      }
  
      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_zstd_filter_module);
  
          flush = 0;
          ctx->nomem = 0;
  
      } else {
          flush = ctx->busy ? 1 : 0;
      }
  
      for ( ;; ) {
  
          /* cycle while we can write to a client */
  
          for ( ;; ) {
  
              rc = ngx_http_zstd_filter_add_data(r, ctx);
  
              if (rc == NGX_DECLINED) {
                  break;
              }
  
              if (rc == NGX_AGAIN) {
                  continue;
              }
  
              rc = ngx_http_zstd_filter_get_buf(r, ctx);
  
              if (rc == NGX_ERROR) {
                  goto failed;
              }
  
              if (rc == NGX_DECLINED) {
                  break;
              }
  
              rc = ngx_http_zstd_filter_compress(r, ctx);
  
              if (rc == NGX_ERROR) {
                  goto failed;
              }
  
              if (rc == NGX_OK) {
                  break;
              }
  
              /* 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_zstd_filter_module);
  
          ctx->last_out = &ctx->out;
          ctx->nomem = 0;
          flush = 0;
  
          if (ctx->done) {
              rv = ZSTD_freeCStream(ctx->cstream);
              if (ZSTD_isError(rv)) {
                  ngx_log_error(NGX_LOG_ALERT, r->connection->log, 0,
                                "ZSTD_freeCStream() failed: %s",
                                ZSTD_getErrorName(rc));
  
                  rc = NGX_ERROR;
              }
  
              return rc;
          }
      }
  
  failed:
  
      ctx->done = 1;
      rv = ZSTD_freeCStream(ctx->cstream);
      if (ZSTD_isError(rv)) {
          ngx_log_error(NGX_LOG_ALERT, r->connection->log, 0,
                        "ZSTD_freeCStream() failed: %s", ZSTD_getErrorName(rv));
      }
  
      return NGX_ERROR;
  }
  
  
  static ngx_int_t
  ngx_http_zstd_filter_compress(ngx_http_request_t *r, ngx_http_zstd_ctx_t *ctx)
  {
      size_t        rc, pos_in, pos_out;
      char         *hint;
      ngx_chain_t  *cl;
      ngx_buf_t    *b;
  
      ngx_log_debug8(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
                     "zstd compress in: src:%p pos:%ud size: %ud, "
                     "dst:%p pos:%ud size:%ud flush:%d redo:%d",
                     ctx->buffer_in.src, ctx->buffer_in.pos, ctx->buffer_in.size,
                     ctx->buffer_out.dst, ctx->buffer_out.pos,
                     ctx->buffer_out.size, ctx->flush, ctx->redo);
  
      pos_in = ctx->buffer_in.pos;
      pos_out = ctx->buffer_out.pos;
  
      switch (ctx->action) {
  
      case NGX_HTTP_ZSTD_FILTER_FLUSH:
          hint = "ZSTD_flushStream() ";
          rc = ZSTD_flushStream(ctx->cstream, &ctx->buffer_out);
          break;
  
      case NGX_HTTP_ZSTD_FILTER_END:
          hint = "ZSTD_endStream() ";
          rc = ZSTD_endStream(ctx->cstream, &ctx->buffer_out);
          break;
  
      default:
          hint = "ZSTD_compressStream() ";
          rc = ZSTD_compressStream(ctx->cstream, &ctx->buffer_out,
                                   &ctx->buffer_in);
          break;
      }
  
      if (ZSTD_isError(rc)) {
          ngx_log_error(NGX_LOG_ALERT, r->connection->log, 0,
                        "%s failed: %s", hint, ZSTD_getErrorName(rc));
  
          return NGX_ERROR;
      }
  
      ngx_log_debug6(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
                     "zstd compress out: src:%p pos:%ud size: %ud, "
                     "dst:%p pos:%ud size:%ud",
                     ctx->buffer_in.src, ctx->buffer_in.pos, ctx->buffer_in.size,
                     ctx->buffer_out.dst, ctx->buffer_out.pos,
                     ctx->buffer_out.size);
  
      ctx->in_buf->pos += ctx->buffer_in.pos - pos_in;
      ctx->out_buf->last += ctx->buffer_out.pos - pos_out;
      ctx->redo = 0;
  
      if (rc > 0) {
          if (ctx->action == NGX_HTTP_ZSTD_FILTER_COMPRESS) {
              ctx->action = NGX_HTTP_ZSTD_FILTER_FLUSH;
          }
  
          ctx->redo = 1;
  
      } else if (ctx->last && ctx->action != NGX_HTTP_ZSTD_FILTER_END) {
          ctx->redo = 1;
          ctx->action = NGX_HTTP_ZSTD_FILTER_END;
  
          /* pending to call the ZSTD_endStream() */
  
          return NGX_AGAIN;
  
      } else {
          ctx->action = NGX_HTTP_ZSTD_FILTER_COMPRESS; /* restore */
      }
  
      if (ngx_buf_size(ctx->out_buf) == 0) {
          return NGX_AGAIN;
      }
  
      cl = ngx_alloc_chain_link(r->pool);
      if (cl == NULL) {
          return NGX_ERROR;
      }
  
      b = ctx->out_buf;
  
      if (rc == 0 && (ctx->flush || ctx->last)) {
          r->connection->buffered &= ~NGX_HTTP_GZIP_BUFFERED;
  
          b->flush = ctx->flush;
          b->last_buf = ctx->last;
  
          ctx->done = ctx->last;
          ctx->flush = 0;
      }
  
      ctx->bytes_out += ngx_buf_size(b);
  
      cl->next = NULL;
      cl->buf = b;
  
      *ctx->last_out = cl;
      ctx->last_out = &cl->next;
  
      ngx_memzero(&ctx->buffer_out, sizeof(ZSTD_outBuffer));
  
      return ctx->last && rc == 0 ? NGX_OK : NGX_AGAIN;
  }
  
  
  static ngx_int_t
  ngx_http_zstd_filter_add_data(ngx_http_request_t *r, ngx_http_zstd_ctx_t *ctx)
  {
      if (ctx->buffer_in.pos < ctx->buffer_in.size
          || ctx->flush
          || ctx->last
          || ctx->redo)
      {
          return NGX_OK;
      }
  
      ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
                     "zstd in: %p", ctx->in);
  
      if (ctx->in == NULL) {
          return NGX_DECLINED;
      }
  
      ctx->in_buf = ctx->in->buf;
      ctx->in = ctx->in->next;
  
      if (ctx->in_buf->flush) {
          ctx->flush = 1;
  
      } else if (ctx->in_buf->last_buf) {
          ctx->last = 1;
      }
  
      ctx->buffer_in.src = ctx->in_buf->pos;
      ctx->buffer_in.pos = 0;
      ctx->buffer_in.size = ngx_buf_size(ctx->in_buf);
  
      ctx->bytes_in += ngx_buf_size(ctx->in_buf);
  
      if (ctx->buffer_in.size == 0) {
          return NGX_AGAIN;
      }
  
      return NGX_OK;
  }
  
  
  static ngx_int_t
  ngx_http_zstd_filter_get_buf(ngx_http_request_t *r, ngx_http_zstd_ctx_t *ctx)
  {
      ngx_chain_t               *cl;
      ngx_http_zstd_loc_conf_t  *zlcf;
  
      if (ctx->buffer_out.pos < ctx->buffer_out.size) {
          return NGX_OK;
      }
  
      zlcf = ngx_http_get_module_loc_conf(r, ngx_http_zstd_filter_module);
  
      if (ctx->free) {
          cl = ctx->free;
          ctx->free = ctx->free->next;
          ctx->out_buf = cl->buf;
          ngx_free_chain(r->pool, cl);
  
      } else if (ctx->bufs < zlcf->bufs.num) {
          ctx->out_buf = ngx_create_temp_buf(r->pool, zlcf->bufs.size);
          if (ctx->out_buf == NULL) {
              return NGX_ERROR;
          }
  
          ctx->out_buf->tag = (ngx_buf_tag_t) &ngx_http_zstd_filter_module;
          ctx->out_buf->recycled = 1;
          ctx->bufs++;
  
      } else {
          ctx->nomem = 1;
          return NGX_DECLINED;
      }
  
      ctx->buffer_out.dst = ctx->out_buf->pos;
      ctx->buffer_out.pos = 0;
      ctx->buffer_out.size = ctx->out_buf->end - ctx->out_buf->start;
  
      return NGX_OK;
  }
  
  
  static ZSTD_CStream *
  ngx_http_zstd_filter_create_cstream(ngx_http_request_t *r,
      ngx_http_zstd_ctx_t *ctx)
  {
      size_t                      rc;
      ZSTD_CStream               *cstream;
      ZSTD_customMem              cmem;
      ngx_http_zstd_loc_conf_t   *zlcf;
  
      zlcf = ngx_http_get_module_loc_conf(r, ngx_http_zstd_filter_module);
  
      cmem.customAlloc = ngx_http_zstd_filter_alloc;
      cmem.customFree = ngx_http_zstd_filter_free;
      cmem.opaque = ctx;
  
      cstream = ZSTD_createCStream_advanced(cmem);
      if (cstream == NULL) {
          ngx_log_error(NGX_LOG_ALERT, r->connection->log, 0,
                        "ZSTD_createCStream_advanced() failed");
  
          return NULL;
      }
  
      /* TODO use the advanced initialize functions */
  
      if (zlcf->dict) {
          rc = ZSTD_initCStream_usingCDict(cstream, zlcf->dict);
          if (ZSTD_isError(rc)) {
              ngx_log_error(NGX_LOG_ALERT, r->connection->log, 0,
                            "ZSTD_initCStream_usingCDict() failed: %s",
                            ZSTD_getErrorName(rc));
  
              goto failed;
          }
  
      } else {
          rc = ZSTD_initCStream(cstream, zlcf->level);
          if (ZSTD_isError(rc)) {
              ngx_log_error(NGX_LOG_ALERT, r->connection->log, 0,
                            "ZSTD_initCStream() failed: %s",
                            ZSTD_getErrorName(rc));
  
              goto failed;
          }
      }
  
      return cstream;
  
  failed:
      rc = ZSTD_freeCStream(cstream);
      if (ZSTD_isError(rc)) {
          ngx_log_error(NGX_LOG_ALERT, r->connection->log, 0,
                        "ZSTD_freeCStream() failed: %s", ZSTD_getErrorName(rc));
      }
  
      return NULL;
  }
  
  
  static ngx_int_t
  ngx_http_zstd_accept_encoding(ngx_str_t *ae)
  {
      u_char  *p;
  
      p = ngx_strcasestrn(ae->data, "zstd", sizeof("zstd") - 1);
      if (p == NULL) {
          return NGX_DECLINED;
      }
  
      if (p == ae->data || (*(p - 1) == ',' || *(p - 1) == ' ')) {
  
          p += sizeof("zstd") - 1;
  
          if (p == ae->data + ae->len || *p == ',' || *p == ' ' || *p == ';') {
              return NGX_OK;
          }
      }
  
      return NGX_DECLINED;
  }
  
  
  static ngx_int_t
  ngx_http_zstd_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("zstd") - 1) {
          return NGX_DECLINED;
      }
  
      if (ngx_memcmp(ae->value.data, "zstd", 4) != 0
          && ngx_http_zstd_accept_encoding(&ae->value) != NGX_OK)
      {
          return NGX_DECLINED;
      }
  
  
      r->gzip_tested = 1;
      r->gzip_ok = 0;
  
      return NGX_OK;
  }
  
  
  static void *
  ngx_http_zstd_create_main_conf(ngx_conf_t *cf)
  {
      ngx_http_zstd_main_conf_t  *zmcf;
  
      zmcf = ngx_pcalloc(cf->pool, sizeof(ngx_http_zstd_main_conf_t));
      if (zmcf == NULL) {
          return NULL;
      }
  
      return zmcf;
  }
  
  
  static char *
  ngx_http_zstd_init_main_conf(ngx_conf_t *cf, void *conf)
  {
      ngx_http_zstd_main_conf_t *zmcf = conf;
  
      if (zmcf->dict_file.len == 0) {
          return NGX_CONF_OK;
      }
  
      if (ngx_conf_full_name(cf->cycle, &zmcf->dict_file, 1) != NGX_OK) {
          return NGX_CONF_ERROR;
      }
  
      return NGX_CONF_OK;
  }
  
  
  static void *
  ngx_http_zstd_create_loc_conf(ngx_conf_t *cf)
  {
      ngx_http_zstd_loc_conf_t  *conf;
  
      conf = ngx_pcalloc(cf->pool, sizeof(ngx_http_zstd_loc_conf_t));
      if (conf == NULL) {
          return NULL;
      }
  
      /*
       * set by ngx_pcalloc():
       *
       *    conf->bufs.num = 0;
       *    conf->types = { NULL };
       *    conf->types_keys = NULL;
       *    conf->dict = NULL;
       */
  
      conf->enable = NGX_CONF_UNSET;
      conf->level = NGX_CONF_UNSET;
      conf->min_length = NGX_CONF_UNSET;
  
      return conf;
  }
  
  
  static char *
  ngx_http_zstd_merge_loc_conf(ngx_conf_t *cf, void *parent, void *child)
  {
      ngx_http_zstd_loc_conf_t *prev = parent;
      ngx_http_zstd_loc_conf_t *conf = child;
  
      ngx_fd_t                    fd;
      size_t                      size;
      ssize_t                     n;
      char                       *rc;
      u_char                     *buf;
      ngx_file_info_t             info;
      ngx_http_zstd_main_conf_t  *zmcf;
  
      rc = NGX_OK;
      buf = NULL;
      fd = NGX_INVALID_FILE;
  
      ngx_conf_merge_value(conf->enable, prev->enable, 0);
      ngx_conf_merge_value(conf->level, prev->level, 1);
      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))
      {
          return NGX_CONF_ERROR;
      }
  
      ngx_conf_merge_ptr_value(conf->dict, prev->dict, NULL);
      ngx_conf_merge_bufs_value(conf->bufs, prev->bufs,
                                (128 * 1024) / ngx_pagesize, ngx_pagesize);
  
      zmcf = ngx_http_conf_get_module_main_conf(cf, ngx_http_zstd_filter_module);
  
      if (conf->enable && zmcf->dict_file.len > 0) {
  
          if (conf->level == prev->level) {
              conf->dict = prev->dict;
  
          } else {
              /*
               * compression level is different from the outer block,
               * so we should create a seperate dict object.
               */
  
              fd = ngx_open_file(zmcf->dict_file.data, NGX_FILE_RDONLY,
                                 NGX_FILE_OPEN, 0);
  
              if (fd == NGX_INVALID_FILE) {
                  ngx_conf_log_error(NGX_LOG_EMERG, cf, ngx_errno,
                                     ngx_open_file_n " \"%V\" failed",
                                     &zmcf->dict_file);
  
                  return NGX_CONF_ERROR;
              }
  
              if (ngx_fd_info(fd, &info) == NGX_FILE_ERROR) {
                  ngx_conf_log_error(NGX_LOG_EMERG, cf, ngx_errno,
                                     ngx_fd_info_n " \"%V\" failed",
                                     &zmcf->dict_file);
  
                  rc = NGX_CONF_ERROR;
                  goto close;
              }
  
              size = ngx_file_size(&info);
              buf = ngx_palloc(cf->pool, size);
              if (buf == NULL) {
                  rc = NGX_CONF_ERROR;
                  goto close;
              }
  
              n = ngx_read_fd(fd, (void *) buf, size);
              if (n < 0) {
                  ngx_conf_log_error(NGX_LOG_EMERG, cf, ngx_errno,
                                     ngx_read_fd_n " %V\" failed",
                                     &zmcf->dict_file);
  
                  rc = NGX_CONF_ERROR;
                  goto close;
  
              } else if ((size_t) n != size) {
                  ngx_conf_log_error(NGX_LOG_EMERG, cf, ngx_errno,
                                     ngx_read_fd_n "\"%V incomplete\"",
                                     &zmcf->dict_file);
  
                  rc = NGX_CONF_ERROR;
                  goto close;
              }
  
              conf->dict = ZSTD_createCDict_byReference(buf, size, conf->level);
              if (conf->dict == NULL) {
                  ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
                                     "ZSTD_createCDict_byReference() failed");
                  rc = NGX_CONF_ERROR;
                  goto close;
              }
          }
      }
  
  close:
  
      if (fd != NGX_INVALID_FILE && ngx_close_file(fd) == NGX_FILE_ERROR) {
          ngx_conf_log_error(NGX_LOG_EMERG, cf, ngx_errno,
                             ngx_close_file_n " \"%V\" failed",
                             &zmcf->dict_file);
  
          rc = NGX_CONF_ERROR;
      }
  
      return rc;
  }
  
  
  static ngx_int_t
  ngx_http_zstd_filter_init(ngx_conf_t *cf)
  {
      ngx_http_next_header_filter = ngx_http_top_header_filter;
      ngx_http_top_header_filter = ngx_http_zstd_header_filter;
  
      ngx_http_next_body_filter = ngx_http_top_body_filter;
      ngx_http_top_body_filter = ngx_http_zstd_body_filter;
  
      return NGX_OK;
  }
  
  
  static void *
  ngx_http_zstd_filter_alloc(void *opaque, size_t size)
  {
      ngx_http_zstd_ctx_t *ctx = opaque;
  
      void  *p;
  
      p = ngx_palloc(ctx->request->pool, size);
  
      ngx_log_debug2(NGX_LOG_DEBUG_HTTP, ctx->request->connection->log, 0,
                     "zstd alloc: %p, size: %uz", p, size);
  
      return p;
  }
  
  
  static ngx_int_t
  ngx_http_zstd_add_variables(ngx_conf_t *cf)
  {
      ngx_http_variable_t  *v;
  
      v = ngx_http_add_variable(cf, &ngx_http_zstd_ratio,
                                NGX_HTTP_VAR_NOCACHEABLE);
      if (v == NULL) {
          return NGX_ERROR;
      }
  
      v->get_handler = ngx_http_zstd_ratio_variable;
  
      return NGX_OK;
  }
  
  
  static ngx_int_t
  ngx_http_zstd_ratio_variable(ngx_http_request_t *r,
      ngx_http_variable_value_t *vv, uintptr_t data)
  {
      ngx_uint_t            ratio_int, ratio_frac;
      ngx_http_zstd_ctx_t  *ctx;
  
      ctx = ngx_http_get_module_ctx(r, ngx_http_zstd_filter_module);
      if (ctx == NULL || !ctx->done || ctx->bytes_out == 0) {
          vv->not_found = 1;
          return NGX_OK;
      }
  
      vv->data = ngx_pnalloc(r->pool, NGX_INT32_LEN + 3);
      if (vv->data == NULL) {
          return NGX_ERROR;
      }
  
      ratio_int = (ngx_uint_t) ctx->bytes_in / ctx->bytes_out;
      ratio_frac = (ngx_uint_t) (ctx->bytes_in * 1000 / ctx->bytes_out % 1000);
  
      vv->len = ngx_sprintf(vv->data, "%ui.%03ui", ratio_int, ratio_frac)
                - vv->data;
  
      vv->valid = 1;
      vv->no_cacheable = 1;
  
      return NGX_OK;
  }
  
  
  static void
  ngx_http_zstd_filter_free(void *opaque, void *address)
  {
  #if (NGX_DEBUG)
  
      ngx_http_zstd_ctx_t *ctx = opaque;
  
      ngx_log_debug1(NGX_LOG_DEBUG_HTTP, ctx->request->connection->log, 0,
                     "zstd free: %p", address);
  
  #endif
  }
  
  
  static char *
  ngx_http_zstd_comp_level(ngx_conf_t *cf, void *post, void *data)
  {
      ngx_int_t  *np = data;
  
      if (*np < 1 || *np > ZSTD_maxCLevel()) {
          ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
                             "zstd compress level must between 1 and %i",
                             ZSTD_maxCLevel());
  
          return NGX_CONF_ERROR;
      }
  
      return NGX_CONF_OK;
  }
  