ngx_int_t hustdb_ha_read_handler(
    ngx_bool_t read_body,
    ngx_bool_t key_in_body,
    hustdb_ha_check_parameter_t check_parameter,
    ngx_str_t * backend_uri,
    ngx_http_request_t *r)
{
	hustdb_ha_ctx_t * ctx = ngx_http_get_addon_module_ctx(r);
	if (!ctx)
	{
	    if (read_body && !(r->method & NGX_HTTP_POST))
	    {
	        return NGX_HTTP_NOT_ALLOWED;
	    }
	    if (check_parameter && !check_parameter(backend_uri, r))
        {
            return NGX_ERROR;
        }
	    return read_body ? __post_read(key_in_body, backend_uri, r) : __start_read(backend_uri, r);
	}
	if (NGX_HTTP_OK != r->headers_out.status)
	{
		ctx->peer = ngx_http_get_next_peer(ctx->peer);
		return ctx->peer ? ngx_http_run_subrequest(
				r, &ctx->base, ctx->peer->peer) : hustdb_ha_send_response(
						NGX_HTTP_NOT_FOUND, NULL, NULL, r);
	}
	return hustdb_ha_send_response(NGX_HTTP_OK, ctx->version, &ctx->base.response, r);
}
static ngx_int_t __write_sync_data(
    uint8_t method,
    ngx_bool_t has_tb,
    ngx_http_request_t *r,
    ngx_http_hustdb_ha_write_ctx_t * ctx)
{
    do
    {
        time_t now = time(NULL);
        uint32_t ttl = (0 == ctx->ttl) ? 0 : (uint32_t) (now + ctx->ttl);

        ngx_buf_t * value = NULL;
        ngx_buf_t * head = __encode_head(method, ctx->proto, has_tb, ctx->base.version, ctx->base.key, ctx->base.tb, ttl, r->pool);
        if (r->request_body)
        {
            if (!ngx_http_insert_head_to_body(head, r))
            {
                break;
            }
            value = ngx_http_get_request_body(r);
        }
        else
        {
            value = head;
        }

        if (!value)
        {
            break;
        }

        ngx_http_hustdb_ha_main_conf_t * mcf = hustdb_ha_get_module_main_conf(r);
        ngx_str_t * server = &ctx->error_peer->peer->server;
        if (!hustdb_ha_write_log(&mcf->zlog_mdc, server, value, r->pool))
        {
            break;
        }

        static ngx_str_t SYNC_KEY = ngx_string("Sync");
        if (!ngx_http_add_field_to_headers_out(&SYNC_KEY, server, r))
        {
            break;
        }

        return hustdb_ha_send_response(NGX_HTTP_OK, ctx->base.version, NULL, r);
    } while (0);

    return hustdb_ha_send_response(NGX_HTTP_NOT_FOUND, NULL, NULL, r);
}
static ngx_int_t __on_write_master1_complete(
    uint8_t method,
    ngx_bool_t has_tb,
    ngx_http_request_t *r,
    ngx_http_hustdb_ha_write_ctx_t * ctx)
{
    __update_error(method, r->headers_out.status, ctx);
    ctx->base.peer = ctx->base.peer->next;
    ngx_bool_t alive = ngx_http_peer_is_alive(ctx->base.peer->peer);
    ngx_http_hustdb_ha_main_conf_t * mcf = hustdb_ha_get_module_main_conf(r);
    if (mcf->debug_sync)
    {
        alive = false;
    }
    if (alive) // master2
    {
        ctx->state = STATE_WRITE_MASTER2;
        return ngx_http_run_subrequest(r, &ctx->base.base, ctx->base.peer->peer);
    }

    // master2 dead
    ++ctx->error_count;
    ctx->error_peer = ctx->base.peer;

    if (ctx->error_count > 1) // both master1 & master2 fail
    {
        return hustdb_ha_send_response(NGX_HTTP_NOT_FOUND, NULL, NULL, r);
    }

    return __write_sync_data(method, has_tb, r, ctx);
}
static void __post_handler(ngx_http_request_t *r)
{
    --r->main->count;

    ngx_http_hustdb_ha_write_ctx_t * ctx = ngx_http_get_addon_module_ctx(r);

    ngx_bool_t rc = true;
    if (ctx->key_in_body)
    {
        if (!__init_write_ctx_by_body(r, ctx))
        {
            rc = false;
        }
    }
    else
    {
        if (ctx->check_body_len)
        {
            rc = hustdb_ha_check_body(r);
        }
    }

    if (!rc)
    {
        hustdb_ha_send_response(NGX_HTTP_NOT_FOUND, NULL, NULL, r);
        return;
    }

    ngx_http_gen_subrequest(
        ctx->base.base.backend_uri,
        r,
        ctx->base.peer->peer,
        &ctx->base.base,
        hustdb_ha_on_subrequest_complete);
}
// -------------------------------------------------------------
// |skip   |  0   |  1   |  2   |  0       |  1   |  2  |  0   |
// -------------------------------------------------------------
// |err    |  0   |  0   |  0   |  1       |  1   |  0  |  2   |
// -------------------------------------------------------------
// |action |  200 |  200 |  404 |  200&log |  404 | 404 | 404  |
// -------------------------------------------------------------
static ngx_int_t __send_write_response(
    uint8_t method,
    ngx_bool_t has_tb,
    ngx_http_request_t *r,
    ngx_http_hustdb_ha_write_ctx_t * ctx)
{
    if (0 == ctx->skip_error_count && 1 == ctx->error_count)
    {
        return __write_sync_data(method, has_tb, r, ctx);
    }
    if (0 == ctx->skip_error_count && 0 == ctx->error_count)
    {
        return hustdb_ha_send_response(NGX_HTTP_OK, &ctx->base.version, NULL, r);
    }
    return hustdb_ha_send_response(NGX_HTTP_NOT_FOUND, NULL, NULL, r);
}
ngx_int_t hustdb_ha_start_post(
    ngx_bool_t support_post_only,
    ngx_bool_t key_in_body,
    ngx_bool_t has_tb,
    ngx_bool_t check_body_len,
    ngx_str_t * backend_uri,
    ngx_http_request_t *r)
{
    ngx_http_hustdb_ha_write_ctx_t * ctx = __create_write_ctx(r);
    if (!ctx)
    {
        return hustdb_ha_send_response(NGX_HTTP_NOT_FOUND, NULL, NULL, r);
    }
    ctx->check_body_len = check_body_len;
    ctx->base.base.backend_uri = backend_uri;

    if (support_post_only && !(r->method & NGX_HTTP_POST))
    {
        return NGX_HTTP_NOT_ALLOWED;
    }

    if (key_in_body)
    {
        ctx->key_in_body = true;
        ctx->has_tb = has_tb;
    }
    else
    {
        write_ctx_t tmp;
        if (!__set_write_context(NULL, has_tb, r, &tmp))
        {
            return hustdb_ha_send_response(NGX_HTTP_NOT_FOUND, NULL, NULL, r);
        }
        __copy_data(&tmp, ctx);
    }

    ngx_int_t rc = ngx_http_read_client_request_body(r, __post_handler);
    if ( rc >= NGX_HTTP_SPECIAL_RESPONSE )
    {
        return rc;
    }
    return NGX_DONE;
}
static ngx_int_t __on_write_master2_complete(
    uint8_t method,
    ngx_bool_t has_tb,
    ngx_http_request_t *r,
    ngx_http_hustdb_ha_write_ctx_t * ctx)
{
    __update_error(method, r->headers_out.status, ctx);
    if (ctx->error_count > 1) // both master1 & master2 fail
    {
        return hustdb_ha_send_response(NGX_HTTP_NOT_FOUND, NULL, NULL, r);
    }
    else if (1 == ctx->error_count) // master1 or master2 fail
    {
        return __write_sync_data(method, has_tb, r, ctx);
    }
    if (ctx->skip_error_count > 1) // both master1 and master2 return 404 for del
    {
        return hustdb_ha_send_response(NGX_HTTP_NOT_FOUND, NULL, NULL, r);
    }
    return hustdb_ha_send_response(NGX_HTTP_OK, ctx->base.version, NULL, r);
}
static ngx_int_t __start_zwrite(ngx_str_t * backend_uri, ngx_http_request_t *r)
{
    ngx_http_hustdb_ha_write_ctx_t * ctx = __create_zwrite_ctx(r);
    if (!ctx)
    {
        return hustdb_ha_send_response(NGX_HTTP_NOT_FOUND, NULL, NULL, r);
    }
    ctx->base.base.backend_uri = backend_uri;

    write_ctx_t tmp;
    if (!__parse_args(true, true, r, &tmp))
    {
        return hustdb_ha_send_response(NGX_HTTP_NOT_FOUND, NULL, NULL, r);
    }
    __copy_data(&tmp, ctx);

    ngx_int_t rc = ngx_http_read_client_request_body(r, __post_body_handler);
    if ( rc >= NGX_HTTP_SPECIAL_RESPONSE )
    {
        return rc;
    }
    return NGX_DONE;
}
static void __post_body_handler(ngx_http_request_t *r)
{
    --r->main->count;

    ngx_http_hustdb_ha_write_ctx_t * ctx = ngx_http_get_addon_module_ctx(r);

    ctx->base.key = hustdb_ha_get_key(r);
    if (!ctx->base.key)
    {
        hustdb_ha_send_response(NGX_HTTP_NOT_FOUND, NULL, NULL, r);
        return;
    }

    ngx_http_gen_subrequest(
        ctx->base.base.backend_uri,
        r,
        ctx->base.peer->peer,
        &ctx->base.base,
        hustdb_ha_on_subrequest_complete);
}
static void __post_handler(ngx_http_request_t *r)
{
    --r->main->count;
    hustdb_ha_ctx_t * ctx = ngx_http_get_addon_module_ctx(r);

    ngx_http_subrequest_peer_t * peer = __get_readable_peer(ctx->key_in_body, r);
    if (!peer)
    {
        hustdb_ha_send_response(NGX_HTTP_NOT_FOUND, NULL, NULL, r);
    }
    else
    {
        ctx->peer = peer;

        ngx_http_gen_subrequest(
            ctx->base.backend_uri,
            r,
            peer->peer,
            &ctx->base,
            hustdb_ha_on_subrequest_complete);
    }
}
static ngx_bool_t __init_write_ctx_by_body(ngx_http_request_t *r, ngx_http_hustdb_ha_write_ctx_t * ctx)
{
    do
    {
        char * key = hustdb_ha_get_key_from_body(r);
        if (!key)
        {
            break;
        }

        write_ctx_t tmp;
        if (!__set_write_context(key, ctx->has_tb, r, &tmp))
        {
            break;
        }

        __copy_data(&tmp, ctx);

        return true;
    } while (0);

    hustdb_ha_send_response(NGX_HTTP_NOT_FOUND, NULL, NULL, r);
    return false;
}