123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558 |
- /* libanode: the Anode C reference implementation
- * Copyright (C) 2009-2010 Adam Ierymenko <[email protected]>
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>. */
- #include <stdio.h>
- #include <netinet/in.h>
- #include <sys/socket.h>
- #include "http_client.h"
- #include "misc.h"
- #include "types.h"
- /* How much to increment read buffer at each capacity top? */
- #define ANODE_HTTP_CAPACITY_INCREMENT 4096
- static void AnodeHttpClient_close_and_fail(struct AnodeHttpClient *client)
- {
- if (client->impl.tcp_connection) {
- client->impl.transport_engine->tcp_close(client->impl.transport_engine,client->impl.tcp_connection);
- client->impl.tcp_connection = (AnodeTransportTcpConnection *)0;
- }
- client->response.data_length = 0;
- client->impl.phase = ANODE_HTTP_REQUEST_PHASE_CLOSED;
- if (client->handler)
- client->handler(client);
- }
- static void AnodeHttpClient_do_initiate_client(struct AnodeHttpClient *client)
- {
- const char *method = "";
- long l,i;
- switch(client->method) {
- case ANODE_HTTP_GET: method = "GET"; break;
- case ANODE_HTTP_HEAD: method = "HEAD"; break;
- case ANODE_HTTP_POST: method = "POST"; break;
- }
- client->impl.outbuf_len = snprintf((char *)client->impl.outbuf,sizeof(client->impl.outbuf),
- "%s %s%s%s HTTP/1.1\r\nHost: %s:%d\r\n%s",
- method,
- client->uri.path,
- ((client->uri.query[0]) ? "?" : ""),
- client->uri.query,
- client->uri.host,
- ((client->uri.port > 0) ? client->uri.port : 80),
- ((client->keepalive) ? "" : "Connection: close\r\n")
- );
- if (client->impl.outbuf_len >= (sizeof(client->impl.outbuf) - 2)) {
- client->response.code = ANODE_HTTP_SPECIAL_RESPONSE_HEADERS_TOO_LARGE;
- AnodeHttpClient_close_and_fail(client);
- return;
- }
- if (client->method == ANODE_HTTP_POST) {
- if ((client->data)&&(client->data_length)) {
- client->impl.outbuf_len += snprintf((char *)client->impl.outbuf + client->impl.outbuf_len,sizeof(client->impl.outbuf) - client->impl.outbuf_len,
- "Content-Type: %s\r\n",
- (client->data_content_type ? client->data_content_type : "application/x-www-form-urlencoded")
- );
- if (client->impl.outbuf_len >= (sizeof(client->impl.outbuf) - 2)) {
- client->response.code = ANODE_HTTP_SPECIAL_RESPONSE_HEADERS_TOO_LARGE;
- AnodeHttpClient_close_and_fail(client);
- return;
- }
- client->impl.outbuf_len += snprintf((char *)client->impl.outbuf + client->impl.outbuf_len,sizeof(client->impl.outbuf) - client->impl.outbuf_len,
- "Content-Length: %u\r\n",
- client->data_length
- );
- if (client->impl.outbuf_len >= (sizeof(client->impl.outbuf) - 2)) {
- client->response.code = ANODE_HTTP_SPECIAL_RESPONSE_HEADERS_TOO_LARGE;
- AnodeHttpClient_close_and_fail(client);
- return;
- }
- } else {
- client->impl.outbuf_len += snprintf((char *)client->impl.outbuf + client->impl.outbuf_len,sizeof(client->impl.outbuf) - client->impl.outbuf_len,
- "Content-Length: 0\r\n"
- );
- if (client->impl.outbuf_len >= (sizeof(client->impl.outbuf) - 2)) {
- client->response.code = ANODE_HTTP_SPECIAL_RESPONSE_HEADERS_TOO_LARGE;
- AnodeHttpClient_close_and_fail(client);
- return;
- }
- }
- }
- l = AnodeDictionary_write(&(client->headers),(char *)client->impl.outbuf + client->impl.outbuf_len,(long)(sizeof(client->impl.outbuf) - client->impl.outbuf_len - 2),"\r\n",": ");
- if (l < 0) {
- client->response.code = ANODE_HTTP_SPECIAL_RESPONSE_HEADERS_TOO_LARGE;
- AnodeHttpClient_close_and_fail(client);
- return;
- }
- client->impl.outbuf_len += (unsigned int)l;
- if (client->impl.outbuf_len >= (sizeof(client->impl.outbuf) - 2)) { /* sanity check */
- client->response.code = ANODE_HTTP_SPECIAL_RESPONSE_HEADERS_TOO_LARGE;
- AnodeHttpClient_close_and_fail(client);
- return;
- }
- client->impl.outbuf[client->impl.outbuf_len++] = '\r';
- client->impl.outbuf[client->impl.outbuf_len++] = '\n';
- if ((client->method == ANODE_HTTP_POST)&&(client->data)&&(client->data_length)) {
- i = sizeof(client->impl.outbuf) - client->impl.outbuf_len;
- if (i > client->data_length)
- i = client->data_length;
- Anode_memcpy((client->impl.outbuf + client->impl.outbuf_len),client->data,i);
- client->impl.request_data_ptr += i;
- client->impl.outbuf_len += i;
- }
- client->impl.phase = ANODE_HTTP_REQUEST_PHASE_SEND;
- client->impl.transport_engine->tcp_start_writing(client->impl.transport_engine,client->impl.tcp_connection);
- }
- static void AnodeHttpClient_tcp_outgoing_connect_handler(
- AnodeTransportEngine *transport,
- AnodeTransportTcpConnection *connection,
- int error_code)
- {
- struct AnodeHttpClient *client;
- if (!(client = (struct AnodeHttpClient *)(connection->ptr)))
- return;
- if ((client->impl.phase == ANODE_HTTP_REQUEST_PHASE_CONNECT)&&(!client->impl.freed)) {
- if (error_code) {
- client->response.code = ANODE_HTTP_SPECIAL_RESPONSE_CONNECT_FAILED;
- AnodeHttpClient_close_and_fail(client);
- } else {
- client->impl.tcp_connection = connection;
- AnodeHttpClient_do_initiate_client(client);
- }
- } else transport->tcp_close(transport,connection);
- }
- static void AnodeHttpClient_tcp_connection_terminated_handler(
- AnodeTransportEngine *transport,
- AnodeTransportTcpConnection *connection,
- int error_code)
- {
- struct AnodeHttpClient *client;
- if (!(client = (struct AnodeHttpClient *)(connection->ptr)))
- return;
- if (client->impl.freed)
- return;
- client->response.data_length = 0;
- client->impl.tcp_connection = (AnodeTransportTcpConnection *)0;
- if ((client->impl.phase != ANODE_HTTP_REQUEST_PHASE_KEEPALIVE)&&(client->impl.phase != ANODE_HTTP_REQUEST_PHASE_CLOSED)) {
- client->response.code = ANODE_HTTP_SPECIAL_RESPONSE_SERVER_CLOSED_CONNECTION;
- client->impl.phase = ANODE_HTTP_REQUEST_PHASE_CLOSED;
- AnodeHttpClient_close_and_fail(client);
- } else client->impl.phase = ANODE_HTTP_REQUEST_PHASE_CLOSED;
- }
- static void AnodeHttpClient_tcp_receive_handler(
- AnodeTransportEngine *transport,
- AnodeTransportTcpConnection *connection,
- void *data,
- unsigned int data_length)
- {
- struct AnodeHttpClient *client;
- char *p1,*p2;
- unsigned int i;
- long l;
- if (!(client = (struct AnodeHttpClient *)(connection->ptr)))
- return;
- if (client->impl.freed) {
- transport->tcp_close(transport,connection);
- return;
- }
- if (!client->response.data)
- client->response.data = malloc(client->impl.response_data_capacity = ANODE_HTTP_CAPACITY_INCREMENT);
- i = 0;
- while (i < data_length) {
- switch(client->impl.read_mode) {
- case ANODE_HTTP_READ_MODE_WAITING:
- for(;i<data_length;++i) {
- if (((const char *)data)[i] == '\n') {
- ((char *)client->response.data)[client->response.data_length] = (char)0;
- client->response.data_length = 0;
- p1 = (char *)Anode_strchr((char *)client->response.data,' ');
- if (!p1)
- p1 = (char *)Anode_strchr((char *)client->response.data,'\t');
- if (p1) {
- while ((*p1 == ' ')||(*p1 == '\t')) ++p1;
- if (!*p1) {
- client->response.code = ANODE_HTTP_SPECIAL_RESPONSE_INVALID_RESPONSE;
- AnodeHttpClient_close_and_fail(client);
- return;
- }
- p2 = p1 + 1;
- while (*p2) {
- if ((*p2 == ' ')||(*p2 == '\t')||(*p2 == '\r')||(*p2 == '\n')) {
- *p2 = (char)0;
- break;
- } else ++p2;
- }
- client->response.code = (int)strtol(p1,(char **)0,10);
- client->impl.read_mode = ANODE_HTTP_READ_MODE_HEADERS;
- ++i; break; /* Exit inner for() */
- }
- } else {
- ((char *)client->response.data)[client->response.data_length++] = ((const char *)data)[i];
- if (client->response.data_length >= client->impl.response_data_capacity)
- client->response.data = realloc(client->response.data,client->impl.response_data_capacity += ANODE_HTTP_CAPACITY_INCREMENT);
- }
- }
- break;
- case ANODE_HTTP_READ_MODE_HEADERS:
- case ANODE_HTTP_READ_MODE_CHUNKED_FOOTER:
- for(;i<data_length;++i) {
- if (((const char *)data)[i] == '\n') {
- client->impl.header_line_buf[client->impl.header_line_buf_ptr] = (char)0;
- client->impl.header_line_buf_ptr = 0;
- if ((!client->impl.header_line_buf[0])||((client->impl.header_line_buf[0] == '\r')&&(!client->impl.header_line_buf[1]))) {
- /* If the line is empty (or is empty with \r\n as the
- * line terminator), we're at the end. */
- if (client->impl.read_mode == ANODE_HTTP_READ_MODE_CHUNKED_FOOTER) {
- /* If this is a chunked footer, we finally end the
- * chunked response. */
- client->impl.read_mode = ANODE_HTTP_READ_MODE_WAITING;
- if (client->keepalive)
- client->impl.phase = ANODE_HTTP_REQUEST_PHASE_KEEPALIVE;
- else {
- client->impl.transport_engine->tcp_close(client->impl.transport_engine,client->impl.tcp_connection);
- client->impl.tcp_connection = (AnodeTransportTcpConnection *)0;
- client->impl.phase = ANODE_HTTP_REQUEST_PHASE_CLOSED;
- }
- if (client->handler)
- client->handler(client);
- if (client->impl.freed)
- return;
- } else {
- /* Otherwise, this is a regular header block */
- if (client->response.code == 100) {
- /* Ignore 100 Continue messages */
- client->impl.read_mode = ANODE_HTTP_READ_MODE_WAITING;
- ++i; break; /* Exit inner for() */
- } else if ((client->response.code == 200)&&(client->method != ANODE_HTTP_HEAD)) {
- /* Other messages get their headers parsed to determine
- * how to read them. */
- p1 = (char *)AnodeDictionary_get(&(client->response.headers),"transfer-encoding");
- if ((p1)&&(Anode_strcaseeq(p1,"chunked"))) {
- /* Chunked encoding enters chunked mode */
- client->impl.header_line_buf_ptr = 0;
- client->impl.read_mode = ANODE_HTTP_READ_MODE_CHUNKED_CHUNK_SIZE;
- ++i; break; /* Exit inner for() */
- } else {
- /* Else we must have a Content-Length header */
- p1 = (char *)AnodeDictionary_get(&(client->response.headers),"content-length");
- if (!p1) {
- /* No chunked or content length is not supported */
- client->response.code = ANODE_HTTP_SPECIAL_RESPONSE_INVALID_RESPONSE;
- AnodeHttpClient_close_and_fail(client);
- return;
- } else {
- /* Enter block read mode with content length */
- l = strtol(p1,(char **)0,10);
- if (l <= 0) {
- /* Zero length data is all done... */
- client->impl.expecting_response_length = 0;
- client->impl.read_mode = ANODE_HTTP_READ_MODE_WAITING;
- if (client->keepalive)
- client->impl.phase = ANODE_HTTP_REQUEST_PHASE_KEEPALIVE;
- else {
- client->impl.transport_engine->tcp_close(client->impl.transport_engine,client->impl.tcp_connection);
- client->impl.tcp_connection = (AnodeTransportTcpConnection *)0;
- client->impl.phase = ANODE_HTTP_REQUEST_PHASE_CLOSED;
- }
- if (client->handler)
- client->handler(client);
- if (client->impl.freed)
- return;
- ++i; break; /* Exit inner for() */
- } else {
- /* Else start reading... */
- client->impl.expecting_response_length = (unsigned int)l;
- client->impl.read_mode = ANODE_HTTP_READ_MODE_BLOCK;
- ++i; break; /* Exit inner for() */
- }
- }
- }
- } else {
- /* HEAD clients or non-200 codes get headers only */
- client->impl.expecting_response_length = 0;
- client->impl.read_mode = ANODE_HTTP_READ_MODE_WAITING;
- if (client->keepalive)
- client->impl.phase = ANODE_HTTP_REQUEST_PHASE_KEEPALIVE;
- else {
- client->impl.transport_engine->tcp_close(client->impl.transport_engine,client->impl.tcp_connection);
- client->impl.tcp_connection = (AnodeTransportTcpConnection *)0;
- client->impl.phase = ANODE_HTTP_REQUEST_PHASE_CLOSED;
- }
- if (client->handler)
- client->handler(client);
- if (client->impl.freed)
- return;
- ++i; break; /* Exit inner for() */
- }
- }
- } else {
- /* Otherwise this is another header, add to dictionary */
- AnodeDictionary_read(
- &(client->response.headers),
- client->impl.header_line_buf,
- "\r\n",
- ": \t",
- "",
- (char)0,
- 1,
- 1
- );
- }
- } else {
- client->impl.header_line_buf[client->impl.header_line_buf_ptr++] = ((const char *)data)[i];
- if (client->impl.header_line_buf_ptr >= sizeof(client->impl.header_line_buf)) {
- client->response.code = ANODE_HTTP_SPECIAL_RESPONSE_INVALID_RESPONSE;
- AnodeHttpClient_close_and_fail(client);
- return;
- }
- }
- }
- break;
- case ANODE_HTTP_READ_MODE_BLOCK:
- if ((client->response.data_length + client->impl.expecting_response_length) > client->impl.response_data_capacity)
- client->response.data = realloc(client->response.data,client->impl.response_data_capacity = (client->response.data_length + client->impl.expecting_response_length));
- for(;((i<data_length)&&(client->impl.expecting_response_length));++i) {
- ((char *)client->response.data)[client->response.data_length++] = ((const char *)data)[i];
- --client->impl.expecting_response_length;
- }
- if (!client->impl.expecting_response_length) {
- client->impl.read_mode = ANODE_HTTP_READ_MODE_WAITING;
- if (client->keepalive)
- client->impl.phase = ANODE_HTTP_REQUEST_PHASE_KEEPALIVE;
- else {
- client->impl.transport_engine->tcp_close(client->impl.transport_engine,client->impl.tcp_connection);
- client->impl.tcp_connection = (AnodeTransportTcpConnection *)0;
- client->impl.phase = ANODE_HTTP_REQUEST_PHASE_CLOSED;
- }
- if (client->handler)
- client->handler(client);
- if (client->impl.freed)
- return;
- }
- break;
- case ANODE_HTTP_READ_MODE_CHUNKED_CHUNK_SIZE:
- for(;i<data_length;++i) {
- if (((const char *)data)[i] == '\n') {
- client->impl.header_line_buf[client->impl.header_line_buf_ptr] = (char)0;
- client->impl.header_line_buf_ptr = 0;
- p1 = client->impl.header_line_buf;
- while (*p1) {
- if ((*p1 == ';')||(*p1 == ' ')||(*p1 == '\r')||(*p1 == '\n')||(*p1 == '\t')) {
- *p1 = (char)0;
- break;
- } else ++p1;
- }
- if (client->impl.header_line_buf[0]) {
- l = strtol(client->impl.header_line_buf,(char **)0,16);
- if (l <= 0) {
- /* Zero length ends chunked and enters footer mode */
- client->impl.expecting_response_length = 0;
- client->impl.read_mode = ANODE_HTTP_READ_MODE_CHUNKED_FOOTER;
- } else {
- /* Otherwise the next chunk is to be read */
- client->impl.expecting_response_length = (unsigned int)l;
- client->impl.read_mode = ANODE_HTTP_READ_MODE_CHUNKED_DATA;
- }
- ++i; break; /* Exit inner for() */
- }
- } else {
- client->impl.header_line_buf[client->impl.header_line_buf_ptr++] = ((const char *)data)[i];
- if (client->impl.header_line_buf_ptr >= sizeof(client->impl.header_line_buf)) {
- client->response.code = ANODE_HTTP_SPECIAL_RESPONSE_INVALID_RESPONSE;
- AnodeHttpClient_close_and_fail(client);
- return;
- }
- }
- }
- break;
- case ANODE_HTTP_READ_MODE_CHUNKED_DATA:
- if ((client->response.data_length + client->impl.expecting_response_length) > client->impl.response_data_capacity)
- client->response.data = realloc(client->response.data,client->impl.response_data_capacity = (client->response.data_length + client->impl.expecting_response_length));
- for(;((i<data_length)&&(client->impl.expecting_response_length));++i) {
- ((char *)client->response.data)[client->response.data_length++] = ((const char *)data)[i];
- --client->impl.expecting_response_length;
- }
- if (!client->impl.expecting_response_length)
- client->impl.read_mode = ANODE_HTTP_READ_MODE_CHUNKED_CHUNK_SIZE;
- break;
- }
- }
- }
- static void AnodeHttpClient_tcp_available_for_write_handler(
- AnodeTransportEngine *transport,
- AnodeTransportTcpConnection *connection)
- {
- struct AnodeHttpClient *client;
- unsigned int i,j;
- int n;
- if (!(client = (struct AnodeHttpClient *)(connection->ptr)))
- return;
- if (client->impl.freed) {
- transport->tcp_close(transport,connection);
- return;
- }
- if (client->impl.phase == ANODE_HTTP_REQUEST_PHASE_SEND) {
- n = client->impl.transport_engine->tcp_send(client->impl.transport_engine,client->impl.tcp_connection,(const void *)client->impl.outbuf,(int)client->impl.outbuf_len);
- if (n < 0) {
- client->response.code = ANODE_HTTP_SPECIAL_RESPONSE_SERVER_CLOSED_CONNECTION;
- AnodeHttpClient_close_and_fail(client);
- } else if (n > 0) {
- for(i=0,j=(client->impl.outbuf_len - (unsigned int)n);i<j;++i)
- client->impl.outbuf[i] = client->impl.outbuf[i + (unsigned int)n];
- client->impl.outbuf_len -= (unsigned int)n;
- if ((client->method == ANODE_HTTP_POST)&&(client->data)&&(client->data_length)) {
- i = sizeof(client->impl.outbuf) - client->impl.outbuf_len;
- j = client->data_length - client->impl.request_data_ptr;
- if (i > j)
- i = j;
- Anode_memcpy((client->impl.outbuf + client->impl.outbuf_len),client->data,i);
- client->impl.request_data_ptr += i;
- client->impl.outbuf_len += i;
- }
- if (!client->impl.outbuf_len) {
- client->impl.transport_engine->tcp_stop_writing(client->impl.transport_engine,client->impl.tcp_connection);
- client->impl.phase = ANODE_HTTP_REQUEST_PHASE_RECEIVE;
- }
- }
- } else client->impl.transport_engine->tcp_stop_writing(client->impl.transport_engine,client->impl.tcp_connection);
- }
- static void AnodeHttpClient_dns_result_handler(
- AnodeTransportEngine *transport,
- void *ptr,
- int error_code,
- const char *name,
- const AnodeTransportIpAddress *ip_addresses,
- unsigned int ip_address_count,
- const AnodeAddress *anode_address)
- {
- struct AnodeHttpClient *client;
- AnodeTransportIpEndpoint to_endpoint;
- if (!(client = (struct AnodeHttpClient *)ptr))
- return;
- if (client->impl.freed)
- return;
- if ((error_code)||(!ip_address_count)) {
- if (client->impl.phase == ANODE_HTTP_REQUEST_PHASE_RESOLVE) {
- client->response.code = ANODE_HTTP_SPECIAL_RESPONSE_DNS_RESOLVE_FAILED;
- AnodeHttpClient_close_and_fail(client);
- }
- } else {
- client->impl.phase = ANODE_HTTP_REQUEST_PHASE_CONNECT;
- Anode_memcpy(&to_endpoint.address,ip_addresses,sizeof(AnodeTransportIpAddress));
- to_endpoint.port = (client->uri.port > 0) ? client->uri.port : 80;
- client->impl.transport_engine->tcp_connect(
- client->impl.transport_engine,
- client,
- &AnodeHttpClient_tcp_outgoing_connect_handler,
- &AnodeHttpClient_tcp_connection_terminated_handler,
- &AnodeHttpClient_tcp_receive_handler,
- &AnodeHttpClient_tcp_available_for_write_handler,
- &to_endpoint);
- }
- }
- struct AnodeHttpClient *AnodeHttpClient_new(AnodeTransportEngine *transport_engine)
- {
- struct AnodeHttpClient *req = malloc(sizeof(struct AnodeHttpClient));
- Anode_zero(req,sizeof(struct AnodeHttpClient));
- AnodeDictionary_init(&(req->headers),0);
- AnodeDictionary_init(&(req->response.headers),0);
- req->impl.transport_engine = transport_engine;
- return req;
- }
- void AnodeHttpClient_send(struct AnodeHttpClient *client)
- {
- client->response.code = 0;
- client->response.data_length = 0;
- AnodeDictionary_clear(&(client->response.headers));
- client->impl.request_data_ptr = 0;
- client->impl.expecting_response_length = 0;
- client->impl.read_mode = ANODE_HTTP_READ_MODE_WAITING;
- client->impl.outbuf_len = 0;
- if (!client->impl.tcp_connection) {
- client->impl.transport_engine->dns_resolve(
- client->impl.transport_engine,
- &AnodeHttpClient_dns_result_handler,
- client,
- client->uri.host,
- ANODE_TRANSPORT_DNS_QUERY_ALWAYS,
- ANODE_TRANSPORT_DNS_QUERY_IF_NO_PREVIOUS,
- ANODE_TRANSPORT_DNS_QUERY_NEVER);
- } else AnodeHttpClient_do_initiate_client(client);
- }
- void AnodeHttpClient_free(struct AnodeHttpClient *client)
- {
- AnodeDictionary_destroy(&(client->headers));
- AnodeDictionary_destroy(&(client->response.headers));
- if (client->impl.tcp_connection) {
- client->impl.transport_engine->tcp_close(client->impl.transport_engine,client->impl.tcp_connection);
- client->impl.tcp_connection = (AnodeTransportTcpConnection *)0;
- }
- if (client->response.data)
- free(client->response.data);
- client->impl.freed = 1;
- client->impl.transport_engine->run_later(client->impl.transport_engine,client,&free);
- }
|