static ngx_int_t
ngx_http_tnt_send_reply(ngx_http_request_t *r,
                        ngx_http_upstream_t *u,
                        ngx_http_tnt_ctx_t *ctx)
{
    tp_transcode_t          tc;
    ngx_int_t               rc;
    ngx_http_tnt_loc_conf_t *tlcf;
    ngx_buf_t               *output;
    size_t                  output_size;


    tlcf = ngx_http_get_module_loc_conf(r, ngx_http_tnt_module);

    output_size =
        (ctx->tp_cache->end - ctx->tp_cache->start + ngx_http_tnt_overhead())
        * tlcf->out_multiplier;
    output = ngx_http_tnt_create_mem_buf(r, u, output_size);
    if (output == NULL) {
        return NGX_ERROR;
    }

    if (ctx->batch_size > 0
        && ctx->rest_batch_size == ctx->batch_size)
    {
        *output->pos = '[';
        ++output->pos;
    }

    tp_transcode_init_args_t args = {
        .output = (char *)output->pos,
        .output_size = output->end - output->pos,
        .method = NULL, .method_len = 0,
        .codec = TP_REPLY_TO_JSON,
        .mf = NULL
    };
    rc = tp_transcode_init(&tc, &args);
    if (rc == TP_TRANSCODE_ERROR) {
        crit("[BUG] failed to call tp_transcode_init(output)");
        return NGX_ERROR;
    }

    rc = tp_transcode(&tc, (char *)ctx->tp_cache->start,
                      ctx->tp_cache->end - ctx->tp_cache->start);
    if (rc == TP_TRANSCODE_OK) {

        size_t complete_msg_size = 0;
        rc = tp_transcode_complete(&tc, &complete_msg_size);
        if (rc == TP_TRANSCODE_ERROR) {

            crit("[BUG] failed to complete output transcoding");

            ngx_pfree(r->pool, output);

            const ngx_http_tnt_error_t *e = get_error_text(UNKNOWN_PARSE_ERROR);
            output = ngx_http_tnt_set_err(r, e->code, e->msg.data, e->msg.len);
            if (output == NULL) {
                goto error_exit;
            }

            goto done;
        }

        output->last = output->pos + complete_msg_size;

    } else if (rc == TP_TRANSCODE_ERROR) {

        crit("[BUG] failed to transcode output, err: '%s'", tc.errmsg);

        ngx_pfree(r->pool, output);

        output = ngx_http_tnt_set_err(r,
                                      tc.errcode,
                                      (u_char *)tc.errmsg,
                                      ngx_strlen(tc.errmsg));
        if (output == NULL) {
            goto error_exit;
        }
    }

done:
    tp_transcode_free(&tc);

    if (ctx->batch_size > 0) {

        if (ctx->rest_batch_size == 1)
        {
            *output->last = ']';
            ++output->last;
        }
        else if (ctx->rest_batch_size <= ctx->batch_size)
        {
            *output->last = ',';
            ++output->last;
        }
    }

    return ngx_http_tnt_output(r, u, output);

error_exit:
    tp_transcode_free(&tc);
    return NGX_ERROR;
}


