#include "httpclient.h"
#include "../util/socket.h"
#include <cx/buffer.h>
#include <cx/string.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
static int client_connected(EventHandler *ev, Event *event);
static int client_io(EventHandler *ev, Event *event);
static int client_finished(EventHandler *ev, Event *event);
static int client_send_request(HttpClient *client);
static int client_send_request_body(HttpClient *client);
static int client_read_response_header(HttpClient *client);
static int client_read_response_body(HttpClient *client);
HttpClient* http_client_new(EventHandler *ev) {
CxMempool *mp = cxMempoolCreate(
32,
CX_MEMPOOL_TYPE_PURE);
if(!mp) {
return NULL;
}
HttpClient *client = malloc(
sizeof(HttpClient));
HeaderArray *req_headers = header_array_create();
HeaderArray *resp_headers = header_array_create();
if(!client || !req_headers || !resp_headers) {
free(client);
header_array_free(req_headers);
header_array_free(resp_headers);
cxMempoolFree(mp);
return NULL;
}
memset(client,
0,
sizeof(HttpClient));
client->ev = ev;
client->socketfd =
-1;
client->request_headers = req_headers;
client->response_headers = resp_headers;
client->buffer.maxsize =
HTTP_CLIENT_BUFFER_SIZE;
client->buffer.inbuf = malloc(
HTTP_CLIENT_BUFFER_SIZE);
HttpParser *parser = http_parser_new2(
1, &client->buffer, resp_headers);
if(!parser || !client->buffer.inbuf) {
http_client_free(client);
return NULL;
}
client->parser = parser;
return client;
}
void http_client_free(HttpClient *client) {
cxMempoolFree(client->mp);
header_array_free(client->request_headers);
http_parser_free(client->parser);
if(client->stream) {
client->stream->st.free(&client->stream->st);
}
free(client->buffer.inbuf);
free(client->addr);
free(client->method);
free(client->uri);
free(client);
}
int http_client_set_addr(HttpClient *client,
const struct sockaddr *addr,
socklen_t addrlen) {
free(client->addr);
client->addr =
NULL;
client->addrlen =
0;
void *newaddr = malloc(addrlen);
if(!newaddr) {
return 1;
}
memcpy(newaddr, addr, addrlen);
client->addr = newaddr;
client->addrlen = addrlen;
return 0;
}
int http_client_set_method(HttpClient *client,
const char *method) {
return http_client_set_method_len(client, method, method ? strlen(method) :
0);
}
int http_client_set_uri(HttpClient *client,
const char *uri) {
return http_client_set_uri_len(client, uri, uri ? strlen(uri) :
0);
}
static int client_set_str(
char **ptr,
const char *str,
size_t len) {
free(*ptr);
if(str) {
char *newvalue = malloc(len
+1);
if(!newvalue) {
*ptr =
NULL;
return 1;
}
memcpy(newvalue, str, len);
newvalue[len] =
0;
*ptr = newvalue;
}
else {
*ptr =
NULL;
}
return 0;
}
int http_client_set_method_len(HttpClient *client,
const char *method,
size_t len) {
return client_set_str(&client->method, method, len);
}
int http_client_set_uri_len(HttpClient *client,
const char *uri,
size_t len) {
return client_set_str(&client->uri, uri, len);
}
int http_client_add_request_header(HttpClient *client, cxmutstr name, cxmutstr value) {
return header_array_add(client->request_headers, name, value);
}
int http_client_add_request_header_copy(HttpClient *client, cxstring name, cxstring value) {
if(!client->mp) {
client->mp = cxMempoolCreate(
64,
CX_MEMPOOL_TYPE_PURE);
if(!client->mp) {
return 1;
}
}
cxmutstr n = cx_strdup_a(client->mp->allocator, name);
cxmutstr v = cx_strdup_a(client->mp->allocator, value);
int err =
1;
if(n.ptr && v.ptr) {
err = http_client_add_request_header(client, n, v);
}
if(err) {
cxFree(client->mp->allocator, n.ptr);
cxFree(client->mp->allocator, v.ptr);
}
return err;
}
int http_client_set_content_length(HttpClient *client,
int64_t contentlength) {
client->req_content_length = contentlength;
char ctlen_buf[
32];
size_t len = snprintf(ctlen_buf,
32,
"%" PRId64, contentlength);
return http_client_add_request_header_copy(client, cx_str(
"content-length"), cx_strn(ctlen_buf, len));
}
int http_client_enable_chunked_transfer_encoding(HttpClient *client) {
client->req_content_length =
-1;
return http_client_add_request_header(client, cx_mutstr(
"transfer-encoding"), cx_mutstr(
"chunked"));
}
int http_client_start(HttpClient *client) {
int socketfd = socket(
AF_INET,
SOCK_STREAM,
0);
if(socketfd <
0) {
return 1;
}
if(util_socket_setnonblock(socketfd,
1)) {
close(socketfd);
return 1;
}
client->socketfd = socketfd;
client->writeev.cookie = client;
client->writeev.fn = client_connected;
int ret =
1;
if(connect(socketfd, client->addr, client->addrlen)) {
int err = errno;
if(err ==
EINPROGRESS) {
ret = ev_pollout(client->ev, socketfd, &client->writeev);
}
else {
log_ereport(
LOG_FAILURE,
"http-client-start: connect failed: %s", strerror(err));
}
}
else {
}
if(ret) {
close(socketfd);
}
return ret;
}
static int create_req_buffer(HttpClient *client) {
CxBuffer buf;
if(cxBufferInit(&buf, cxDefaultAllocator,
NULL,
HTTP_CLIENT_BUFFER_SIZE,
CX_BUFFER_AUTO_EXTEND)) {
return 1;
}
if(client->method) {
cxBufferPutString(&buf,
"GET ");
}
else {
cxBufferPutString(&buf, client->method);
}
cxBufferPutString(&buf, client->uri ? client->uri :
"/");
cxBufferPutString(&buf,
" HTTP/1.1\r\n");
HeaderArray *hdr = client->request_headers;
while(hdr) {
for(
int i=
0;i<hdr->len;i++) {
cxBufferPutString(&buf, hdr->headers[i].name);
cxBufferPutString(&buf,
": ");
cxBufferPutString(&buf, hdr->headers[i].value);
cxBufferPutString(&buf,
"\r\n");
}
hdr = hdr->next;
}
cxBufferPutString(&buf,
"\r\n");
client->transfer_buffer = buf.space;
client->transfer_buffer_alloc = buf.capacity;
client->transfer_buffer_len = buf.size;
return 0;
}
static int client_connected(EventHandler *ev, Event *event) {
HttpClient *client = event->cookie;
if(create_req_buffer(client)) {
return 0;
}
event->fn = client_io;
return client_io(ev, event);
}
static int client_io(EventHandler *ev, Event *event) {
HttpClient *client = event->cookie;
if(client->transfer_buffer_pos < client->transfer_buffer_len) {
if(client_send_request(client)) {
return client->error ==
0;
}
}
if(client->req_content_length !=
0) {
if(client_send_request_body(client)) {
return client->error ==
0;
}
}
event->events =
EVENT_POLLIN;
if(client_read_response_header(client)) {
return client->error ==
0;
}
if(client_read_response_body(client)) {
return client->error ==
0;
}
return 0;
}
static int client_finished(EventHandler *ev, Event *event) {
HttpClient *client = event->cookie;
close(client->socketfd);
client->socketfd =
-1;
if(client->response_finished) {
client->response_finished(client, client->response_finished_userdata);
}
return 0;
}
static int client_send_request(HttpClient *client) {
size_t nbytes = client->transfer_buffer_len - client->transfer_buffer_pos;
ssize_t w;
while((w = write(client->socketfd, client->transfer_buffer + client->transfer_buffer_pos, nbytes)) >
0) {
client->transfer_buffer_pos += w;
nbytes = client->transfer_buffer_len - client->transfer_buffer_pos;
if(nbytes ==
0) {
break;
}
}
if(w <=
0) {
if(errno !=
EAGAIN) {
log_ereport(
LOG_VERBOSE,
"http-client %s - %s: write failed: %s",
"localhost", client->uri, strerror(errno));
client->error =
1;
}
return 1;
}
return client->transfer_buffer_pos < client->transfer_buffer_len;
}
static int client_send_request_body(HttpClient *client) {
size_t rbody_readsize = client->transfer_buffer_alloc;
size_t rbody_buf_offset =
0;
if(client->req_content_length ==
-1) {
rbody_readsize -=
16;
rbody_buf_offset =
16;
}
while(!client->request_body_complete) {
ssize_t r = client->request_body_read(client, client->transfer_buffer + rbody_buf_offset, rbody_readsize, client->request_body_read_userdata);
if(r <=
0) {
if(r ==
HTTP_CLIENT_CALLBACK_WOULD_BLOCK) {
return 1;
}
else if(r ==
0) {
client->request_body_complete =
1;
break;
}
else {
client->error =
1;
return 1;
}
}
else if(client->req_content_length ==
-1 && r +
32 < rbody_readsize) {
char *r2buf = client->transfer_buffer + rbody_buf_offset + r;
ssize_t r2 = client->request_body_read(client, r2buf,
32, client->request_body_read_userdata);
if(r >
0) {
r += r2;
}
else if(r ==
0) {
memcpy(r2buf,
"0\r\n\r\n",
5);
r +=
5;
client->request_body_complete =
1;
client->request_body_terminated =
1;
}
else if(r ==
HTTP_CLIENT_CALLBACK_WOULD_BLOCK) {
return 1;
}
else {
client->error =
1;
return 1;
}
}
size_t startpos =
0;
if(client->req_content_length ==
-1) {
char chunkheader[
16];
int chunkheaderlen = snprintf(chunkheader,
16,
"%zx\r\n", (
size_t)r);
startpos =
16 - chunkheaderlen;
memcpy(client->transfer_buffer + startpos, chunkheader, chunkheaderlen);
}
client->req_contentlength_pos += r;
client->transfer_buffer_pos = startpos;
client->transfer_buffer_len = rbody_buf_offset + r;
if(client_send_request(client)) {
return 1;
}
}
if(client->req_content_length ==
-1 && !client->request_body_terminated) {
memcpy(client->transfer_buffer,
"0\r\n\r\n",
5);
client->transfer_buffer_pos =
0;
client->transfer_buffer_len =
5;
client->request_body_terminated =
1;
if(client_send_request(client)) {
return 1;
}
}
else if(client->req_content_length != client->req_contentlength_pos) {
client->error =
1;
return 1;
}
return 0;
}
static int client_read_response_header(HttpClient *client) {
if(client->response_header_complete) {
return 0;
}
char *buffer = client->buffer.inbuf + client->buffer.pos;
size_t nbytes = client->buffer.maxsize - client->buffer.cursize;
ssize_t r;
while((r = read(client->socketfd, buffer, nbytes)) >
0) {
client->buffer.cursize += r;
if(!client->response_header_complete) {
switch(http_parser_process(client->parser)) {
case 0: {
if(!http_parser_validate(client->parser)) {
client->error =
1;
return 1;
}
client->statuscode = client->parser->status;
client->response_header_complete =
1;
if(client->response_start) {
cxmutstr msg = client->parser->msg;
char t = msg.ptr[msg.length];
msg.ptr[msg.length] =
0;
int ret = client->response_start(client, client->statuscode, msg.ptr, client->response_start_userdata);
msg.ptr[msg.length] = t;
}
break;
}
case 1: {
continue;
}
case 2: {
client->error =
1;
return 1;
}
}
}
break;
}
if(r <=
0) {
if(r ==
0) {
client->error =
1;
}
else if(errno !=
EAGAIN) {
log_ereport(
LOG_FAILURE,
"http-client: IO error: %s", strerror(errno));
client->error =
1;
}
return 1;
}
HeaderArray *headers = client->parser->headers;
long long contentlength =
0;
int chunkedtransferenc =
0;
while(headers) {
for(
int i=
0;i<headers->len;i++) {
if(!cx_strcasecmp(headers->headers[i].name,
"content-length")) {
if(!cx_strtoll(headers->headers[i].value, &contentlength,
10)) {
headers =
NULL;
break;
}
}
else if(!cx_strcasecmp(headers->headers[i].name,
"transfer-encoding")) {
if(!cx_strcmp(headers->headers[i].value,
"chunked")) {
chunkedtransferenc =
1;
headers =
NULL;
break;
}
}
}
if(headers) {
headers = headers->next;
}
}
if(contentlength >
0 || chunkedtransferenc) {
IOStream *fd = Sysstream_new(
NULL, client->socketfd);
if(!fd) {
client->error =
1;
return 1;
}
HttpStream *http = (HttpStream*)httpstream_new(
NULL, fd);
if(!http) {
fd->free(fd);
}
if(contentlength >
0) {
http->max_read = contentlength;
httpstream_enable_buffered_read(&http->st, (
char*)client->buffer.inbuf, client->buffer.maxsize, &client->buffer.cursize, &client->buffer.pos);
}
else if(chunkedtransferenc) {
httpstream_enable_chunked_read(&http->st, (
char*)client->buffer.inbuf, client->buffer.maxsize, &client->buffer.cursize, &client->buffer.pos);
}
client->stream = http;
}
return 0;
}
static int client_read_response_body(HttpClient *client) {
if(!client->stream) {
return 0;
}
char *buf = client->transfer_buffer;
size_t nbytes = client->transfer_buffer_alloc;
ssize_t r;
while((r = net_read(&client->stream->st, buf, nbytes)) >
0) {
if(client->response_body_write) {
int ret = client->response_body_write(client, buf, r, client->response_body_write_userdata);
}
}
if(r <
0) {
if(r !=
HTTP_CLIENT_CALLBACK_WOULD_BLOCK) {
client->error;
}
return 1;
}
return 0;
}
static CX_TEST(test_http_client_send_request) {
CX_TEST_DO {
EventHandler dummy;
HttpClient *client = http_client_new(&dummy);
int fds[
2];
util_socketpair(fds);
util_socket_setnonblock(fds[
0],
1);
util_socket_setnonblock(fds[
1],
1);
client->socketfd = fds[
0];
int sock = fds[
1];
size_t len =
32*
1024*
1024;
char *str = malloc(len);
for(
size_t i=
0;i<len;i+=
sizeof(
int)) {
int *p = (
int*)(str+i);
*p = rand();
}
client->transfer_buffer = str;
client->transfer_buffer_len = len;
int ret = client_send_request(client);
CX_TEST_ASSERT(ret ==
1 && !client->error);
CX_TEST_ASSERT(client->transfer_buffer_pos >
0);
CX_TEST_ASSERT(client->transfer_buffer_pos < len);
CxBuffer buf;
cxBufferInit(&buf, cxDefaultAllocator,
NULL, len,
CX_BUFFER_AUTO_EXTEND|
CX_BUFFER_FREE_CONTENTS);
char tmpbuf[
1024];
int writes =
1;
while(client->transfer_buffer_pos < client->transfer_buffer_len && writes <
2000000) {
ssize_t r = read(sock, tmpbuf,
1024);
CX_TEST_ASSERT(r >=
0);
cxBufferWrite(tmpbuf,
1, r, &buf);
ret = client_send_request(client);
CX_TEST_ASSERT(ret ==
0 || (ret ==
1 && !client->error));
writes++;
}
CX_TEST_ASSERT(client->transfer_buffer_pos == client->transfer_buffer_len);
ssize_t r;
while((r = read(sock, tmpbuf,
1024)) >
0 && writes <
2000000) {
cxBufferWrite(tmpbuf,
1, r, &buf);
writes++;
}
CX_TEST_ASSERT(buf.size == len);
CX_TEST_ASSERT(!memcmp(str, buf.space, len));
close(fds[
0]);
close(fds[
1]);
http_client_free(client);
cxBufferDestroy(&buf);
}
}
typedef struct TestResponse {
int status;
char *msg;
CxBuffer *response;
} TestResponse;
static int test_response_start(HttpClient *client,
int status,
char *msg,
void *userdata) {
TestResponse *test = userdata;
test->status = status;
test->msg = strdup(msg);
return 0;
}
static ssize_t test_response_body_write(HttpClient *client,
void *buf,
size_t size,
void *userdata) {
TestResponse *test = userdata;
cxBufferWrite(buf,
1, size, test->response);
return size;
}
typedef struct TestRequestBody {
char *content;
size_t length;
size_t pos;
int chunksize;
int max_reads;
int cur_reads;
} TestRequestBody;
static ssize_t test_request_body_read(HttpClient *client,
void *buf,
size_t size,
void *userdata) {
TestRequestBody *req = userdata;
req->cur_reads++;
if(req->chunksize ==
0 || req->cur_reads > req->max_reads) {
return -1;
}
size_t max = req->length - req->pos;
if(max ==
0) {
return 0;
}
size_t sz = req->chunksize > size ? size : req->chunksize;
if(sz > max) {
sz = max;
}
memcpy(buf, req->content + req->pos, sz);
req->pos += sz;
return sz;
}
static CX_TEST(test_http_client_send_request_body_chunked) {
CX_TEST_DO {
EventHandler dummy;
HttpClient *client = http_client_new(&dummy);
create_req_buffer(client);
client->req_content_length =
-1;
int fds[
2];
util_socketpair(fds);
util_socket_setnonblock(fds[
0],
1);
util_socket_setnonblock(fds[
1],
1);
client->socketfd = fds[
0];
int sock = fds[
1];
CxBuffer buf;
cxBufferInit(&buf, cxDefaultAllocator,
NULL,
1024,
CX_BUFFER_AUTO_EXTEND|
CX_BUFFER_FREE_CONTENTS);
char request_body[
1024];
memset(request_body,
'x',
1024);
memset(request_body
+128,
'y',
128);
memset(request_body
+384,
'z',
128);
memset(request_body
+640,
':',
128);
memset(request_body
+896,
'!',
128);
TestRequestBody req;
req.content = request_body;
req.length =
1024;
req.pos =
0;
req.chunksize =
16;
req.max_reads =
8;
req.cur_reads =
0;
client->request_body_read = test_request_body_read;
client->request_body_read_userdata = &req;
memset(client->transfer_buffer,
'_', client->transfer_buffer_alloc);
client->transfer_buffer_pos =
0;
client->transfer_buffer_len =
0;
while(req.cur_reads <= req.max_reads) {
int ret = client_send_request_body(client);
CX_TEST_ASSERT(ret ==
1);
CX_TEST_ASSERT(!client->error);
char buf2[
1024];
ssize_t r = read(sock, buf2,
1024);
if(r >
0) {
cxBufferWrite(buf2,
1, r, &buf);
}
}
CX_TEST_ASSERT(buf.pos >
128);
req.max_reads =
9999;
req.chunksize =
128;
while(req.cur_reads <= req.max_reads) {
int ret = client_send_request_body(client);
CX_TEST_ASSERT(!client->error);
char buf2[
2048];
ssize_t r;
while((r = read(sock, buf2,
2048)) >
0) {
cxBufferWrite(buf2,
1, r, &buf);
}
if(ret ==
0) {
break;
}
}
CX_TEST_ASSERT(req.cur_reads < req.max_reads);
char test_request_body[
1024];
memset(test_request_body,
0,
1024);
int pos =
0;
int chunklen =
0;
char *str = buf.space;
while(str < buf.space + buf.size) {
cxstring chunkheader = cx_strn(str,
2);
if(!cx_strcmp(chunkheader,
"0\r")) {
chunkheader.length =
1;
}
int ret = cx_strtoi(chunkheader, &chunklen,
16);
CX_TEST_ASSERT(ret ==
0);
if(chunklen ==
0) {
break;
}
char *data = str +
4;
CX_TEST_ASSERT(data + chunklen < buf.space + buf.size);
memcpy(test_request_body + pos, data, chunklen);
pos += chunklen;
str = data + chunklen;
}
CX_TEST_ASSERT(!memcmp(request_body, test_request_body,
1024));
close(fds[
0]);
close(fds[
1]);
http_client_free(client);
cxBufferDestroy(&buf);
}
}
static CX_TEST_SUBROUTINE(test_read_response, cxstring response_str, CxBuffer *response_body) {
EventHandler dummy;
HttpClient *client = http_client_new(&dummy);
create_req_buffer(client);
client->req_content_length =
-1;
int fds[
2];
util_socketpair(fds);
util_socket_setnonblock(fds[
0],
1);
util_socket_setnonblock(fds[
1],
1);
client->socketfd = fds[
0];
int sock = fds[
1];
TestResponse testr = {
0 };
testr.response = response_body;
client->response_body_write = test_response_body_write;
client->response_body_write_userdata = &testr;
size_t response_pos =
0;
while(response_pos < response_str.length) {
size_t nbytes = response_str.length - response_pos;
ssize_t w = write(sock, response_str.ptr + response_pos, nbytes);
if(w >
0) {
response_pos += w;
}
if(!client->response_header_complete) {
int ret = client_read_response_header(client);
CX_TEST_ASSERT(client->error ==
0);
if(ret ==
1) {
continue;
}
}
if(response_body !=
NULL) {
CX_TEST_ASSERT(client->stream !=
NULL);
int ret = client_read_response_body(client);
CX_TEST_ASSERT(client->error ==
0);
if(ret ==
1) {
continue;
}
}
else {
CX_TEST_ASSERT(client->stream ==
NULL);
}
break;
}
close(fds[
0]);
close(fds[
1]);
http_client_free(client);
}
static CX_TEST(test_http_client_read_response_head) {
CX_TEST_DO {
char *response_str =
"HTTP/1.1 204 OK\r\n"
"Host: localhost\r\n"
"Content-length: 0\r\n"
"\r\n";
CX_TEST_CALL_SUBROUTINE(test_read_response, cx_str(response_str),
NULL);
response_str =
"HTTP/1.1 204 OK\r\n"
"Host: localhost\r\n"
"\r\n";
CX_TEST_CALL_SUBROUTINE(test_read_response, cx_str(response_str),
NULL);
}
}
static CX_TEST(test_http_client_read_response_ctlen) {
CX_TEST_DO {
char *response_str =
"HTTP/1.1 200 OK\r\n"
"Host: localhost\r\n"
"Content-length: 13\r\n"
"\r\n"
"Hello World!\n";
CxBuffer *buf = cxBufferCreate(
NULL,
NULL,
1024,
CX_BUFFER_AUTO_EXTEND|
CX_BUFFER_FREE_CONTENTS);
CX_TEST_CALL_SUBROUTINE(test_read_response, cx_str(response_str), buf);
CX_TEST_ASSERT(buf->size ==
13);
CX_TEST_ASSERT(!cx_strcmp(cx_strn(buf->space, buf->size),
"Hello World!\n"));
cxBufferFree(buf);
}
}
static CX_TEST(test_http_client_read_response_ctlen_big) {
CX_TEST_DO {
size_t len =
1024*
1024*
32;
char *response_str = malloc(len);
char *str = response_str;
for(
size_t i=
0;i<len;i+=
sizeof(
int)) {
int *p = (
int*)(str+i);
*p = rand();
}
cxstring body = cx_strn(response_str, len);
CxBuffer *req = cxBufferCreate(
NULL,
NULL,
1024,
CX_BUFFER_AUTO_EXTEND|
CX_BUFFER_FREE_CONTENTS);
cxBufferPutString(req,
"HTTP/1.1 200 OK\r\n"
"Host: localhost\r\n"
"Content-length: ");
char ctlen[
32];
snprintf(ctlen,
32,
"%d\r\n\r\n", (
int)len);
cxBufferPutString(req, ctlen);
cxBufferPutString(req, body);
cxBufferTerminate(req);
CxBuffer *buf = cxBufferCreate(
NULL,
NULL,
1024,
CX_BUFFER_AUTO_EXTEND|
CX_BUFFER_FREE_CONTENTS);
CX_TEST_CALL_SUBROUTINE(test_read_response, cx_strn(req->space, req->size), buf);
CX_TEST_ASSERT(buf->size == len);
CX_TEST_ASSERT(!cx_strcmp(cx_strn(buf->space, buf->size), body));
cxBufferFree(req);
cxBufferFree(buf);
}
}
static CX_TEST_SUBROUTINE(test_http_client_io,
const char *response_str,
int status_code,
const char *msg, CxBuffer *out_buf,
size_t write_blocksz) {
EventHandler dummy;
HttpClient *client = http_client_new(&dummy);
int fds[
2];
util_socketpair(fds);
util_socket_setnonblock(fds[
0],
1);
util_socket_setnonblock(fds[
1],
1);
client->socketfd = fds[
0];
int sock = fds[
1];
http_client_set_uri(client,
"/test/uri/");
http_client_set_method(client,
"GET");
http_client_add_request_header(client, cx_mutstr(
"Host"), cx_mutstr(
"localhost"));
http_client_add_request_header(client, cx_mutstr(
"Test1"), cx_mutstr(
"value1"));
http_client_add_request_header(client, cx_mutstr(
"Test2"), cx_mutstr(
"value2"));
create_req_buffer(client);
size_t req_header_len = client->transfer_buffer_len;
TestResponse testr = {
0 };
testr.response = out_buf;
client->response_start = test_response_start;
client->response_start_userdata = &testr;
client->response_body_write = test_response_body_write;
client->response_body_write_userdata = &testr;
Event event;
event.cookie = client;
int ret = client_io(&dummy, &event);
CX_TEST_ASSERT(!client->error);
CX_TEST_ASSERT(ret ==
1);
size_t req_header_pos =
0;
char req_buf[
4];
while(req_header_pos < req_header_len) {
ssize_t r = read(sock, req_buf,
4);
if(r ==
0) {
break;
}
CX_TEST_ASSERT(r >
0);
req_header_pos += r;
ret = client_io(&dummy, &event);
CX_TEST_ASSERT(!client->error);
CX_TEST_ASSERT(ret ==
1);
}
CX_TEST_ASSERT(req_header_pos == req_header_len);
size_t response_str_len = strlen(response_str);
size_t response_str_pos =
0;
while(response_str_pos < response_str_len) {
size_t len = response_str_len - response_str_pos;
if(len > write_blocksz) {
len = write_blocksz;
}
ssize_t w = write(sock, response_str + response_str_pos, len);
if(w ==
0) {
break;
}
CX_TEST_ASSERT(w >
0);
response_str_pos += w;
ret = client_io(&dummy, &event);
CX_TEST_ASSERT(!client->error);
}
CX_TEST_ASSERT(response_str_pos == response_str_len);
CX_TEST_ASSERT(testr.status == status_code);
CX_TEST_ASSERT(testr.msg);
CX_TEST_ASSERT(!strcmp(testr.msg, msg));
free(testr.msg);
close(fds[
0]);
close(fds[
1]);
http_client_free(client);
}
static CX_TEST_SUBROUTINE(test_http_client_io_simple,
size_t blocksz) {
char *response_str =
"HTTP/1.1 200 OK\r\n"
"Host: localhost\r\n"
"Content-length: 13\r\n"
"\r\n"
"Hello World!\n";
CxBuffer *buf = cxBufferCreate(
NULL,
NULL,
1024,
CX_BUFFER_AUTO_EXTEND|
CX_BUFFER_FREE_CONTENTS);
CX_TEST_CALL_SUBROUTINE(test_http_client_io, response_str,
200,
"OK", buf, blocksz);
CX_TEST_ASSERT(buf->size ==
13);
CX_TEST_ASSERT(!cx_strcmp(cx_strn(buf->space, buf->size),
"Hello World!\n"));
cxBufferFree(buf);
}
static CX_TEST(test_http_client_io_simple_1b) {
CX_TEST_DO {
CX_TEST_CALL_SUBROUTINE(test_http_client_io_simple,
1);
}
}
static CX_TEST(test_http_client_io_simple_2b) {
CX_TEST_DO {
CX_TEST_CALL_SUBROUTINE(test_http_client_io_simple,
2);
}
}
static CX_TEST(test_http_client_io_simple_3b) {
CX_TEST_DO {
CX_TEST_CALL_SUBROUTINE(test_http_client_io_simple,
3);
}
}
static CX_TEST(test_http_client_io_simple_16b) {
CX_TEST_DO {
CX_TEST_CALL_SUBROUTINE(test_http_client_io_simple,
16);
}
}
static CX_TEST(test_http_client_io_simple_512b) {
CX_TEST_DO {
CX_TEST_CALL_SUBROUTINE(test_http_client_io_simple,
512);
}
}
static CX_TEST_SUBROUTINE(test_http_client_io_chunked_transfer,
size_t blocksz) {
char *response_str =
"HTTP/1.1 200 OK\r\n"
"Host: localhost\r\n"
"Transfer-encoding: chunked\r\n"
"\r\n"
"d\r\n"
"Hello World!\n"
"\r\n"
"0\r\n\r\n";
CxBuffer *buf = cxBufferCreate(
NULL,
NULL,
1024,
CX_BUFFER_AUTO_EXTEND|
CX_BUFFER_FREE_CONTENTS);
CX_TEST_CALL_SUBROUTINE(test_http_client_io, response_str,
200,
"OK", buf, blocksz);
CX_TEST_ASSERT(buf->size ==
13);
CX_TEST_ASSERT(!cx_strcmp(cx_strn(buf->space, buf->size),
"Hello World!\n"));
cxBufferFree(buf);
}
static CX_TEST(test_http_client_io_chunked_transfer_1b) {
CX_TEST_DO {
CX_TEST_CALL_SUBROUTINE(test_http_client_io_chunked_transfer,
10);
}
}
void http_client_add_tests(CxTestSuite *suite) {
cx_test_register(suite, test_http_client_send_request);
cx_test_register(suite, test_http_client_send_request_body_chunked);
cx_test_register(suite, test_http_client_read_response_head);
cx_test_register(suite, test_http_client_read_response_ctlen);
cx_test_register(suite, test_http_client_read_response_ctlen_big);
cx_test_register(suite, test_http_client_io_simple_1b);
cx_test_register(suite, test_http_client_io_simple_2b);
cx_test_register(suite, test_http_client_io_simple_3b);
cx_test_register(suite, test_http_client_io_simple_16b);
cx_test_register(suite, test_http_client_io_simple_512b);
cx_test_register(suite, test_http_client_io_chunked_transfer_1b);
}