small-package/luci-app-nginx-pingos/modules/nginx-client-module/ngx_http_client.c

1890 lines
48 KiB
C

/*
* Copyright (C) AlexWoo(Wu Jie) wj19840501@gmail.com
*/
#include "ngx_http_client.h"
#include "ngx_rbuf.h"
#include "ngx_poold.h"
#include "ngx_map.h"
#include "ngx_timerd.h"
static void *ngx_http_client_module_create_conf(ngx_cycle_t *cycle);
static char *ngx_http_client_module_init_conf(ngx_cycle_t *cycle, void *conf);
/* headers in */
static ngx_int_t ngx_http_client_process_header_line(ngx_http_request_t *r,
ngx_table_elt_t *h, ngx_uint_t offset);
static ngx_int_t ngx_http_client_process_content_length(ngx_http_request_t *r,
ngx_table_elt_t *h, ngx_uint_t offset);
static ngx_int_t ngx_http_client_process_connection(ngx_http_request_t *r,
ngx_table_elt_t *h, ngx_uint_t offset);
static ngx_int_t
ngx_http_client_process_transfer_encoding(ngx_http_request_t *r,
ngx_table_elt_t *h, ngx_uint_t offset);
/* headers out */
static void ngx_http_client_host(ngx_http_request_t *r, ngx_str_t *value);
static void ngx_http_client_user_agent(ngx_http_request_t *r,
ngx_str_t *value);
static void ngx_http_client_connection(ngx_http_request_t *r, ngx_str_t *value);
static void ngx_http_client_accept(ngx_http_request_t *r, ngx_str_t *value);
static void ngx_http_client_date(ngx_http_request_t *r, ngx_str_t *value);
/* for http response */
typedef struct {
ngx_list_t headers;
ngx_uint_t http_version;
ngx_uint_t status_n;
ngx_str_t status_line;
ngx_table_elt_t *status;
ngx_table_elt_t *date;
ngx_table_elt_t *server;
ngx_table_elt_t *connection;
ngx_table_elt_t *expires;
ngx_table_elt_t *etag;
ngx_table_elt_t *x_accel_expires;
ngx_table_elt_t *x_accel_redirect;
ngx_table_elt_t *x_accel_limit_rate;
ngx_table_elt_t *content_type;
ngx_table_elt_t *content_length;
ngx_table_elt_t *last_modified;
ngx_table_elt_t *location;
ngx_table_elt_t *accept_ranges;
ngx_table_elt_t *www_authenticate;
ngx_table_elt_t *transfer_encoding;
#if (NGX_HTTP_GZIP)
ngx_table_elt_t *content_encoding;
#endif
off_t content_length_n;
unsigned connection_type:2;
unsigned chunked:1;
} ngx_http_client_headers_in_t;
typedef struct {
ngx_map_node_t node;
ngx_str_t key;
ngx_str_t value;
} ngx_http_client_header_out_t;
typedef struct {
ngx_array_t headers; /* ngx_http_client_header_out_t */
ngx_map_t hash; /* find header by header */
} ngx_http_client_headers_out_t;
typedef struct {
ngx_client_session_t *session;
void *request;
/* Request */
ngx_request_url_t url;
/* Response */
ngx_http_status_t status;
ngx_http_chunked_t chunked;
ngx_int_t length;
/* bufs */
ngx_chain_t *in;
ngx_buf_t *buffer; /* status line buf */
/* config */
ngx_msec_t header_timeout;
size_t header_buffer_size;
/* runtime */
off_t rbytes; /* read bytes */
off_t wbytes; /* write bytes */
ngx_http_client_headers_in_t headers_in;
ngx_http_client_headers_out_t headers_out;
ngx_http_client_handler_pt read_handler;
ngx_http_client_handler_pt write_handler;
} ngx_http_client_ctx_t;
static ngx_str_t ngx_http_client_method[] = {
ngx_string("GET"),
ngx_string("HEAD"),
ngx_string("POST"),
ngx_string("PUT"),
ngx_string("DELETE"),
ngx_string("MKCOL"),
ngx_string("COPY"),
ngx_string("MOVE"),
ngx_string("OPTIONS"),
ngx_string("PROPFIND"),
ngx_string("PROPPATCH"),
ngx_string("LOCK"),
ngx_string("UNLOCK"),
ngx_string("PATCH"),
ngx_string("TRACE")
};
static ngx_str_t ngx_http_client_version[] = {
ngx_string("HTTP/0.9"), /* not support, will not use */
ngx_string("HTTP/1.0"),
ngx_string("HTTP/1.1"),
ngx_string("HTTP/2.0")
};
#define NGX_HTTP_CLIENT_CONNECTION_CLOSE 1
#define NGX_HTTP_CLIENT_CONNECTION_KEEP_ALIVE 2
#define NGX_HTTP_CLIENT_CONNECTION_UPGRADE 3
typedef void (*ngx_http_client_fill_header_pt)(ngx_http_request_t *r,
ngx_str_t *value);
typedef struct {
ngx_str_t name;
ngx_http_client_fill_header_pt handler;
} ngx_http_client_fill_header_t;
typedef struct {
ngx_hash_t headers_in_hash;
/* wait for response header timeout */
ngx_msec_t header_timeout;
size_t header_buffer_size;
size_t body_buffer_size;
} ngx_http_client_conf_t;
ngx_http_header_t ngx_http_client_headers_in[] = {
{ ngx_string("Status"), offsetof(ngx_http_client_headers_in_t, status),
ngx_http_client_process_header_line },
{ ngx_string("Date"), offsetof(ngx_http_client_headers_in_t, date),
ngx_http_client_process_header_line },
{ ngx_string("Server"), offsetof(ngx_http_client_headers_in_t, server),
ngx_http_client_process_header_line },
{ ngx_string("Connection"),
offsetof(ngx_http_client_headers_in_t, connection),
ngx_http_client_process_connection },
{ ngx_string("Expires"), offsetof(ngx_http_client_headers_in_t, expires),
ngx_http_client_process_header_line },
{ ngx_string("ETag"), offsetof(ngx_http_client_headers_in_t, etag),
ngx_http_client_process_header_line },
{ ngx_string("X-Accel-Expires"),
offsetof(ngx_http_client_headers_in_t, x_accel_expires),
ngx_http_client_process_header_line },
{ ngx_string("X-Accel-Redirect"),
offsetof(ngx_http_client_headers_in_t, x_accel_redirect),
ngx_http_client_process_header_line },
{ ngx_string("X-Accel-Limit-Rate"),
offsetof(ngx_http_client_headers_in_t, x_accel_limit_rate),
ngx_http_client_process_header_line },
{ ngx_string("Content-Type"),
offsetof(ngx_http_client_headers_in_t, content_type),
ngx_http_client_process_header_line },
{ ngx_string("Content-Length"),
offsetof(ngx_http_client_headers_in_t, content_length),
ngx_http_client_process_content_length },
{ ngx_string("Last-Modified"),
offsetof(ngx_http_client_headers_in_t, last_modified),
ngx_http_client_process_header_line },
{ ngx_string("Location"), offsetof(ngx_http_client_headers_in_t, location),
ngx_http_client_process_header_line },
{ ngx_string("Accept-Ranges"),
offsetof(ngx_http_client_headers_in_t, accept_ranges),
ngx_http_client_process_header_line },
{ ngx_string("WWW-Authenticate"),
offsetof(ngx_http_client_headers_in_t, www_authenticate),
ngx_http_client_process_header_line },
{ ngx_string("Transfer-Encoding"),
offsetof(ngx_http_client_headers_in_t, transfer_encoding),
ngx_http_client_process_transfer_encoding },
#if (NGX_HTTP_GZIP)
{ ngx_string("Content-Encoding"),
offsetof(ngx_http_client_headers_in_t, content_encoding),
ngx_http_client_process_header_line },
#endif
{ ngx_null_string, 0, NULL }
};
ngx_http_client_fill_header_t ngx_http_client_default_header[] = {
{ ngx_string("Host"), ngx_http_client_host },
{ ngx_string("User-Agent"), ngx_http_client_user_agent },
{ ngx_string("Connection"), ngx_http_client_connection },
{ ngx_string("Accept"), ngx_http_client_accept },
{ ngx_string("Date"), ngx_http_client_date },
{ ngx_null_string, NULL }
};
static ngx_command_t ngx_http_client_commands[] = {
{ ngx_string("header_timeout"),
NGX_MAIN_CONF|NGX_DIRECT_CONF|NGX_CONF_TAKE1,
ngx_conf_set_msec_slot,
0,
offsetof(ngx_http_client_conf_t, header_timeout),
NULL },
{ ngx_string("header_buffer_size"),
NGX_MAIN_CONF|NGX_DIRECT_CONF|NGX_CONF_TAKE1,
ngx_conf_set_size_slot,
0,
offsetof(ngx_http_client_conf_t, header_buffer_size),
NULL },
{ ngx_string("body_buffer_size"),
NGX_MAIN_CONF|NGX_DIRECT_CONF|NGX_CONF_TAKE1,
ngx_conf_set_size_slot,
0,
offsetof(ngx_http_client_conf_t, body_buffer_size),
NULL },
ngx_null_command
};
static ngx_core_module_t ngx_http_client_module_ctx = {
ngx_string("http_client"),
ngx_http_client_module_create_conf,
ngx_http_client_module_init_conf
};
ngx_module_t ngx_http_client_module = {
NGX_MODULE_V1,
&ngx_http_client_module_ctx, /* module context */
ngx_http_client_commands, /* module directives */
NGX_CORE_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 void *
ngx_http_client_module_create_conf(ngx_cycle_t *cycle)
{
ngx_http_client_conf_t *hccf;
hccf = ngx_pcalloc(cycle->pool, sizeof(ngx_http_client_conf_t));
if (hccf == NULL) {
return NULL;
}
hccf->header_timeout = NGX_CONF_UNSET_MSEC;
hccf->header_buffer_size = NGX_CONF_UNSET_SIZE;
hccf->body_buffer_size = NGX_CONF_UNSET_SIZE;
return hccf;
}
static char *
ngx_http_client_module_init_conf(ngx_cycle_t *cycle, void *conf)
{
ngx_http_client_conf_t *hccf = conf;
ngx_array_t headers_in;
ngx_hash_key_t *hk;
ngx_hash_init_t hash;
ngx_http_header_t *header;
/* upstream_headers_in_hash */
if (ngx_array_init(&headers_in, cycle->pool, 32, sizeof(ngx_hash_key_t))
!= NGX_OK)
{
return NGX_CONF_ERROR;
}
for (header = ngx_http_client_headers_in; header->name.len; header++) {
hk = ngx_array_push(&headers_in);
if (hk == NULL) {
return NGX_CONF_ERROR;
}
hk->key = header->name;
hk->key_hash = ngx_hash_key_lc(header->name.data, header->name.len);
hk->value = header;
}
hash.hash = &hccf->headers_in_hash;
hash.key = ngx_hash_key_lc;
hash.max_size = 512;
hash.bucket_size = ngx_align(64, ngx_cacheline_size);
hash.name = "upstream_headers_in_hash";
hash.pool = cycle->pool;
hash.temp_pool = NULL;
if (ngx_hash_init(&hash, headers_in.elts, headers_in.nelts) != NGX_OK) {
return NGX_CONF_ERROR;
}
ngx_conf_init_msec_value(hccf->header_timeout, 10000);
ngx_conf_init_size_value(hccf->header_buffer_size, ngx_pagesize);
ngx_conf_init_size_value(hccf->body_buffer_size, ngx_pagesize);
return NGX_CONF_OK;
}
static ngx_int_t
ngx_http_client_process_header_line(ngx_http_request_t *r, ngx_table_elt_t *h,
ngx_uint_t offset)
{
ngx_table_elt_t **ph;
ngx_http_client_ctx_t *ctx;
ctx = r->ctx[0];
ph = (ngx_table_elt_t **) ((char *) &ctx->headers_in + offset);
if (*ph == NULL) {
*ph = h;
}
return NGX_OK;
}
static ngx_int_t
ngx_http_client_process_content_length(ngx_http_request_t *r,
ngx_table_elt_t *h, ngx_uint_t offset)
{
ngx_http_client_ctx_t *ctx;
ctx = r->ctx[0];
if (ctx->headers_in.content_length != NULL) {
ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
"duplicate Content-Length header, %V: %V", &h->key, &h->value);
return NGX_ERROR;
}
ctx->headers_in.content_length = h;
ctx->headers_in.content_length_n = ngx_atoof(h->value.data, h->value.len);
return NGX_OK;
}
static ngx_int_t
ngx_http_client_process_connection(ngx_http_request_t *r, ngx_table_elt_t *h,
ngx_uint_t offset)
{
ngx_http_client_ctx_t *ctx;
ctx = r->ctx[0];
ctx->headers_in.connection = h;
if (ngx_strcasestrn(h->value.data, "close", 5)) {
ctx->headers_in.connection_type = NGX_HTTP_CLIENT_CONNECTION_CLOSE;
} else if (ngx_strcasestrn(h->value.data, "keep-alive", 10)) {
ctx->headers_in.connection_type = NGX_HTTP_CLIENT_CONNECTION_KEEP_ALIVE;
} else if (ngx_strcasestrn(h->value.data, "upgrade", 7)) {
ctx->headers_in.connection_type = NGX_HTTP_CLIENT_CONNECTION_UPGRADE;
}
return NGX_OK;
}
static ngx_int_t
ngx_http_client_process_transfer_encoding(ngx_http_request_t *r,
ngx_table_elt_t *h, ngx_uint_t offset)
{
ngx_http_client_ctx_t *ctx;
ctx = r->ctx[0];
ctx->headers_in.transfer_encoding = h;
if (ngx_strlcasestrn(h->value.data, h->value.data + h->value.len,
(u_char *) "chunked", 7 - 1)
!= NULL)
{
ctx->headers_in.chunked = 1;
}
return NGX_OK;
}
static void
ngx_http_client_host(ngx_http_request_t *r, ngx_str_t *value)
{
ngx_http_client_ctx_t *ctx;
ctx = r->ctx[0];
value->data = ctx->url.host.data;
value->len = ctx->url.host.len;
}
static void
ngx_http_client_user_agent(ngx_http_request_t *r, ngx_str_t *value)
{
value->data = (u_char *) NGINX_VER;
value->len = sizeof(NGINX_VER) - 1;
}
static void
ngx_http_client_connection(ngx_http_request_t *r, ngx_str_t *value)
{
if (r->http_version < NGX_HTTP_CLIENT_VERSION_11) {
value->data = (u_char *) "close";
value->len = sizeof("close") - 1;
} else {
value->len = 0;
}
}
static void
ngx_http_client_accept(ngx_http_request_t *r, ngx_str_t *value)
{
value->data = (u_char *) "*/*";
value->len = sizeof("*/*") - 1;
}
static void
ngx_http_client_date(ngx_http_request_t *r, ngx_str_t *value)
{
value->data = ngx_cached_http_time.data;
value->len = ngx_cached_http_time.len;
}
static void
ngx_http_client_free_request(ngx_http_request_t *hcr)
{
ngx_http_client_ctx_t *ctx;
ngx_client_session_t *s;
ngx_pool_t *pool;
ngx_http_cleanup_t *cln;
if (hcr->pool == NULL) {
return;
}
ctx = hcr->ctx[0];
s = ctx->session;
if (ctx->request) {
cln = hcr->cleanup;
hcr->cleanup = NULL;
while (cln) {
if (cln->handler) {
cln->handler(cln->data);
}
cln = cln->next;
}
}
if (ctx->in) {
ngx_put_chainbufs(ctx->in);
ctx->in = NULL;
}
if (s) {
s->client_recv = NULL;
s->client_send = NULL;
s->client_closed = NULL;
s->out = NULL;
}
pool = hcr->pool;
hcr->pool = NULL;
NGX_DESTROY_POOL(pool);
}
static void
ngx_http_client_close_handler(ngx_client_session_t *s)
{
ngx_http_request_t *r;
r = s->data;
ngx_http_client_free_request(r);
}
static void
ngx_http_client_discarded_body(ngx_http_request_t *r)
{
ngx_http_client_ctx_t *ctx;
ngx_chain_t *cl;
ngx_int_t rc;
ctx = r->ctx[0];
rc = ngx_http_client_read_body(r, &cl);
if (rc == 0 || rc == NGX_ERROR) { // http client close
ngx_http_client_finalize_request(r, 1);
return;
}
// if detach, all http response receive, set keepalive
if (rc == NGX_DONE) {
ngx_http_client_finalize_request(r, 0);
return;
}
// NGX_AGAIN
if (ctx->in) { // make rbuf recycle immediately
ngx_put_chainbufs(ctx->in);
ctx->in = NULL;
}
}
static void
ngx_http_client_read_handler(ngx_client_session_t *s)
{
ngx_http_request_t *r;
ngx_http_client_ctx_t *ctx;
r = s->data;
ctx = r->ctx[0];
ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
"http client, read handler");
if (ctx->request && ctx->read_handler) {
ctx->read_handler(ctx->request, r);
} else {
ngx_http_client_discarded_body(r);
}
}
static void
ngx_http_client_process_header(ngx_client_session_t *s)
{
ngx_http_request_t *r;
ngx_http_client_ctx_t *ctx;
ngx_buf_t *b;
ngx_int_t n, rc;
ngx_table_elt_t *h;
ngx_http_header_t *hh;
ngx_http_client_conf_t *hccf;
ngx_event_t *rev;
hccf = (ngx_http_client_conf_t *) ngx_get_conf(ngx_cycle->conf_ctx,
ngx_http_client_module);
r = s->data;
ctx = r->ctx[0];
rev = r->connection->read;
b = ctx->buffer;
ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
"http client, process header");
for (;;) {
rc = ngx_http_parse_header_line(r, b, 1);
if (rc == NGX_OK) {
/* a header line has been parsed successfully */
h = ngx_list_push(&ctx->headers_in.headers);
if (h == NULL) {
goto error;
}
h->hash = r->header_hash;
h->key.len = r->header_name_end - r->header_name_start;
h->value.len = r->header_end - r->header_start;
h->key.data = ngx_pnalloc(r->pool,
h->key.len + 1 + h->value.len + 1 + h->key.len);
if (h->key.data == NULL) {
goto error;
}
h->value.data = h->key.data + h->key.len + 1;
h->lowcase_key = h->key.data + h->key.len + 1 + h->value.len + 1;
ngx_memcpy(h->key.data, r->header_name_start, h->key.len);
h->key.data[h->key.len] = '\0';
ngx_memcpy(h->value.data, r->header_start, h->value.len);
h->value.data[h->value.len] = '\0';
if (h->key.len == r->lowcase_index) {
ngx_memcpy(h->lowcase_key, r->lowcase_header, h->key.len);
} else {
ngx_strlow(h->lowcase_key, h->key.data, h->key.len);
}
hh = ngx_hash_find(&hccf->headers_in_hash, h->hash,
h->lowcase_key, h->key.len);
if (hh && hh->handler(r, h, hh->offset) != NGX_OK) {
goto error;
}
ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
"http server header: \"%V: %V\"", &h->key, &h->value);
continue;
}
if (rc == NGX_HTTP_PARSE_HEADER_DONE) {
/* a whole header has been parsed successfully */
ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
"http server header done");
/*
* if no "Server" and "Date" in header line,
* then add the special empty headers
*/
if (ctx->headers_in.server == NULL) {
h = ngx_list_push(&ctx->headers_in.headers);
if (h == NULL) {
goto error;
}
h->hash = ngx_hash(ngx_hash(ngx_hash(ngx_hash(
ngx_hash('s', 'e'), 'r'), 'v'), 'e'), 'r');
ngx_str_set(&h->key, "Server");
ngx_str_null(&h->value);
h->lowcase_key = (u_char *) "server";
}
if (ctx->headers_in.date == NULL) {
h = ngx_list_push(&ctx->headers_in.headers);
if (h == NULL) {
goto error;
}
h->hash = ngx_hash(ngx_hash(ngx_hash('d', 'a'), 't'), 'e');
ngx_str_set(&h->key, "Date");
ngx_str_null(&h->value);
h->lowcase_key = (u_char *) "date";
}
/* clear content length if response is chunked */
if (ctx->headers_in.chunked) {
ctx->headers_in.content_length_n = -1;
}
ctx->length = ctx->headers_in.content_length_n;
break;
}
if (rc == NGX_AGAIN) {
n = ngx_client_read(s, b);
if (n == NGX_ERROR || n == 0) {
ngx_log_error(NGX_LOG_ERR, r->connection->log, ngx_errno,
"http client, process header read error");
ngx_http_client_finalize_request(r, 1);
return;
}
if (n == NGX_AGAIN) {
if (!rev->timer_set) {
NGX_ADD_TIMER(rev, ctx->header_timeout,
offsetof(ngx_connection_t, number));
}
if (ngx_handle_read_event(rev, 0) != NGX_OK) {
ngx_http_client_finalize_request(r, 1);
return;
}
return;
}
/* NGX_OK */
ctx->rbytes += n;
continue;
}
/* there was error while a header line parsing */
ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
"http client, http server sent invalid header");
goto error;
}
s->client_recv = ngx_http_client_read_handler;
if (rev->timer_set) {
NGX_DEL_TIMER(rev, r->connection->number);
}
return ngx_http_client_read_handler(s);
error:
ngx_http_client_finalize_request(r, 1);
}
static void
ngx_http_client_process_status_line(ngx_client_session_t *s)
{
ngx_http_request_t *r;
ngx_http_client_ctx_t *ctx;
ngx_buf_t *b;
ngx_int_t n, rc;
ngx_event_t *rev;
r = s->data;
ctx = r->ctx[0];
rev = r->connection->read;
b = ctx->buffer;
ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
"http client, process status line");
for (;;) {
rc = ngx_http_parse_status_line(r, b, &ctx->status);
if (rc == NGX_AGAIN) {
n = ngx_client_read(s, b);
if (n == NGX_ERROR || n == 0) {
ngx_log_error(NGX_LOG_ERR, r->connection->log, ngx_errno,
"http client, process status line read error");
ngx_http_client_finalize_request(r, 1);
return;
}
if (n == NGX_AGAIN) {
if (!rev->timer_set) {
NGX_ADD_TIMER(rev, ctx->header_timeout,
offsetof(ngx_connection_t, number));
}
if (ngx_handle_read_event(rev, 0) != NGX_OK) {
ngx_http_client_finalize_request(r, 1);
return;
}
return;
}
/* NGX_OK */
ctx->rbytes += n;
continue;
}
if (rc == NGX_ERROR) {
ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
"http client, recv no valid HTTP/1.0 header");
r->http_version = NGX_HTTP_VERSION_9;
}
/* NGX_OK */
break;
}
ctx->headers_in.http_version = ctx->status.http_version;
ctx->headers_in.status_n = ctx->status.code;
ctx->headers_in.status_line.len = ctx->status.end - ctx->status.start;
ctx->headers_in.status_line.data = ngx_pcalloc(r->connection->pool,
ctx->headers_in.status_line.len);
if (ctx->headers_in.status_line.data == NULL) {
ngx_http_client_finalize_request(r, 1);
return;
}
ngx_memcpy(ctx->headers_in.status_line.data, ctx->status.start,
ctx->headers_in.status_line.len);
s->client_recv = ngx_http_client_process_header;
return ngx_http_client_process_header(s);
}
static void
ngx_http_client_wait_response_handler(ngx_client_session_t *s)
{
ngx_http_request_t *r;
ngx_http_client_ctx_t *ctx;
ngx_buf_t *b;
ngx_connection_t *c;
size_t size;
ngx_int_t n;
ngx_event_t *rev;
r = s->data;
c = r->connection;
ctx = r->ctx[0];
size = ctx->header_buffer_size;
rev = s->connection->read;
b = ctx->buffer;
ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
"http client, process response handler");
if (b == NULL) {
b = ngx_create_temp_buf(c->pool, size);
if (b == NULL) {
ngx_http_client_finalize_request(r, 1);
return;
}
ctx->buffer = b;
} else if (b->start == NULL) {
b->start = ngx_pcalloc(c->pool, size);
if (b->start == NULL) {
ngx_http_client_finalize_request(r, 1);
return;
}
b->last = b->pos = b->start;
b->end = b->last + size;
}
n = ngx_client_read(s, b);
/*
* if NGX_ERROR or no bytes read
* if ngx_client_read return NGX_ERROR, s will reconnect
* if 0, ngx_http_client_wait_response_handler
* will called next read event triggered
*/
if (n == NGX_ERROR || n == 0) {
ngx_log_error(NGX_LOG_ERR, r->connection->log, ngx_errno,
"http client, process response handler read error, rc :%i", n);
ngx_http_client_finalize_request(r, 1);
return;
}
if (n == NGX_AGAIN) {
if (!rev->timer_set) {
NGX_ADD_TIMER(rev, ctx->header_timeout,
offsetof(ngx_connection_t, number));
}
if (ngx_handle_read_event(rev, 0) != NGX_OK) {
ngx_http_client_finalize_request(r, 1);
return;
}
return;
}
ctx->rbytes += n;
s->client_recv = ngx_http_client_process_status_line;
return ngx_http_client_process_status_line(s);
}
static ngx_int_t
ngx_http_client_set_url(ngx_http_request_t *r, ngx_str_t *url, ngx_log_t *log)
{
ngx_http_client_ctx_t *ctx;
ngx_client_session_t *cs;
ngx_int_t rc;
ctx = r->ctx[0];
if (ctx->session) {
ngx_log_error(NGX_LOG_INFO, log, 0, "http client, url has been set");
return NGX_OK;
}
r->request_line.data = ngx_pcalloc(r->pool, url->len);
if (r->request_line.data == NULL) {
return NGX_ERROR;
}
ngx_memcpy(r->request_line.data, url->data, url->len);
r->request_line.len = url->len;
rc = ngx_parse_request_url(&ctx->url, &r->request_line);
if (rc == NGX_ERROR) {
return NGX_ERROR;
}
/* create session */
cs = ngx_client_create(&ctx->url.host, NULL, 0, log);
if (cs == NULL) {
return NGX_ERROR;
}
cs->port = ngx_request_port(&ctx->url.scheme, &ctx->url.port);
ctx->session = cs;
cs->data = r;
return NGX_OK;
}
static ngx_int_t
ngx_http_client_add_header(ngx_http_request_t *r, ngx_str_t *header,
ngx_str_t *value)
{
ngx_http_client_ctx_t *ctx;
ngx_http_client_header_out_t *h;
ngx_map_node_t *node;
ctx = r->ctx[0];
node = ngx_map_find(&ctx->headers_out.hash, (intptr_t) header);
if (node) { // header exist
h = (ngx_http_client_header_out_t *) node;
} else { // header not exist
h = ngx_array_push(&ctx->headers_out.headers);
if (h == NULL) {
return NGX_ERROR;
}
ngx_memzero(h, sizeof(ngx_http_client_header_out_t));
h->key.data = ngx_pcalloc(r->pool, header->len);
if (h->key.data == NULL) {
return NGX_ERROR;
}
ngx_memcpy(h->key.data, header->data, header->len);
h->key.len = header->len;
h->node.raw_key = (intptr_t) &h->key;
ngx_map_insert(&ctx->headers_out.hash, &h->node, 1);
}
if (value->len == 0) { // delete header
h->value.len = 0;
return NGX_OK;
}
// add or modify header
h->value.data = ngx_pcalloc(r->pool, value->len);
if (h->value.data == NULL) {
return NGX_ERROR;
}
ngx_memcpy(h->value.data, value->data, value->len);
h->value.len = value->len;
return NGX_OK;
}
static ngx_buf_t *
ngx_http_client_create_request_buf(ngx_client_session_t *s)
{
ngx_http_request_t *r;
ngx_http_client_ctx_t *ctx;
ngx_buf_t *b;
size_t len;
ngx_http_client_header_out_t *h;
ngx_uint_t i;
r = s->data;
ctx = r->ctx[0];
/* Request Line */
/* method */
len = ngx_http_client_method[r->method].len + 1; /* "GET " */
/* path + args */
++len; /* "/" */
if (ctx->url.path.len) {
/* "path" */
len += ctx->url.path.len;
}
if (ctx->url.args.len) {
/* "?args" */
++len;
len += ctx->url.args.len;
}
++len; /* " " */
/* version */
len += sizeof("HTTP/1.x") - 1 + sizeof(CRLF) - 1;
/* Request Headers */
/* User set headers */
h = ctx->headers_out.headers.elts;
for (i = 0; i < ctx->headers_out.headers.nelts; ++i, ++h) {
if (h->value.len == 0) {
continue;
}
len += h->key.len + sizeof(": ") - 1 + h->value.len + sizeof(CRLF) - 1;
}
/* Request Headers end */
len += sizeof(CRLF) - 1;
/* start fill http request */
b = ngx_create_temp_buf(r->pool, len);
if (b == NULL) {
return NULL;
}
/* method */
b->last = ngx_cpymem(b->last, ngx_http_client_method[r->method].data,
ngx_http_client_method[r->method].len);
*b->last++ = ' ';
/* path + args */
*b->last++ = '/';
if (ctx->url.path.len) {
b->last = ngx_cpymem(b->last, ctx->url.path.data, ctx->url.path.len);
}
if (ctx->url.args.len) {
*b->last++ = '?';
b->last = ngx_cpymem(b->last, ctx->url.args.data, ctx->url.args.len);
}
*b->last++ = ' ';
/* version */
b->last = ngx_cpymem(b->last,
ngx_http_client_version[r->http_version].data,
ngx_http_client_version[r->http_version].len);
*b->last++ = CR; *b->last++ = LF;
/* Request Headers */
h = ctx->headers_out.headers.elts;
for (i = 0; i < ctx->headers_out.headers.nelts; ++i, ++h) {
if (h->value.len == 0) {
continue;
}
b->last = ngx_cpymem(b->last, h->key.data, h->key.len);
*b->last++ = ':'; *b->last++ = ' ';
b->last = ngx_cpymem(b->last, h->value.data, h->value.len);
*b->last++ = CR; *b->last++ = LF;
}
/* Request Headers end */
*b->last++ = CR; *b->last++ = LF;
return b;
}
static void
ngx_http_client_send_header(ngx_client_session_t *s)
{
ngx_http_request_t *r;
ngx_http_client_ctx_t *ctx;
ngx_buf_t *b;
ngx_chain_t out;
ngx_event_t *rev;
r = s->data;
ctx = r->ctx[0];
r->connection = s->connection;
rev = r->connection->read;
ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
"http client, send header");
r->connection = s->peer.connection;
b = ngx_http_client_create_request_buf(s);
if (b == NULL) {
goto destroy;
}
b->flush = 1;
out.buf = b;
out.next = NULL;
/* send http request header */
ngx_client_write(s, &out);
/* user defined, for send body function callback */
if (ctx->request && ctx->write_handler) {
ctx->write_handler(ctx->request, r);
}
NGX_ADD_TIMER(rev, ctx->header_timeout, offsetof(ngx_connection_t, number));
return;
destroy:
ngx_http_client_finalize_request(r, 1);
}
static ngx_int_t
ngx_http_client_body_length(ngx_http_request_t *r, ngx_chain_t *cl)
{
ngx_http_client_ctx_t *ctx;
ngx_buf_t *buf;
ngx_chain_t **ll;
ngx_int_t len;
ctx = r->ctx[0];
for (ll = &ctx->in; *ll; ll = &(*ll)->next);
while (cl) {
*ll = cl;
cl = cl->next;
(*ll)->next = NULL;
if (ctx->length != -1) {
buf = (*ll)->buf;
len = ngx_min(buf->last - buf->pos, ctx->length);
ctx->length -= len;
if (ctx->length == 0) {
if (cl || buf->last - buf->pos > len) {
ngx_log_error(NGX_LOG_INFO, r->connection->log, 0,
"http client, read unexpected data");
ngx_put_chainbufs(cl);
}
return NGX_DONE;
}
}
ll = &(*ll)->next;
}
return NGX_AGAIN;
}
static ngx_int_t
ngx_http_client_body_chunked(ngx_http_request_t *r, ngx_chain_t *cl)
{
ngx_http_client_ctx_t *ctx;
ngx_http_client_conf_t *hccf;
ngx_buf_t *buf, *b;
ngx_chain_t **ll, *ln;
ngx_int_t rc;
size_t len;
ctx = r->ctx[0];
hccf = (ngx_http_client_conf_t *) ngx_get_conf(ngx_cycle->conf_ctx,
ngx_http_client_module);
for (ll = &ctx->in; *ll; ll = &(*ll)->next);
while (1) {
b = cl->buf;
rc = ngx_http_parse_chunked(r, b, &ctx->chunked);
ngx_log_debug7(NGX_LOG_DEBUG_CORE, r->connection->log, 0,
"http client, parse chunked %p %p-%p %p, rc: %d, %O %O",
b->start, b->pos, b->last, b->end,
rc, ctx->chunked.size, ctx->chunked.length);
if (rc == NGX_OK) {
/* a chunk has been parsed successfully */
while (1) {
if (*ll == NULL) {
*ll = ngx_get_chainbuf(hccf->body_buffer_size, 1);
if (*ll == NULL) {
ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
"http client, get chainbuf failed");
return NGX_ERROR;
}
}
buf = (*ll)->buf;
if (b->last - b->pos >= ctx->chunked.size) {
len = ngx_min(buf->end - buf->last, ctx->chunked.size);
} else {
len = ngx_min(buf->end - buf->last, b->last - b->pos);
}
buf->last = ngx_cpymem(buf->last, b->pos, len);
b->pos += len;
ctx->chunked.size -= len;
if (buf->last == buf->end) {
ll = &(*ll)->next;
}
if (b->pos == b->last) { // current cl read over
ln = cl;
cl = cl->next;
ngx_put_chainbuf(ln);
if (cl == NULL) {
return NGX_AGAIN;
}
b = cl->buf;
}
if (ctx->chunked.size == 0) { // current chunk read over
break;
}
}
ngx_log_debug7(NGX_LOG_DEBUG_CORE, r->connection->log, 0,
"http client, parse done %p %p-%p %p, rc: %d, %O %O",
b->start, b->pos, b->last, b->end,
rc, ctx->chunked.size, ctx->chunked.length);
continue;
}
if (rc == NGX_AGAIN) {
ln = cl;
cl = cl->next;
ngx_put_chainbuf(ln);
if (cl == NULL) {
return NGX_AGAIN;
}
continue;
}
if (rc == NGX_DONE) {
if (b->pos != b->last || cl->next) {
ngx_log_error(NGX_LOG_INFO, r->connection->log, 0,
"http client, read unexpected chunked data");
}
ngx_put_chainbufs(cl);
return NGX_DONE;
}
ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
"http client, invalid chunked response");
return NGX_ERROR;
}
}
/* create and set http request */
ngx_http_request_t *
ngx_http_client_create(ngx_log_t *log, ngx_uint_t method, ngx_str_t *url,
ngx_keyval_t *headers, ngx_http_client_handler_pt send_body, void *request)
{
ngx_pool_t *pool;
ngx_http_request_t *r;
ngx_http_client_ctx_t *ctx;
ngx_http_client_conf_t *hccf;
ngx_http_client_fill_header_t *h;
ngx_str_t value;
hccf = (ngx_http_client_conf_t *) ngx_get_conf(ngx_cycle->conf_ctx,
ngx_http_client_module);
if (url == NULL) {
ngx_log_error(NGX_LOG_ERR, log, 0,
"url is NULL when create http client");
return NULL;
}
pool = NGX_CREATE_POOL(4096, ngx_cycle->log);
if (pool == NULL) {
ngx_log_error(NGX_LOG_ERR, log, 0,
"client create, create pool failed");
return NULL;
}
r = ngx_pcalloc(pool, sizeof(ngx_http_request_t));
if (r == NULL) {
ngx_log_error(NGX_LOG_ERR, log, 0,
"client create, create http request failed");
goto destroy;
}
r->pool = pool;
r->main = r;
/* create http client ctx */
r->ctx = ngx_pcalloc(pool, sizeof(void *) * 1);
if (r->ctx == NULL) {
ngx_log_error(NGX_LOG_ERR, log, 0,
"client create, create http request ctxs failed");
goto destroy;
}
ctx = ngx_pcalloc(pool, sizeof(ngx_http_client_ctx_t));
if (ctx == NULL) {
ngx_log_error(NGX_LOG_ERR, log, 0,
"client create, create http request ctx failed");
goto destroy;
}
r->ctx[0] = ctx;
/* set paras for http client */
r->method = method;
/* default version HTTP/1.1 */
r->http_version = NGX_HTTP_CLIENT_VERSION_11;
/* for send body */
ctx->request = request;
ctx->write_handler = send_body;
ctx->header_timeout = hccf->header_timeout;
ctx->header_buffer_size = hccf->header_buffer_size;
if (ngx_http_client_set_url(r, url, log) == NGX_ERROR) {
ngx_log_error(NGX_LOG_ERR, log, 0,
"client create, set url failed");
goto destroy;
}
/* headers_out */
if (ngx_array_init(&ctx->headers_out.headers, pool, 64,
sizeof(ngx_http_client_header_out_t)) != NGX_OK)
{
ngx_log_error(NGX_LOG_ERR, log, 0,
"client create, init headers out failed");
goto destroy;
}
ngx_map_init(&ctx->headers_out.hash, ngx_map_hash_str, ngx_cmp_str);
h = ngx_http_client_default_header;
while(h->name.len) {
h->handler(r, &value);
if (ngx_http_client_add_header(r, &h->name, &value) != NGX_OK) {
ngx_log_error(NGX_LOG_ERR, log, 0,
"client create, set default header %V: %V failed",
&h->name, &value);
goto destroy;
}
++h;
}
while (headers && headers->key.len) {
if (ngx_http_client_add_header(r, &headers->key, &headers->value)
!= NGX_OK)
{
ngx_log_error(NGX_LOG_ERR, log, 0,
"client create, set user header %V: %V failed",
&h->name, &value);
goto destroy;
}
++headers;
}
return r;
destroy:
NGX_DESTROY_POOL(pool);
return NULL;
}
ngx_http_cleanup_t *
ngx_http_client_cleanup_add(ngx_http_request_t *r, size_t size)
{
ngx_http_cleanup_t *cln;
r = r->main;
cln = ngx_palloc(r->pool, sizeof(ngx_http_cleanup_t));
if (cln == NULL) {
return NULL;
}
if (size) {
cln->data = ngx_palloc(r->pool, size);
if (cln->data == NULL) {
return NULL;
}
} else {
cln->data = NULL;
}
cln->handler = NULL;
cln->next = r->cleanup;
r->cleanup = cln;
return cln;
}
void
ngx_http_client_set_read_handler(ngx_http_request_t *r,
ngx_http_client_handler_pt read_handler)
{
ngx_http_client_ctx_t *ctx;
ctx = r->ctx[0];
ctx->read_handler = read_handler;
}
ngx_int_t
ngx_http_client_set_headers(ngx_http_request_t *r, ngx_keyval_t *headers)
{
while (headers && headers->key.len) {
if (ngx_http_client_add_header(r, &headers->key, &headers->value)
!= NGX_OK)
{
return NGX_ERROR;
}
++headers;
}
return NGX_OK;
}
void
ngx_http_client_set_write_handler(ngx_http_request_t *r,
ngx_http_client_handler_pt write_handler)
{
ngx_http_client_ctx_t *ctx;
ctx = r->ctx[0];
ctx->write_handler = write_handler;
}
void
ngx_http_client_set_version(ngx_http_request_t *r, ngx_uint_t version)
{
r->http_version = version;
}
void
ngx_http_client_setopt(ngx_http_request_t *r, unsigned opt, ngx_uint_t value)
{
ngx_http_client_ctx_t *ctx;
ngx_client_session_t *s;
ctx = r->ctx[0];
s = ctx->session;
switch (opt) {
case NGX_HTTP_CLIENT_OPT_CONNECT_TIMEOUT:
s->connect_timeout = value;
break;
case NGX_HTTP_CLIENT_OPT_SEND_TIMEOUT:
s->send_timeout = value;
break;
case NGX_HTTP_CLIENT_OPT_POSTPONE_OUTPUT:
s->postpone_output = value;
break;
case NGX_HTTP_CLIENT_OPT_DYNAMIC_RESOLVER:
s->dynamic_resolver = value > 0;
break;
case NGX_HTTP_CLIENT_OPT_TCP_NODELAY:
s->tcp_nodelay = value > 0;
break;
case NGX_HTTP_CLIENT_OPT_TCP_NOPUSH:
s->tcp_nopush = value > 0;
break;
case NGX_HTTP_CLIENT_OPT_HEADER_TIMEOUT:
ctx->header_timeout = value;
break;
default:
ngx_log_error(NGX_LOG_ERR, &s->log, 0,
"try to set unsupported opt %d", opt);
break;
}
}
/* send http request */
ngx_int_t
ngx_http_client_send(ngx_http_request_t *r)
{
ngx_client_session_t *s;
ngx_http_client_ctx_t *ctx;
ctx = r->ctx[0];
s = ctx->session;
/* init */
s->client_connected = ngx_http_client_send_header;
s->client_recv = ngx_http_client_wait_response_handler;
s->client_closed = ngx_http_client_close_handler;
/*
* init ctx->headers_in, headers_in use c->pool,
* reconnect will destroy and reinit ctx->headsers_in
*/
if (ngx_list_init(&ctx->headers_in.headers, r->pool, 20,
sizeof(ngx_table_elt_t))
!= NGX_OK)
{
return NGX_ERROR;
}
ctx->headers_in.content_length_n = -1;
ngx_client_connect(s);
return NGX_OK;
}
ngx_http_request_t *
ngx_http_client_get(ngx_log_t *log, ngx_str_t *url, ngx_keyval_t *headers,
void *request)
{
ngx_http_request_t *r;
r = ngx_http_client_create(log, NGX_HTTP_CLIENT_GET, url, headers,
NULL, request);
if (r == NULL) {
return NULL;
}
if (ngx_http_client_send(r) == NGX_ERROR) {
return NULL;
}
return r;
}
ngx_http_request_t *
ngx_http_client_head(ngx_log_t *log, ngx_str_t *url, ngx_keyval_t *headers,
void *request)
{
ngx_http_request_t *r;
r = ngx_http_client_create(log, NGX_HTTP_CLIENT_HEAD, url, headers,
NULL, request);
if (r == NULL) {
return NULL;
}
if (ngx_http_client_send(r) == NGX_ERROR) {
return NULL;
}
return r;
}
ngx_http_request_t *
ngx_http_client_post(ngx_log_t *log, ngx_str_t *url, ngx_keyval_t *headers,
ngx_http_client_handler_pt send_body, void *request)
{
ngx_http_request_t *r;
r = ngx_http_client_create(log, NGX_HTTP_CLIENT_POST, url, headers,
send_body, request);
if (r == NULL) {
return NULL;
}
if (ngx_http_client_send(r) == NGX_ERROR) {
return NULL;
}
return r;
}
/* get response */
ngx_uint_t
ngx_http_client_http_version(ngx_http_request_t *r)
{
ngx_http_client_ctx_t *ctx;
ctx = r->ctx[0];
return ctx->headers_in.http_version;
}
ngx_uint_t
ngx_http_client_status_code(ngx_http_request_t *r)
{
ngx_http_client_ctx_t *ctx;
ctx = r->ctx[0];
return ctx->headers_in.status_n;
}
ngx_str_t *
ngx_http_client_header_in(ngx_http_request_t *r, ngx_str_t *key)
{
ngx_http_client_ctx_t *ctx;
ngx_table_elt_t *h;
ngx_list_part_t *part;
ngx_uint_t i;
ctx = r->ctx[0];
part = &ctx->headers_in.headers.part;
h = part->elts;
for (i = 0; /* void */; ++i) {
if (i >= part->nelts) {
if (part->next == NULL) {
break;
}
part = part->next;
h = part->elts;
i = 0;
}
if (h[i].hash == 0) {
continue;
}
if (h[i].key.len == key->len
&& ngx_strncasecmp(h[i].key.data, key->data, key->len) == 0)
{
return &h[i].value;
}
}
return NULL;
}
ngx_int_t
ngx_http_client_read_body(ngx_http_request_t *r, ngx_chain_t **in)
{
ngx_client_session_t *s;
ngx_http_client_ctx_t *ctx;
ngx_http_client_conf_t *hccf;
ngx_buf_t *buf;
ngx_int_t n, rc;
ngx_event_t *rev;
ngx_chain_t *cl, **ll, *ln;
ctx = r->ctx[0];
s = ctx->session;
rev = r->connection->read;
hccf = (ngx_http_client_conf_t *) ngx_get_conf(ngx_cycle->conf_ctx,
ngx_http_client_module);
// recycle bufs
while (ctx->in) {
cl = ctx->in;
ctx->in = cl->next;
if (cl->buf->pos != cl->buf->last) {
break;
}
ngx_put_chainbuf(cl);
}
cl = NULL;
ll = &cl;
// part of body will read with header
if (ctx->buffer->last != ctx->buffer->pos) {
ln = ngx_get_chainbuf(hccf->body_buffer_size, 0);
if (ln == NULL) {
ngx_log_error(NGX_LOG_INFO, r->connection->log, 0,
"http client, alloc chainbuf without buffer failed");
return NGX_ERROR;
}
buf = ln->buf;
buf->pos = ctx->buffer->pos;
buf->last = ctx->buffer->last;
ctx->buffer->pos = ctx->buffer->last;
*ll = ln;
ll = &(*ll)->next;
}
// start read
while (1) {
ln = ngx_get_chainbuf(hccf->body_buffer_size, 1);
if (ln == NULL) {
ngx_log_error(NGX_LOG_INFO, r->connection->log, 0,
"http client, alloc chainbuf with buffer failed");
return NGX_ERROR;
}
buf = ln->buf;
n = ngx_client_read(s, buf);
if (n == 0) {
ngx_put_chainbuf(ln);
ngx_put_chainbufs(cl);
ngx_log_error(NGX_LOG_INFO, r->connection->log, 0,
"http client, server close");
return 0;
}
if (n == NGX_ERROR) {
ngx_put_chainbuf(ln);
ngx_put_chainbufs(cl);
ngx_log_error(NGX_LOG_ERR, r->connection->log, ngx_errno,
"http client, server error close");
return NGX_ERROR;
}
if (n == NGX_AGAIN) { // all data in socket has been read
ngx_put_chainbuf(ln);
if (ngx_handle_read_event(rev, 0) != NGX_OK) {
ngx_log_error(NGX_LOG_ERR, r->connection->log, ngx_errno,
"http client, handle read event error");
return NGX_ERROR;
}
break;
}
*ll = ln;
ll = &(*ll)->next;
ctx->rbytes += n;
}
if (ctx->headers_in.chunked) {
rc = ngx_http_client_body_chunked(r, cl);
} else {
rc = ngx_http_client_body_length(r, cl);
}
if (rc == NGX_ERROR) { // parse chunked error
return NGX_ERROR;
}
*in = ctx->in;
if (rc == NGX_DONE) { // all body has been read
return NGX_DONE;
}
return NGX_AGAIN;
}
off_t
ngx_http_client_rbytes(ngx_http_request_t *r)
{
ngx_http_client_ctx_t *ctx;
ctx = r->ctx[0];
return ctx->rbytes;
}
off_t
ngx_http_client_wbytes(ngx_http_request_t *r)
{
ngx_http_client_ctx_t *ctx;
ctx = r->ctx[0];
return ctx->wbytes;
}
/* end request */
void
ngx_http_client_detach(ngx_http_request_t *r)
{
ngx_http_client_ctx_t *ctx;
if (r == NULL) {
return;
}
ctx = r->ctx[0];
ctx->request = NULL;
if (r->connection) {
ngx_post_event(r->connection->read, &ngx_posted_events);
}
}
void
ngx_http_client_finalize_request(ngx_http_request_t *r, ngx_flag_t closed)
{
ngx_http_client_ctx_t *ctx;
ngx_client_session_t *s;
ctx = r->ctx[0];
s = ctx->session;
ngx_http_client_free_request(r);
if (closed) {
ngx_client_close(s);
} else {
ngx_client_set_keepalive(s);
}
}