static ngx_int_t
ngx_http_tnt_filter_reply(ngx_http_request_t *r,
                          ngx_http_upstream_t *u,
                          ngx_buf_t *b)
{
    ngx_http_tnt_ctx_t *ctx = ngx_http_get_module_ctx(r, ngx_http_tnt_module);
    ssize_t            bytes = b->last - b->pos;

    dd("filter_reply -> recv bytes: %i, rest: %i", (int)bytes, (int)ctx->rest);

    if (ctx->state == READ_PAYLOAD) {

        ssize_t payload_rest = ngx_min(ctx->payload.e - ctx->payload.p, bytes);
        if (payload_rest > 0) {
            ctx->payload.p = ngx_copy(ctx->payload.p, b->pos, payload_rest);
            bytes -= payload_rest;
            b->pos += payload_rest;
            payload_rest = ctx->payload.e - ctx->payload.p;

            dd("filter_reply -> payload rest:%i", (int)payload_rest);
        }

        if (payload_rest == 0) {
            ctx->payload_size = tp_read_payload((char *)&ctx->payload.mem[0],
                                                (char *)ctx->payload.e);
            if (ctx->payload_size <= 0) {
                crit("[BUG] tp_read_payload failed, ret:%i",
                        (int)ctx->payload_size);
                return NGX_ERROR;
            }

            ctx->rest = ctx->payload_size - 5 /* - header size */;

            dd("filter_reply -> got header payload:%i, rest:%i",
                    (int)ctx->payload_size,
                    (int)ctx->rest);

            ctx->tp_cache = ngx_create_temp_buf(r->pool, ctx->payload_size);
            if (ctx->tp_cache == NULL) {
                return NGX_ERROR;
            }

            ctx->tp_cache->pos = ctx->tp_cache->start;
            ctx->tp_cache->memory = 1;


            ctx->tp_cache->pos = ngx_copy(ctx->tp_cache->pos,
                                          &ctx->payload.mem[0],
                                          sizeof(ctx->payload.mem) - 1);

            ctx->payload.p = &ctx->payload.mem[0];

            ctx->state = READ_BODY;
        } else {
            return NGX_OK;
        }
    }

    ngx_int_t rc = NGX_OK;
    if (ctx->state == READ_BODY) {

        ssize_t rest = ctx->rest - bytes, read_on = bytes;
        if (rest < 0) {
            rest *= -1;
            read_on = bytes - rest;
            ctx->rest = 0;
            ctx->state = SEND_REPLY;
            rc = NGX_AGAIN;
        } else if (rest == 0) {
            ctx->state = SEND_REPLY;
            ctx->rest = 0;
        } else {
            ctx->rest -= bytes;
        }

        ctx->tp_cache->pos = ngx_copy(ctx->tp_cache->pos, b->pos, read_on);
        b->pos += read_on;

        dd("filter_reply -> read_on:%i, rest:%i, cache rest:%i, buf size:%i",
                (int)read_on,
                (int)ctx->rest,
                (int)(ctx->tp_cache->end - ctx->tp_cache->pos),
                (int)(b->last - b->pos));
    }

    if (ctx->state == SEND_REPLY) {

        rc = ngx_http_tnt_send_reply(r, u, ctx);

        ctx->state = READ_PAYLOAD;
        ctx->rest = ctx->payload_size = 0;

        --ctx->rest_batch_size;

        if (ctx->rest_batch_size <= 0) {
            u->length = 0;
            ctx->rest_batch_size = 0;
            ctx->batch_size = 0;
        }

        ngx_pfree(r->pool, ctx->tp_cache);
        ctx->tp_cache = NULL;

        if (b->last - b->pos > 0) {
            rc = NGX_AGAIN;
        }
    }

    return rc;
}
static ngx_int_t
ngx_http_tnt_send_once(ngx_http_request_t *r,
                       ngx_http_tnt_ctx_t *ctx,
                       ngx_chain_t *out_chain,
                       const u_char *buf, size_t len)
{
    tp_transcode_t           tc;
    size_t                   complete_msg_size;
    tp_transcode_init_args_t args = {
        .output = (char *)out_chain->buf->start,
        .output_size = out_chain->buf->end - out_chain->buf->start,
        .method = NULL, .method_len = 0,
        .codec = YAJL_JSON_TO_TP,
        .mf = NULL
    };

    if (tp_transcode_init(&tc, &args) == TP_TRANSCODE_ERROR) {
        goto error_exit;
    }

    if (tp_transcode(&tc, (char *)buf, len) == TP_TRANSCODE_ERROR) {
        dd("ngx_http_tnt_send:tp_transcode error: %s, code:%d",
                tc.errmsg, tc.errcode);
        goto error_exit;
    }

    if (tp_transcode_complete(&tc, &complete_msg_size) == TP_TRANSCODE_OK) {

        out_chain->buf->last = out_chain->buf->start + complete_msg_size;

        if (tc.batch_size > 1) {
            ctx->rest_batch_size = ctx->batch_size = tc.batch_size;
        }
    } else {
        goto error_exit;
    }

    tp_transcode_free(&tc);
    return NGX_OK;

error_exit:
    tp_transcode_free(&tc);
    return NGX_ERROR;
}


static inline void
ngx_http_tnt_cleanup(ngx_http_request_t *r, ngx_http_tnt_ctx_t *ctx)
{
    if (ctx == NULL) {
        return;
    }

    if (ctx->tp_cache != NULL) {
        ngx_pfree(r->pool, ctx->tp_cache);
        ctx->tp_cache = NULL;
    }
}


static inline ngx_int_t
ngx_http_tnt_set_method(ngx_http_tnt_ctx_t *ctx,
                        ngx_http_request_t *r,
                        ngx_http_tnt_loc_conf_t *tlcf)
{
    u_char *start, *pos, *end;

    if (tlcf->method.data && tlcf->method.len) {

        ctx->preset_method_len = ngx_min(tlcf->method.len,
                                         sizeof(ctx->preset_method)-1);
        ngx_memcpy(ctx->preset_method,
                   tlcf->method.data,
                   ctx->preset_method_len);

    } else if (tlcf->http_rest_methods & r->method) {

        if (r->uri.data == NULL || !r->uri.len) {
            goto error;
        }

        start = pos = (*r->uri.data == '/'? r->uri.data + 1: r->uri.data);
        end = r->uri.data + r->uri.len;

        for (;pos != end; ++pos) {
            if (*pos == '/') {
                ctx->preset_method_len = ngx_min(sizeof(ctx->preset_method)-1,
                                                 (size_t)(pos - start));
                ngx_memcpy(ctx->preset_method, start, ctx->preset_method_len);
                break;
            }
        }

        if (!ctx->preset_method[0]) {

            if (start == end) {
                goto error;
            }

            ctx->preset_method_len = ngx_min(sizeof(ctx->preset_method)-1,
                                            (size_t)(end - start));
            ngx_memcpy(ctx->preset_method, start, ctx->preset_method_len);
        }
    }
    /* Else -- expect method in the body */

    return NGX_OK;

error:
    ctx->preset_method[0] = 0;
    ctx->preset_method_len = 0;
    return NGX_ERROR;
}