test-http-payload.c revision 641ab76d7defa171d2b558afcce2e1ac6e49d8b9
2454dfa32c93c20a8522c6ed42fe057baaac9f9aStephan Bosch/* Copyright (c) 2013-2016 Dovecot authors, see the included COPYING file */
25757faf029c369a8318349dafe952e2358df1d8Timo Sirainen
08d6658a4e2ec8104cd1307f6baa75fdb07a24f8Mark Washenberger#include "lib.h"
43a66a0b16299bd4f7615acd85e98bd3832c54d5Timo Sirainen#include "str.h"
ff487c974815bdaa2d05a3b834f4c2c841f4cc34Timo Sirainen#include "llist.h"
66d2db642fe24d555d113ba463e446b038d476efTimo Sirainen#include "abspath.h"
25757faf029c369a8318349dafe952e2358df1d8Timo Sirainen#include "hostpid.h"
25757faf029c369a8318349dafe952e2358df1d8Timo Sirainen#include "ioloop.h"
4ee00532a265bdfb38539d811fcd12d51210ac35Timo Sirainen#include "istream.h"
4ee00532a265bdfb38539d811fcd12d51210ac35Timo Sirainen#include "ostream.h"
b321df9603081896b70ec44635af96d674a9839aTimo Sirainen#include "iostream-temp.h"
9d75363d3fbabc2fbc2d80f06672e3ed8965804aTimo Sirainen#include "connection.h"
9d75363d3fbabc2fbc2d80f06672e3ed8965804aTimo Sirainen#include "test-common.h"
9d75363d3fbabc2fbc2d80f06672e3ed8965804aTimo Sirainen#include "http-url.h"
9d75363d3fbabc2fbc2d80f06672e3ed8965804aTimo Sirainen#include "http-request.h"
43a66a0b16299bd4f7615acd85e98bd3832c54d5Timo Sirainen#include "http-server.h"
43a66a0b16299bd4f7615acd85e98bd3832c54d5Timo Sirainen#include "http-client.h"
43a66a0b16299bd4f7615acd85e98bd3832c54d5Timo Sirainen
43a66a0b16299bd4f7615acd85e98bd3832c54d5Timo Sirainen#include <sys/types.h>
7bd72e4deca3cbf757dd1ea298486d9f3bc24226Timo Sirainen#include <sys/stat.h>
7bd72e4deca3cbf757dd1ea298486d9f3bc24226Timo Sirainen#include <sys/wait.h>
7bd72e4deca3cbf757dd1ea298486d9f3bc24226Timo Sirainen#include <signal.h>
7bd72e4deca3cbf757dd1ea298486d9f3bc24226Timo Sirainen#include <fcntl.h>
7bd72e4deca3cbf757dd1ea298486d9f3bc24226Timo Sirainen#include <unistd.h>
43a66a0b16299bd4f7615acd85e98bd3832c54d5Timo Sirainen#include <dirent.h>
43a66a0b16299bd4f7615acd85e98bd3832c54d5Timo Sirainen
43a66a0b16299bd4f7615acd85e98bd3832c54d5Timo Sirainenstatic bool debug = FALSE;
43a66a0b16299bd4f7615acd85e98bd3832c54d5Timo Sirainen
43a66a0b16299bd4f7615acd85e98bd3832c54d5Timo Sirainenstatic bool blocking = FALSE;
43a66a0b16299bd4f7615acd85e98bd3832c54d5Timo Sirainenstatic bool request_100_continue = FALSE;
5c99eaa4e3e07ee065580d163240b4ce95b66befTimo Sirainenstatic size_t read_partial = 1024;
5c99eaa4e3e07ee065580d163240b4ce95b66befTimo Sirainenstatic unsigned int test_max_pending = 200;
5c99eaa4e3e07ee065580d163240b4ce95b66befTimo Sirainen
5c99eaa4e3e07ee065580d163240b4ce95b66befTimo Sirainenstatic struct ip_addr bind_ip;
5c99eaa4e3e07ee065580d163240b4ce95b66befTimo Sirainenstatic in_port_t bind_port = 0;
5c99eaa4e3e07ee065580d163240b4ce95b66befTimo Sirainenstatic int fd_listen = -1;
5c99eaa4e3e07ee065580d163240b4ce95b66befTimo Sirainen
43a66a0b16299bd4f7615acd85e98bd3832c54d5Timo Sirainen/*
43a66a0b16299bd4f7615acd85e98bd3832c54d5Timo Sirainen * Test files
43a66a0b16299bd4f7615acd85e98bd3832c54d5Timo Sirainen */
43a66a0b16299bd4f7615acd85e98bd3832c54d5Timo Sirainen
43a66a0b16299bd4f7615acd85e98bd3832c54d5Timo Sirainenstatic ARRAY_TYPE(const_string) files;
43a66a0b16299bd4f7615acd85e98bd3832c54d5Timo Sirainenstatic pool_t files_pool;
43a66a0b16299bd4f7615acd85e98bd3832c54d5Timo Sirainen
43a66a0b16299bd4f7615acd85e98bd3832c54d5Timo Sirainenstatic void test_files_read_dir(const char *path)
43a66a0b16299bd4f7615acd85e98bd3832c54d5Timo Sirainen{
7bd72e4deca3cbf757dd1ea298486d9f3bc24226Timo Sirainen DIR *dirp;
43a66a0b16299bd4f7615acd85e98bd3832c54d5Timo Sirainen
7bd72e4deca3cbf757dd1ea298486d9f3bc24226Timo Sirainen /* open the directory */
7bd72e4deca3cbf757dd1ea298486d9f3bc24226Timo Sirainen if ((dirp = opendir(path)) == NULL) {
7bd72e4deca3cbf757dd1ea298486d9f3bc24226Timo Sirainen if (errno == ENOENT || errno == EACCES)
7bd72e4deca3cbf757dd1ea298486d9f3bc24226Timo Sirainen return;
43a66a0b16299bd4f7615acd85e98bd3832c54d5Timo Sirainen i_fatal("test files: "
43a66a0b16299bd4f7615acd85e98bd3832c54d5Timo Sirainen "failed to open directory %s: %m", path);
43a66a0b16299bd4f7615acd85e98bd3832c54d5Timo Sirainen }
43a66a0b16299bd4f7615acd85e98bd3832c54d5Timo Sirainen
43a66a0b16299bd4f7615acd85e98bd3832c54d5Timo Sirainen /* read entries */
1f1e81aab38d833d1c9cdc244c91fd762e0080d4Timo Sirainen for (;;) {
b44650b0f48a4b5f0dc240ed836833a00b643b9fTimo Sirainen const char *file;
b44650b0f48a4b5f0dc240ed836833a00b643b9fTimo Sirainen struct dirent *dp;
b44650b0f48a4b5f0dc240ed836833a00b643b9fTimo Sirainen struct stat st;
473080c7c0d25ddfdf77e7dfa0ba8f73c6c669d5Timo Sirainen
a8e132559a7ebe54c8269d79ce29fa3338c76199Timo Sirainen errno = 0;
605c40c77fc3851cb2845da1c5319e32c791592aSergey Kitov if ((dp=readdir(dirp)) == NULL)
b44650b0f48a4b5f0dc240ed836833a00b643b9fTimo Sirainen break;
605c40c77fc3851cb2845da1c5319e32c791592aSergey Kitov if (*dp->d_name == '.')
473080c7c0d25ddfdf77e7dfa0ba8f73c6c669d5Timo Sirainen continue;
430c0b0c370bebeeceba2e206be76bc134742f41Timo Sirainen
430c0b0c370bebeeceba2e206be76bc134742f41Timo Sirainen file = t_abspath_to(dp->d_name, path);
430c0b0c370bebeeceba2e206be76bc134742f41Timo Sirainen if (stat(file, &st) == 0) {
430c0b0c370bebeeceba2e206be76bc134742f41Timo Sirainen if (S_ISREG(st.st_mode)) {
430c0b0c370bebeeceba2e206be76bc134742f41Timo Sirainen file += 2; /* skip "./" */
430c0b0c370bebeeceba2e206be76bc134742f41Timo Sirainen file = p_strdup(files_pool, file);
430c0b0c370bebeeceba2e206be76bc134742f41Timo Sirainen array_append(&files, &file, 1);
ce6c2809b8a1673372a683716566d973efd2f6eeTimo Sirainen } else {
ce6c2809b8a1673372a683716566d973efd2f6eeTimo Sirainen test_files_read_dir(file);
b44650b0f48a4b5f0dc240ed836833a00b643b9fTimo Sirainen }
b44650b0f48a4b5f0dc240ed836833a00b643b9fTimo Sirainen }
6135260095e1704ed6edff9d00bdfc043c11429cTimo Sirainen }
ce6c2809b8a1673372a683716566d973efd2f6eeTimo Sirainen
ce6c2809b8a1673372a683716566d973efd2f6eeTimo Sirainen if (errno != 0)
b44650b0f48a4b5f0dc240ed836833a00b643b9fTimo Sirainen i_fatal("test files: "
6135260095e1704ed6edff9d00bdfc043c11429cTimo Sirainen "failed to read directory %s: %m", path);
b44650b0f48a4b5f0dc240ed836833a00b643b9fTimo Sirainen
b44650b0f48a4b5f0dc240ed836833a00b643b9fTimo Sirainen /* Close the directory */
b44650b0f48a4b5f0dc240ed836833a00b643b9fTimo Sirainen if (closedir(dirp) < 0)
b44650b0f48a4b5f0dc240ed836833a00b643b9fTimo Sirainen i_error("test files: "
b44650b0f48a4b5f0dc240ed836833a00b643b9fTimo Sirainen "failed to close directory %s: %m", path);
b4f2560c29dacd066ba89e782d95ceed7ac473a3Timo Sirainen}
b4f2560c29dacd066ba89e782d95ceed7ac473a3Timo Sirainen
b4f2560c29dacd066ba89e782d95ceed7ac473a3Timo Sirainenstatic void test_files_init(void)
b4f2560c29dacd066ba89e782d95ceed7ac473a3Timo Sirainen{
1479a685cdb1641783ac02ba135450929f5c2658Timo Sirainen /* initialize file array */
b4f2560c29dacd066ba89e782d95ceed7ac473a3Timo Sirainen files_pool = pool_alloconly_create
b4f2560c29dacd066ba89e782d95ceed7ac473a3Timo Sirainen (MEMPOOL_GROWING"http_server_request", 4096);
b4f2560c29dacd066ba89e782d95ceed7ac473a3Timo Sirainen p_array_init(&files, files_pool, 512);
b44650b0f48a4b5f0dc240ed836833a00b643b9fTimo Sirainen
b44650b0f48a4b5f0dc240ed836833a00b643b9fTimo Sirainen /* obtain all filenames */
ac713658d206e8d001fef7c0e36945793f2eb942Timo Sirainen test_files_read_dir(".");
49e513d090753ccbf95560b2f3a21f081a5b6c51Timo Sirainen}
b44650b0f48a4b5f0dc240ed836833a00b643b9fTimo Sirainen
9ed2951bd0bb1878a27437d7c00611b2baadd614Timo Sirainenstatic void test_files_deinit(void)
ac713658d206e8d001fef7c0e36945793f2eb942Timo Sirainen{
ac713658d206e8d001fef7c0e36945793f2eb942Timo Sirainen pool_unref(&files_pool);
ac713658d206e8d001fef7c0e36945793f2eb942Timo Sirainen}
6135260095e1704ed6edff9d00bdfc043c11429cTimo Sirainen
ac713658d206e8d001fef7c0e36945793f2eb942Timo Sirainenstatic struct istream *
b44650b0f48a4b5f0dc240ed836833a00b643b9fTimo Sirainentest_file_open(const char *path,
473080c7c0d25ddfdf77e7dfa0ba8f73c6c669d5Timo Sirainen unsigned int *status_r, const char **reason_r)
517d1e7142d57299c733b30423e35e7e1f8d01d6Timo Sirainen ATTR_NULL(2, 3)
517d1e7142d57299c733b30423e35e7e1f8d01d6Timo Sirainen{
b44650b0f48a4b5f0dc240ed836833a00b643b9fTimo Sirainen int fd;
605c40c77fc3851cb2845da1c5319e32c791592aSergey Kitov
605c40c77fc3851cb2845da1c5319e32c791592aSergey Kitov if (status_r != NULL)
446e518e4fe86ff40e33543445f4e99edf840a21Timo Sirainen *status_r = 200;
605c40c77fc3851cb2845da1c5319e32c791592aSergey Kitov if (reason_r != NULL)
446e518e4fe86ff40e33543445f4e99edf840a21Timo Sirainen *reason_r = "OK";
605c40c77fc3851cb2845da1c5319e32c791592aSergey Kitov
446e518e4fe86ff40e33543445f4e99edf840a21Timo Sirainen fd = open(path, O_RDONLY);
446e518e4fe86ff40e33543445f4e99edf840a21Timo Sirainen if (fd < 0) {
9ed2951bd0bb1878a27437d7c00611b2baadd614Timo Sirainen if (debug) {
6135260095e1704ed6edff9d00bdfc043c11429cTimo Sirainen i_debug("test files: "
446e518e4fe86ff40e33543445f4e99edf840a21Timo Sirainen "open(%s) failed: %m", path);
605c40c77fc3851cb2845da1c5319e32c791592aSergey Kitov }
d368bfd671ae6d04a69eb7f418521d49b8bbf77aTimo Sirainen switch (errno) {
605c40c77fc3851cb2845da1c5319e32c791592aSergey Kitov case EFAULT:
b44650b0f48a4b5f0dc240ed836833a00b643b9fTimo Sirainen case ENOENT:
6135260095e1704ed6edff9d00bdfc043c11429cTimo Sirainen if (status_r != NULL)
b44650b0f48a4b5f0dc240ed836833a00b643b9fTimo Sirainen *status_r = 404;
b44650b0f48a4b5f0dc240ed836833a00b643b9fTimo Sirainen if (reason_r != NULL)
b44650b0f48a4b5f0dc240ed836833a00b643b9fTimo Sirainen *reason_r = "Not Found";
473080c7c0d25ddfdf77e7dfa0ba8f73c6c669d5Timo Sirainen break;
473080c7c0d25ddfdf77e7dfa0ba8f73c6c669d5Timo Sirainen case EISDIR:
b44650b0f48a4b5f0dc240ed836833a00b643b9fTimo Sirainen case EACCES:
a3dd97fb6d92a89c3de0597fed2d4b044c7aeb84Timo Sirainen if (status_r != NULL)
a3dd97fb6d92a89c3de0597fed2d4b044c7aeb84Timo Sirainen *status_r = 403;
f968e62caa52a8924bd05ebf76ff515b5c18e17bTimo Sirainen if (reason_r != NULL)
a3dd97fb6d92a89c3de0597fed2d4b044c7aeb84Timo Sirainen *reason_r = "Forbidden";
a3dd97fb6d92a89c3de0597fed2d4b044c7aeb84Timo Sirainen break;
a3dd97fb6d92a89c3de0597fed2d4b044c7aeb84Timo Sirainen default:
a3dd97fb6d92a89c3de0597fed2d4b044c7aeb84Timo Sirainen if (status_r != NULL)
5367840b91df098e016f382960c391691c8d33ffTimo Sirainen *status_r = 500;
b44650b0f48a4b5f0dc240ed836833a00b643b9fTimo Sirainen if (reason_r != NULL)
b44650b0f48a4b5f0dc240ed836833a00b643b9fTimo Sirainen *reason_r = "Internal Server Error";
f968e62caa52a8924bd05ebf76ff515b5c18e17bTimo Sirainen }
b44650b0f48a4b5f0dc240ed836833a00b643b9fTimo Sirainen return NULL;
f968e62caa52a8924bd05ebf76ff515b5c18e17bTimo Sirainen }
448e068f2fe3904e18656d730e2b7cf0e6572fc1Aki Tuomi
448e068f2fe3904e18656d730e2b7cf0e6572fc1Aki Tuomi return i_stream_create_fd(fd, 40960, TRUE);
448e068f2fe3904e18656d730e2b7cf0e6572fc1Aki Tuomi}
f968e62caa52a8924bd05ebf76ff515b5c18e17bTimo Sirainen
a3dd97fb6d92a89c3de0597fed2d4b044c7aeb84Timo Sirainen/*
b24ffea8baa472d9b542e54ed3f9939eefd020adTimo Sirainen * Test server
b24ffea8baa472d9b542e54ed3f9939eefd020adTimo Sirainen */
b24ffea8baa472d9b542e54ed3f9939eefd020adTimo Sirainen
b24ffea8baa472d9b542e54ed3f9939eefd020adTimo Sirainenstruct client {
b24ffea8baa472d9b542e54ed3f9939eefd020adTimo Sirainen pool_t pool;
b24ffea8baa472d9b542e54ed3f9939eefd020adTimo Sirainen struct client *prev, *next;
b24ffea8baa472d9b542e54ed3f9939eefd020adTimo Sirainen
7d26aee0c0b6c0ce227ef4ae4f20fc86e2c423f2Timo Sirainen struct http_server_connection *http_conn;
7d26aee0c0b6c0ce227ef4ae4f20fc86e2c423f2Timo Sirainen};
7d26aee0c0b6c0ce227ef4ae4f20fc86e2c423f2Timo Sirainen
7d26aee0c0b6c0ce227ef4ae4f20fc86e2c423f2Timo Sirainenstruct client_request {
b24ffea8baa472d9b542e54ed3f9939eefd020adTimo Sirainen struct client *client;
6135260095e1704ed6edff9d00bdfc043c11429cTimo Sirainen struct http_server_request *server_req;
4ed1b49d815ec41a5e4b6a23d23e94b958da1923Timo Sirainen
4ed1b49d815ec41a5e4b6a23d23e94b958da1923Timo Sirainen const char *path;
a3dd97fb6d92a89c3de0597fed2d4b044c7aeb84Timo Sirainen
4ed1b49d815ec41a5e4b6a23d23e94b958da1923Timo Sirainen struct istream *payload_input;
b44650b0f48a4b5f0dc240ed836833a00b643b9fTimo Sirainen struct ostream *payload_output;
b44650b0f48a4b5f0dc240ed836833a00b643b9fTimo Sirainen struct io *io;
473080c7c0d25ddfdf77e7dfa0ba8f73c6c669d5Timo Sirainen};
473080c7c0d25ddfdf77e7dfa0ba8f73c6c669d5Timo Sirainen
9ed2951bd0bb1878a27437d7c00611b2baadd614Timo Sirainenstatic const struct http_server_callbacks http_callbacks;
9ed2951bd0bb1878a27437d7c00611b2baadd614Timo Sirainenstatic struct http_server *http_server;
9ed2951bd0bb1878a27437d7c00611b2baadd614Timo Sirainen
9ed2951bd0bb1878a27437d7c00611b2baadd614Timo Sirainenstatic struct io *io_listen;
9ed2951bd0bb1878a27437d7c00611b2baadd614Timo Sirainenstatic struct client *clients;
9ed2951bd0bb1878a27437d7c00611b2baadd614Timo Sirainen
9ed2951bd0bb1878a27437d7c00611b2baadd614Timo Sirainen/* location: /succes */
9ed2951bd0bb1878a27437d7c00611b2baadd614Timo Sirainen
9ed2951bd0bb1878a27437d7c00611b2baadd614Timo Sirainenstatic void
9ed2951bd0bb1878a27437d7c00611b2baadd614Timo Sirainenclient_handle_success_request(struct client_request *creq)
9ed2951bd0bb1878a27437d7c00611b2baadd614Timo Sirainen{
9ed2951bd0bb1878a27437d7c00611b2baadd614Timo Sirainen struct http_server_request *req = creq->server_req;
9ed2951bd0bb1878a27437d7c00611b2baadd614Timo Sirainen const struct http_request *hreq =
9ed2951bd0bb1878a27437d7c00611b2baadd614Timo Sirainen http_server_request_get(req);
9ed2951bd0bb1878a27437d7c00611b2baadd614Timo Sirainen struct http_server_response *resp;
9ed2951bd0bb1878a27437d7c00611b2baadd614Timo Sirainen
9ed2951bd0bb1878a27437d7c00611b2baadd614Timo Sirainen if (strcmp(hreq->method, "GET") != 0) {
e48d89622047bd8bbd0475b881ca9377d592f535Timo Sirainen http_server_request_fail(req,
04052d7cacaa866a3f00afb4e104fa46c04c1dd7Timo Sirainen 405, "Method Not Allowed");
25757faf029c369a8318349dafe952e2358df1d8Timo Sirainen return;
3cf67672fdc87583cb23ce088c95bb5dee60e74dTimo Sirainen }
43a66a0b16299bd4f7615acd85e98bd3832c54d5Timo Sirainen
e48d89622047bd8bbd0475b881ca9377d592f535Timo Sirainen resp = http_server_response_create(req, 200, "OK");
9ed2951bd0bb1878a27437d7c00611b2baadd614Timo Sirainen http_server_response_submit(resp);
0727e38ac12efb8963a339daf56255e2be1f29fcTimo Sirainen}
04052d7cacaa866a3f00afb4e104fa46c04c1dd7Timo Sirainen
8ff9812659728d4166df8e003a1dd3524ae8514eTimo Sirainen/* location: /download/... */
966cb0c1aa58578339cea6f79b4a423a851ab074Timo Sirainen
966cb0c1aa58578339cea6f79b4a423a851ab074Timo Sirainenstatic void
966cb0c1aa58578339cea6f79b4a423a851ab074Timo Sirainenclient_handle_download_request(
966cb0c1aa58578339cea6f79b4a423a851ab074Timo Sirainen struct client_request *creq,
d5abbb932a0a598f002da39a8b3326643b1b5efcTimo Sirainen const char *path)
04052d7cacaa866a3f00afb4e104fa46c04c1dd7Timo Sirainen{
d5abbb932a0a598f002da39a8b3326643b1b5efcTimo Sirainen struct http_server_request *req = creq->server_req;
d5abbb932a0a598f002da39a8b3326643b1b5efcTimo Sirainen const struct http_request *hreq =
04052d7cacaa866a3f00afb4e104fa46c04c1dd7Timo Sirainen http_server_request_get(req);
04052d7cacaa866a3f00afb4e104fa46c04c1dd7Timo Sirainen struct http_server_response *resp;
04052d7cacaa866a3f00afb4e104fa46c04c1dd7Timo Sirainen const char *fpath, *reason;
04052d7cacaa866a3f00afb4e104fa46c04c1dd7Timo Sirainen struct istream *fstream;
04052d7cacaa866a3f00afb4e104fa46c04c1dd7Timo Sirainen struct ostream *output;
04052d7cacaa866a3f00afb4e104fa46c04c1dd7Timo Sirainen unsigned int status;
747e77e3ab073a8e9e69c7a3e71b4593c5655d03Timo Sirainen int ret;
dd93aba1901a457346990f49c54a738947dc7128Timo Sirainen
04052d7cacaa866a3f00afb4e104fa46c04c1dd7Timo Sirainen if (strcmp(hreq->method, "GET") != 0) {
9ed2951bd0bb1878a27437d7c00611b2baadd614Timo Sirainen http_server_request_fail(req,
9ed2951bd0bb1878a27437d7c00611b2baadd614Timo Sirainen 405, "Method Not Allowed");
9ed2951bd0bb1878a27437d7c00611b2baadd614Timo Sirainen return;
e48d89622047bd8bbd0475b881ca9377d592f535Timo Sirainen }
e48d89622047bd8bbd0475b881ca9377d592f535Timo Sirainen
e48d89622047bd8bbd0475b881ca9377d592f535Timo Sirainen fpath = t_strconcat(".", path, NULL);
04052d7cacaa866a3f00afb4e104fa46c04c1dd7Timo Sirainen
e48d89622047bd8bbd0475b881ca9377d592f535Timo Sirainen if (debug) {
e48d89622047bd8bbd0475b881ca9377d592f535Timo Sirainen i_debug("test server: download: "
04052d7cacaa866a3f00afb4e104fa46c04c1dd7Timo Sirainen "sending payload for %s", fpath);
10f6f2224c897fc543973efd2f46b86a3ab1148dAki Tuomi }
10f6f2224c897fc543973efd2f46b86a3ab1148dAki Tuomi
10f6f2224c897fc543973efd2f46b86a3ab1148dAki Tuomi fstream = test_file_open(fpath, &status, &reason);
10f6f2224c897fc543973efd2f46b86a3ab1148dAki Tuomi if (fstream == NULL) {
10f6f2224c897fc543973efd2f46b86a3ab1148dAki Tuomi http_server_request_fail(req, status, reason);
10f6f2224c897fc543973efd2f46b86a3ab1148dAki Tuomi return;
10f6f2224c897fc543973efd2f46b86a3ab1148dAki Tuomi }
04052d7cacaa866a3f00afb4e104fa46c04c1dd7Timo Sirainen
268a76700330d159c805c70d1e3eae2e21f1cb9eAki Tuomi resp = http_server_response_create(req, 200, "OK");
268a76700330d159c805c70d1e3eae2e21f1cb9eAki Tuomi http_server_response_add_header(resp, "Content-Type", "text/plain");
268a76700330d159c805c70d1e3eae2e21f1cb9eAki Tuomi
268a76700330d159c805c70d1e3eae2e21f1cb9eAki Tuomi if (blocking) {
268a76700330d159c805c70d1e3eae2e21f1cb9eAki Tuomi output = http_server_response_get_payload_output(resp, TRUE);
9ed2951bd0bb1878a27437d7c00611b2baadd614Timo Sirainen while ((ret=o_stream_send_istream (output, fstream)) > 0);
e48d89622047bd8bbd0475b881ca9377d592f535Timo Sirainen if (ret < 0) {
25757faf029c369a8318349dafe952e2358df1d8Timo Sirainen i_fatal("test server: download: "
25757faf029c369a8318349dafe952e2358df1d8Timo Sirainen "failed to send blocking file payload");
f3d506e525a720f214020ca0f989a1966b30edaeTimo Sirainen }
08aea01ef9a9d20703e0fcf8618e6195c0037a44Timo Sirainen
9ed2951bd0bb1878a27437d7c00611b2baadd614Timo Sirainen if (debug) {
f3d506e525a720f214020ca0f989a1966b30edaeTimo Sirainen i_debug("test server: download: "
9ed2951bd0bb1878a27437d7c00611b2baadd614Timo Sirainen "finished sending blocking payload for %s"
08aea01ef9a9d20703e0fcf8618e6195c0037a44Timo Sirainen "(%"PRIuUOFF_T":%"PRIuUOFF_T")",
08aea01ef9a9d20703e0fcf8618e6195c0037a44Timo Sirainen fpath, fstream->v_offset, output->offset);
849969f639a00eab26791db3cb1b66430420c0cdTimo Sirainen }
25757faf029c369a8318349dafe952e2358df1d8Timo Sirainen
9ed2951bd0bb1878a27437d7c00611b2baadd614Timo Sirainen o_stream_close(output);
9ed2951bd0bb1878a27437d7c00611b2baadd614Timo Sirainen o_stream_unref(&output);
9ed2951bd0bb1878a27437d7c00611b2baadd614Timo Sirainen } else {
9ed2951bd0bb1878a27437d7c00611b2baadd614Timo Sirainen http_server_response_set_payload(resp, fstream);
9ed2951bd0bb1878a27437d7c00611b2baadd614Timo Sirainen http_server_response_submit(resp);
9ed2951bd0bb1878a27437d7c00611b2baadd614Timo Sirainen }
9ed2951bd0bb1878a27437d7c00611b2baadd614Timo Sirainen i_stream_unref(&fstream);
9ed2951bd0bb1878a27437d7c00611b2baadd614Timo Sirainen}
9ed2951bd0bb1878a27437d7c00611b2baadd614Timo Sirainen
9ed2951bd0bb1878a27437d7c00611b2baadd614Timo Sirainen/* location: /echo */
e48d89622047bd8bbd0475b881ca9377d592f535Timo Sirainen
849969f639a00eab26791db3cb1b66430420c0cdTimo Sirainenstatic void
849969f639a00eab26791db3cb1b66430420c0cdTimo Sirainenclient_request_read_echo_more(struct client_request *creq)
9d75363d3fbabc2fbc2d80f06672e3ed8965804aTimo Sirainen{
9d75363d3fbabc2fbc2d80f06672e3ed8965804aTimo Sirainen struct http_server_response *resp;
9d75363d3fbabc2fbc2d80f06672e3ed8965804aTimo Sirainen struct istream *payload_input;
25757faf029c369a8318349dafe952e2358df1d8Timo Sirainen off_t ret;
43a66a0b16299bd4f7615acd85e98bd3832c54d5Timo Sirainen
9625595c47c665f5aee57ebfcb1fcbe9ad1bf3a0Martti Rannanjärvi o_stream_set_max_buffer_size(creq->payload_output, IO_BLOCK_SIZE);
be5c76fabc7439fd33bc799bc3ab3f570799977bTimo Sirainen ret = o_stream_send_istream(creq->payload_output, creq->payload_input);
be5c76fabc7439fd33bc799bc3ab3f570799977bTimo Sirainen o_stream_set_max_buffer_size(creq->payload_output, (size_t)-1);
be5c76fabc7439fd33bc799bc3ab3f570799977bTimo Sirainen if (ret < 0) {
be5c76fabc7439fd33bc799bc3ab3f570799977bTimo Sirainen if (creq->payload_output->stream_errno != 0) {
be5c76fabc7439fd33bc799bc3ab3f570799977bTimo Sirainen i_fatal("test server: echo: "
be5c76fabc7439fd33bc799bc3ab3f570799977bTimo Sirainen "Failed to write all echo payload [%s]", creq->path);
be5c76fabc7439fd33bc799bc3ab3f570799977bTimo Sirainen }
be5c76fabc7439fd33bc799bc3ab3f570799977bTimo Sirainen if (creq->payload_input->stream_errno != 0) {
be5c76fabc7439fd33bc799bc3ab3f570799977bTimo Sirainen i_fatal("test server: echo: "
be5c76fabc7439fd33bc799bc3ab3f570799977bTimo Sirainen "Failed to read all echo payload [%s]", creq->path);
be5c76fabc7439fd33bc799bc3ab3f570799977bTimo Sirainen }
be5c76fabc7439fd33bc799bc3ab3f570799977bTimo Sirainen i_unreached();
be5c76fabc7439fd33bc799bc3ab3f570799977bTimo Sirainen }
be5c76fabc7439fd33bc799bc3ab3f570799977bTimo Sirainen if (i_stream_have_bytes_left(creq->payload_input))
be5c76fabc7439fd33bc799bc3ab3f570799977bTimo Sirainen return;
be5c76fabc7439fd33bc799bc3ab3f570799977bTimo Sirainen
43a66a0b16299bd4f7615acd85e98bd3832c54d5Timo Sirainen io_remove(&creq->io);
43a66a0b16299bd4f7615acd85e98bd3832c54d5Timo Sirainen i_stream_unref(&creq->payload_input);
2028d80c2704bbf62b29b2c624b0ee3c3a03c462Timo Sirainen
9698cd24356147d8a5471260062ea7e10e692fdfAki Tuomi if (debug) {
9698cd24356147d8a5471260062ea7e10e692fdfAki Tuomi i_debug("test server: echo: "
9698cd24356147d8a5471260062ea7e10e692fdfAki Tuomi "finished receiving payload for %s", creq->path);
43a66a0b16299bd4f7615acd85e98bd3832c54d5Timo Sirainen }
43a66a0b16299bd4f7615acd85e98bd3832c54d5Timo Sirainen
43a66a0b16299bd4f7615acd85e98bd3832c54d5Timo Sirainen payload_input = iostream_temp_finish(&creq->payload_output, 4096);
43a66a0b16299bd4f7615acd85e98bd3832c54d5Timo Sirainen
43a66a0b16299bd4f7615acd85e98bd3832c54d5Timo Sirainen resp = http_server_response_create
43a66a0b16299bd4f7615acd85e98bd3832c54d5Timo Sirainen (creq->server_req, 200, "OK");
43a66a0b16299bd4f7615acd85e98bd3832c54d5Timo Sirainen http_server_response_add_header(resp, "Content-Type", "text/plain");
43a66a0b16299bd4f7615acd85e98bd3832c54d5Timo Sirainen http_server_response_set_payload(resp, payload_input);
1a669829132a4b68aaba32400e28bb2a4e19bcaaTimo Sirainen http_server_response_submit(resp);
9b670175445a75987a713ff899d1a945255b0b5bAki Tuomi
43a66a0b16299bd4f7615acd85e98bd3832c54d5Timo Sirainen i_stream_unref(&payload_input);
43a66a0b16299bd4f7615acd85e98bd3832c54d5Timo Sirainen}
43a66a0b16299bd4f7615acd85e98bd3832c54d5Timo Sirainen
43a66a0b16299bd4f7615acd85e98bd3832c54d5Timo Sirainenstatic void
9ed2951bd0bb1878a27437d7c00611b2baadd614Timo Sirainenclient_handle_echo_request(struct client_request *creq,
43a66a0b16299bd4f7615acd85e98bd3832c54d5Timo Sirainen const char *path)
43a66a0b16299bd4f7615acd85e98bd3832c54d5Timo Sirainen{
2028d80c2704bbf62b29b2c624b0ee3c3a03c462Timo Sirainen struct http_server_request *req = creq->server_req;
9698cd24356147d8a5471260062ea7e10e692fdfAki Tuomi const struct http_request *hreq =
9698cd24356147d8a5471260062ea7e10e692fdfAki Tuomi http_server_request_get(req);
9698cd24356147d8a5471260062ea7e10e692fdfAki Tuomi struct http_server_response *resp;
43a66a0b16299bd4f7615acd85e98bd3832c54d5Timo Sirainen struct ostream *payload_output;
43a66a0b16299bd4f7615acd85e98bd3832c54d5Timo Sirainen uoff_t size;
43a66a0b16299bd4f7615acd85e98bd3832c54d5Timo Sirainen int ret;
43a66a0b16299bd4f7615acd85e98bd3832c54d5Timo Sirainen
43a66a0b16299bd4f7615acd85e98bd3832c54d5Timo Sirainen creq->path = p_strdup
43a66a0b16299bd4f7615acd85e98bd3832c54d5Timo Sirainen (http_server_request_get_pool(req), path);
43a66a0b16299bd4f7615acd85e98bd3832c54d5Timo Sirainen
43a66a0b16299bd4f7615acd85e98bd3832c54d5Timo Sirainen if (strcmp(hreq->method, "PUT") != 0) {
1a669829132a4b68aaba32400e28bb2a4e19bcaaTimo Sirainen http_server_request_fail(req,
9b670175445a75987a713ff899d1a945255b0b5bAki Tuomi 405, "Method Not Allowed");
43a66a0b16299bd4f7615acd85e98bd3832c54d5Timo Sirainen return;
43a66a0b16299bd4f7615acd85e98bd3832c54d5Timo Sirainen }
43a66a0b16299bd4f7615acd85e98bd3832c54d5Timo Sirainen
43a66a0b16299bd4f7615acd85e98bd3832c54d5Timo Sirainen size = 0;
9ed2951bd0bb1878a27437d7c00611b2baadd614Timo Sirainen (void)http_request_get_payload_size(hreq, &size);
43a66a0b16299bd4f7615acd85e98bd3832c54d5Timo Sirainen if (size == 0) {
43a66a0b16299bd4f7615acd85e98bd3832c54d5Timo Sirainen resp = http_server_response_create
(creq->server_req, 200, "OK");
http_server_response_add_header(resp, "Content-Type", "text/plain");
http_server_response_submit(resp);
return;
}
payload_output = iostream_temp_create
("/tmp/test-http-server", 0);
if (blocking) {
struct istream *payload_input;
payload_input =
http_server_request_get_payload_input(req, TRUE);
if (read_partial > 0) {
struct istream *partial =
i_stream_create_limit(payload_input, read_partial);
i_stream_unref(&payload_input);
payload_input = partial;
}
while ((ret=o_stream_send_istream
(payload_output, payload_input)) > 0);
if (ret < 0) {
i_fatal("test server: echo: "
"failed to receive blocking echo payload");
}
i_stream_unref(&payload_input);
payload_input = iostream_temp_finish(&payload_output, 4096);
if (debug) {
i_debug("test server: echo: "
"finished receiving blocking payload for %s", path);
}
resp = http_server_response_create(req, 200, "OK");
http_server_response_add_header(resp, "Content-Type", "text/plain");
payload_output = http_server_response_get_payload_output(resp, TRUE);
while ((ret=o_stream_send_istream
(payload_output, payload_input)) > 0);
if (ret < 0) {
i_fatal("test server: echo: "
"failed to send blocking echo payload");
}
if (debug) {
i_debug("test server: echo: "
"finished sending blocking payload for %s", path);
}
i_stream_unref(&payload_input);
o_stream_close(payload_output);
o_stream_unref(&payload_output);
} else {
creq->payload_output = payload_output;
creq->payload_input =
http_server_request_get_payload_input(req, FALSE);
if (read_partial > 0) {
struct istream *partial =
i_stream_create_limit(creq->payload_input, read_partial);
i_stream_unref(&creq->payload_input);
creq->payload_input = partial;
}
creq->io = io_add_istream(creq->payload_input,
client_request_read_echo_more, creq);
client_request_read_echo_more(creq);
}
}
/* request */
static void
http_server_request_destroyed(void *context);
static struct client_request *
client_request_init(struct client *client,
struct http_server_request *req)
{
struct client_request *creq;
pool_t pool = http_server_request_get_pool(req);
http_server_request_ref(req);
creq = p_new(pool, struct client_request, 1);
creq->client = client;
creq->server_req = req;
http_server_request_set_destroy_callback(req,
http_server_request_destroyed, creq);
return creq;
}
static void client_request_deinit(struct client_request **_creq)
{
struct client_request *creq = *_creq;
struct http_server_request *req = creq->server_req;
*_creq = NULL;
if (creq->io != NULL) {
i_stream_unref(&creq->payload_input);
io_remove(&creq->io);
}
http_server_request_unref(&req);
}
static void
http_server_request_destroyed(void *context)
{
struct client_request *creq =
(struct client_request *)context;
client_request_deinit(&creq);
}
static void
client_handle_request(void *context,
struct http_server_request *req)
{
const struct http_request *hreq =
http_server_request_get(req);
const char *path = hreq->target.url->path, *p;
struct client *client = (struct client *)context;
struct client_request *creq;
if (debug) {
i_debug("test server: "
"request method=`%s' path=`%s'", hreq->method, path);
}
creq = client_request_init(client, req);
if (strcmp(path, "/success") == 0) {
client_handle_success_request(creq);
return;
}
if ((p=strchr(path+1, '/')) == NULL) {
http_server_request_fail(req, 404, "Not found");
return;
}
if (strncmp(path, "/download", p-path) == 0) {
client_handle_download_request(creq, p);
return;
}
if (strncmp(path, "/echo", p-path) == 0) {
client_handle_echo_request(creq, p);
return;
}
http_server_request_fail(req, 404, "Not found");
return;
}
/* client connection */
static void
client_connection_destroy(void *context, const char *reason);
static const struct http_server_callbacks http_callbacks = {
.connection_destroy = client_connection_destroy,
.handle_request = client_handle_request
};
static void client_init(int fd)
{
struct client *client;
pool_t pool;
net_set_nonblock(fd, TRUE);
pool = pool_alloconly_create("client", 256);
client = p_new(pool, struct client, 1);
client->pool = pool;
client->http_conn = http_server_connection_create(http_server,
fd, fd, FALSE, &http_callbacks, client);
DLLIST_PREPEND(&clients, client);
}
static void client_deinit(struct client **_client)
{
struct client *client = *_client;
*_client = NULL;
DLLIST_REMOVE(&clients, client);
if (client->http_conn != NULL)
http_server_connection_close(&client->http_conn, "deinit");
pool_unref(&client->pool);
}
static void
client_connection_destroy(void *context, const char *reason ATTR_UNUSED)
{
struct client *client = context;
client->http_conn = NULL;
client_deinit(&client);
}
static void client_accept(void *context ATTR_UNUSED)
{
int fd;
/* accept new client */
fd = net_accept(fd_listen, NULL, NULL);
if (fd == -1)
return;
if (fd == -2) {
i_fatal("test server: accept() failed: %m");
}
client_init(fd);
}
/* */
static void
test_server_init(const struct http_server_settings *server_set)
{
/* open server socket */
io_listen = io_add(fd_listen,
IO_READ, client_accept, (void *)NULL);
http_server = http_server_init(server_set);
}
static void test_server_deinit(void)
{
/* close server socket */
io_remove(&io_listen);
/* deinitialize */
http_server_deinit(&http_server);
}
/*
* Test client
*/
struct test_client_request {
struct io *io;
struct istream *payload;
struct istream *file;
unsigned int files_idx;
};
static struct http_client *http_client;
static unsigned int client_files_first, client_files_last;
static void
test_client_request_destroy(void *context)
{
struct test_client_request *tcreq =
(struct test_client_request *)context;
if (tcreq->io != NULL)
io_remove(&tcreq->io);
if (tcreq->payload != NULL)
i_stream_unref(&tcreq->payload);
if (tcreq->file != NULL)
i_stream_unref(&tcreq->file);
i_free(tcreq);
}
/* download */
static void test_client_download_continue(void);
static void
test_client_download_finished(struct test_client_request *tcreq)
{
const char **paths;
unsigned int count;
paths = array_get_modifiable(&files, &count);
i_assert(tcreq->files_idx < count);
i_assert(client_files_first < count);
i_assert(paths[tcreq->files_idx] != NULL);
paths[tcreq->files_idx] = NULL;
test_client_download_continue();
}
static void
test_client_download_payload_input(struct test_client_request *tcreq)
{
struct istream *payload = tcreq->payload;
const unsigned char *pdata, *fdata;
size_t psize, fsize, pleft;
off_t ret;
/* read payload */
while ((ret=i_stream_read_data
(payload, &pdata, &psize, 0)) > 0) {
if (debug) {
i_debug("test client: download: "
"got data for [%u] (size=%d)",
tcreq->files_idx, (int)psize);
}
/* compare with file on disk */
pleft = psize;
while ((ret=i_stream_read_data
(tcreq->file, &fdata, &fsize, 0)) > 0 && pleft > 0) {
fsize = (fsize > pleft ? pleft : fsize);
if (memcmp(pdata, fdata, fsize) != 0) {
i_fatal("test client: download: "
"received data does not match file "
"(%"PRIuUOFF_T":%"PRIuUOFF_T")",
payload->v_offset, tcreq->file->v_offset);
}
i_stream_skip(tcreq->file, fsize);
pleft -= fsize;
pdata += fsize;
}
if (ret < 0 && tcreq->file->stream_errno != 0) {
i_fatal("test client: download: "
"failed to read file: %s", i_stream_get_error(tcreq->file));
}
i_stream_skip(payload, psize);
}
if (ret == 0) {
if (debug) {
i_debug("test client: download: "
"need more data for [%u]",
tcreq->files_idx);
}
/* we will be called again for this request */
} else {
(void)i_stream_read(tcreq->file);
if (payload->stream_errno != 0) {
i_fatal("test client: download: "
"failed to read request payload: %s",
i_stream_get_error(payload));
} if (i_stream_have_bytes_left(tcreq->file)) {
if (i_stream_read_data(tcreq->file, &fdata, &fsize, 0) <= 0)
fsize = 0;
i_fatal("test client: download: "
"payload ended prematurely "
"(at least %"PRIuSIZE_T" bytes left)", fsize);
} else if (debug) {
i_debug("test client: download: "
"finished request for [%u]",
tcreq->files_idx);
}
/* finished */
test_client_download_finished(tcreq);
/* dereference payload stream; finishes the request */
tcreq->payload = NULL;
io_remove(&tcreq->io); /* holds a reference too */
i_stream_unref(&payload);
}
}
static void
test_client_download_response(const struct http_response *resp,
struct test_client_request *tcreq)
{
const char **paths;
const char *path;
unsigned int count, status;
struct istream *fstream;
const char *reason;
if (debug) {
i_debug("test client: download: "
"got response for [%u]",
tcreq->files_idx);
}
paths = array_get_modifiable(&files, &count);
i_assert(tcreq->files_idx < count);
i_assert(client_files_first < count);
path = paths[tcreq->files_idx];
i_assert(path != NULL);
if (debug) {
i_debug("test client: download: "
"path for [%u]: %s",
tcreq->files_idx, path);
}
fstream = test_file_open(path, &status, &reason);
if (status != resp->status) {
i_fatal("test client: download: "
"got wrong response for %s: %u %s (expected: %u %s)",
path, resp->status, resp->reason, status, reason);
}
if (resp->status / 100 != 2) {
if (debug) {
i_debug("test client: download: "
"HTTP request for %s failed: %u %s",
path, resp->status, resp->reason);
}
i_stream_unref(&fstream);
test_client_download_finished(tcreq);
return;
}
if (resp->payload == NULL) {
if (debug) {
i_debug("test client: download: "
"no payload for %s [%u]",
path, tcreq->files_idx);
}
i_stream_unref(&fstream);
test_client_download_finished(tcreq);
return;
}
i_assert(fstream != NULL);
tcreq->file = fstream;
i_stream_ref(resp->payload);
tcreq->payload = resp->payload;
tcreq->io = io_add_istream(resp->payload,
test_client_download_payload_input, tcreq);
test_client_download_payload_input(tcreq);
}
static void test_client_download_continue(void)
{
struct test_client_request *tcreq;
struct http_client_request *hreq;
const char *const *paths;
unsigned int count;
paths = array_get(&files, &count);
i_assert(client_files_first <= count);
i_assert(client_files_last <= count);
i_assert(client_files_first <= client_files_last);
for (; client_files_first < client_files_last &&
paths[client_files_first] == NULL; client_files_first++)
if (debug) {
i_debug("test client: download: "
"received until [%u]",
client_files_first-1);
}
if (client_files_first >= count) {
io_loop_stop(current_ioloop);
return;
}
for (; client_files_last < count &&
(client_files_last - client_files_first) < test_max_pending;
client_files_last++) {
const char *path = paths[client_files_last];
tcreq = i_new(struct test_client_request, 1);
tcreq->files_idx = client_files_last;
if (debug) {
i_debug("test client: download: "
"retrieving %s [%u]",
path, tcreq->files_idx);
}
hreq = http_client_request(http_client,
"GET", net_ip2addr(&bind_ip),
t_strconcat("/download/", path, NULL),
test_client_download_response, tcreq);
http_client_request_set_port(hreq, bind_port);
http_client_request_set_destroy_callback(hreq,
test_client_request_destroy, tcreq);
http_client_request_submit(hreq);
}
}
static void
test_client_download(const struct http_client_settings *client_set)
{
/* create client */
http_client = http_client_init(client_set);
/* start querying server */
client_files_first = client_files_last = 0;
test_client_download_continue();
}
/* echo */
static void test_client_echo_continue(void);
static void
test_client_echo_finished(struct test_client_request *tcreq)
{
const char **paths;
unsigned int count;
paths = array_get_modifiable(&files, &count);
i_assert(tcreq->files_idx < count);
i_assert(client_files_first < count);
i_assert(paths[tcreq->files_idx] != NULL);
paths[tcreq->files_idx] = NULL;
test_client_echo_continue();
}
static void
test_client_echo_payload_input(struct test_client_request *tcreq)
{
struct istream *payload = tcreq->payload;
const unsigned char *pdata, *fdata;
size_t psize, fsize, pleft;
off_t ret;
/* read payload */
while ((ret=i_stream_read_data
(payload, &pdata, &psize, 0)) > 0) {
if (debug) {
i_debug("test client: echo: "
"got data for [%u] (size=%d)",
tcreq->files_idx, (int)psize);
}
/* compare with file on disk */
pleft = psize;
while ((ret=i_stream_read_data
(tcreq->file, &fdata, &fsize, 0)) > 0 && pleft > 0) {
fsize = (fsize > pleft ? pleft : fsize);
if (memcmp(pdata, fdata, fsize) != 0) {
i_fatal("test client: echo: "
"received data does not match file "
"(%"PRIuUOFF_T":%"PRIuUOFF_T")",
payload->v_offset, tcreq->file->v_offset);
}
i_stream_skip(tcreq->file, fsize);
pleft -= fsize;
pdata += fsize;
}
if (ret < 0 && tcreq->file->stream_errno != 0) {
i_fatal("test client: echo: "
"failed to read file: %s", i_stream_get_error(tcreq->file));
}
i_stream_skip(payload, psize);
}
if (ret == 0) {
if (debug) {
i_debug("test client: echo: "
"need more data for [%u]",
tcreq->files_idx);
}
/* we will be called again for this request */
} else {
(void)i_stream_read(tcreq->file);
if (payload->stream_errno != 0) {
i_fatal("test client: echo: "
"failed to read request payload: %s",
i_stream_get_error(payload));
} if (i_stream_have_bytes_left(tcreq->file)) {
if (i_stream_read_data(tcreq->file, &fdata, &fsize, 0) <= 0)
fsize = 0;
i_fatal("test client: echo: "
"payload ended prematurely "
"(at least %"PRIuSIZE_T" bytes left)", fsize);
} else if (debug) {
i_debug("test client: echo: "
"finished request for [%u]",
tcreq->files_idx);
}
/* finished */
test_client_echo_finished(tcreq);
/* dereference payload stream; finishes the request */
tcreq->payload = NULL;
io_remove(&tcreq->io); /* holds a reference too */
i_stream_unref(&payload);
}
}
static void
test_client_echo_response(const struct http_response *resp,
struct test_client_request *tcreq)
{
const char **paths;
const char *path;
unsigned int count, status;
struct istream *fstream;
if (debug) {
i_debug("test client: echo: "
"got response for [%u]",
tcreq->files_idx);
}
paths = array_get_modifiable(&files, &count);
i_assert(tcreq->files_idx < count);
i_assert(client_files_first < count);
path = paths[tcreq->files_idx];
i_assert(path != NULL);
if (debug) {
i_debug("test client: echo: "
"path for [%u]: %s",
tcreq->files_idx, path);
}
if (resp->status / 100 != 2) {
i_fatal("test client: echo: "
"HTTP request for %s failed: %u %s",
path, resp->status, resp->reason);
}
fstream = test_file_open(path, &status, NULL);
if (fstream == NULL) {
i_fatal("test client: echo: "
"failed to open %s", path);
}
if (read_partial > 0) {
struct istream *partial =
i_stream_create_limit(fstream, read_partial);
i_stream_unref(&fstream);
fstream = partial;
}
if (resp->payload == NULL) {
// FIXME: check file is empty
if (debug) {
i_debug("test client: echo: "
"no payload for %s [%u]",
path, tcreq->files_idx);
}
i_stream_unref(&fstream);
test_client_echo_finished(tcreq);
return;
}
i_assert(fstream != NULL);
tcreq->file = fstream;
i_stream_ref(resp->payload);
tcreq->payload = resp->payload;
tcreq->io = io_add_istream(resp->payload,
test_client_echo_payload_input, tcreq);
test_client_echo_payload_input(tcreq);
}
static void test_client_echo_continue(void)
{
struct test_client_request *tcreq;
struct http_client_request *hreq;
const char **paths;
unsigned int count;
paths = array_get_modifiable(&files, &count);
i_assert(client_files_first <= count);
i_assert(client_files_last <= count);
i_assert(client_files_first <= client_files_last);
for (; client_files_first < client_files_last &&
paths[client_files_first] == NULL; client_files_first++)
if (debug) {
i_debug("test client: echo: "
"received until [%u/%u]", client_files_first-1, count);
}
if (debug && client_files_first < count) {
const char *path = paths[client_files_first];
i_debug("test client: echo: "
"next blocking: %s", (path == NULL ? "none" : path));
}
if (client_files_first >= count) {
io_loop_stop(current_ioloop);
return;
}
for (; client_files_last < count &&
(client_files_last - client_files_first) < test_max_pending;
client_files_last++) {
struct istream *fstream;
const char *path = paths[client_files_last];
fstream = test_file_open(path, NULL, NULL);
if (fstream == NULL) {
paths[client_files_last] = NULL;
if (debug) {
i_debug("test client: echo: "
"skipping %s [%u]",
path, client_files_last);
}
continue;
}
if (debug) {
i_debug("test client: echo: "
"retrieving %s [%u]",
path, client_files_last);
}
tcreq = i_new(struct test_client_request, 1);
tcreq->files_idx = client_files_last;
hreq = http_client_request(http_client,
"PUT", net_ip2addr(&bind_ip),
t_strconcat("/echo/", path, NULL),
test_client_echo_response, tcreq);
http_client_request_set_port(hreq, bind_port);
http_client_request_set_payload
(hreq, fstream, request_100_continue);
http_client_request_set_destroy_callback(hreq,
test_client_request_destroy, tcreq);
http_client_request_submit(hreq);
i_stream_unref(&fstream);
}
}
static void
test_client_echo(const struct http_client_settings *client_set)
{
/* create client */
http_client = http_client_init(client_set);
/* start querying server */
client_files_first = client_files_last = 0;
test_client_echo_continue();
}
/* cleanup */
static void test_client_deinit(void)
{
http_client_deinit(&http_client);
}
/*
* Tests
*/
static void test_open_server_fd(void)
{
if (fd_listen != -1)
i_close_fd(&fd_listen);
fd_listen = net_listen(&bind_ip, &bind_port, 128);
if (fd_listen == -1) {
i_fatal("listen(%s:%u) failed: %m",
net_ip2addr(&bind_ip), bind_port);
}
}
static void test_run_client_server(
const struct http_client_settings *client_set,
const struct http_server_settings *server_set,
void (*client_init)(const struct http_client_settings *client_set))
{
struct ioloop *ioloop;
pid_t pid;
int status;
test_open_server_fd();
if ((pid = fork()) == (pid_t)-1)
i_fatal("fork() failed: %m");
if (pid == 0) {
hostpid_init();
if (debug)
i_debug("server: PID=%s", my_pid);
/* child: server */
ioloop = io_loop_create();
test_server_init(server_set);
io_loop_run(ioloop);
test_server_deinit();
io_loop_destroy(&ioloop);
i_close_fd(&fd_listen);
} else {
if (debug)
i_debug("client: PID=%s", my_pid);
i_close_fd(&fd_listen);
/* parent: client */
ioloop = io_loop_create();
client_init(client_set);
io_loop_run(ioloop);
test_client_deinit();
io_loop_destroy(&ioloop);
(void)kill(pid, SIGKILL);
(void)waitpid(pid, &status, 0);
}
}
static void test_run_sequential(
void (*client_init)(const struct http_client_settings *client_set))
{
struct http_server_settings http_server_set;
struct http_client_settings http_client_set;
/* download files from blocking server */
/* server settings */
memset(&http_server_set, 0, sizeof(http_server_set));
http_server_set.max_pipelined_requests = 0;
http_server_set.debug = debug;
http_server_set.request_limits.max_payload_size = (uoff_t)-1;
/* client settings */
memset(&http_client_set, 0, sizeof(http_client_set));
http_client_set.max_idle_time_msecs = 5*1000;
http_client_set.max_parallel_connections = 1;
http_client_set.max_pipelined_requests = 1;
http_client_set.max_redirects = 0;
http_client_set.max_attempts = 1;
http_client_set.debug = debug;
test_files_init();
test_run_client_server
(&http_client_set, &http_server_set, client_init);
test_files_deinit();
test_out("sequential", TRUE);
}
static void test_run_pipeline(
void (*client_init)(const struct http_client_settings *client_set))
{
struct http_server_settings http_server_set;
struct http_client_settings http_client_set;
/* download files from blocking server */
/* server settings */
memset(&http_server_set, 0, sizeof(http_server_set));
http_server_set.max_pipelined_requests = 4;
http_server_set.debug = debug;
http_server_set.request_limits.max_payload_size = (uoff_t)-1;
/* client settings */
memset(&http_client_set, 0, sizeof(http_client_set));
http_client_set.max_idle_time_msecs = 5*1000;
http_client_set.max_parallel_connections = 1;
http_client_set.max_pipelined_requests = 8;
http_client_set.max_redirects = 0;
http_client_set.max_attempts = 1;
http_client_set.debug = debug;
test_files_init();
test_run_client_server
(&http_client_set, &http_server_set, client_init);
test_files_deinit();
test_out("pipeline", TRUE);
}
static void test_run_parallel(
void (*client_init)(const struct http_client_settings *client_set))
{
struct http_server_settings http_server_set;
struct http_client_settings http_client_set;
/* download files from blocking server */
/* server settings */
memset(&http_server_set, 0, sizeof(http_server_set));
http_server_set.max_pipelined_requests = 4;
http_server_set.debug = debug;
http_server_set.request_limits.max_payload_size = (uoff_t)-1;
/* client settings */
memset(&http_client_set, 0, sizeof(http_client_set));
http_client_set.max_idle_time_msecs = 5*1000;
http_client_set.max_parallel_connections = 40;
http_client_set.max_pipelined_requests = 8;
http_client_set.max_redirects = 0;
http_client_set.max_attempts = 1;
http_client_set.debug = debug;
test_files_init();
test_run_client_server
(&http_client_set, &http_server_set, client_init);
test_files_deinit();
test_out("parallel", TRUE);
}
static void test_download_server_nonblocking(void)
{
test_begin("http payload download (server non-blocking)");
blocking = FALSE;
request_100_continue = FALSE;
read_partial = 0;
test_run_sequential(test_client_download);
test_run_pipeline(test_client_download);
test_run_parallel(test_client_download);
test_end();
}
static void test_download_server_blocking(void)
{
test_begin("http payload download (server blocking)");
blocking = TRUE;
request_100_continue = FALSE;
read_partial = 0;
test_run_sequential(test_client_download);
test_run_pipeline(test_client_download);
test_run_parallel(test_client_download);
test_end();
}
static void test_echo_server_nonblocking(void)
{
test_begin("http payload echo (server non-blocking)");
blocking = FALSE;
request_100_continue = FALSE;
read_partial = 0;
test_run_sequential(test_client_echo);
test_run_pipeline(test_client_echo);
test_run_parallel(test_client_echo);
test_end();
}
static void test_echo_server_blocking(void)
{
test_begin("http payload echo (server blocking)");
blocking = TRUE;
request_100_continue = FALSE;
read_partial = 0;
test_run_sequential(test_client_echo);
test_run_pipeline(test_client_echo);
test_run_parallel(test_client_echo);
test_end();
}
static void test_echo_server_nonblocking_sync(void)
{
test_begin("http payload echo (server non-blocking; 100-continue)");
blocking = FALSE;
request_100_continue = TRUE;
read_partial = 0;
test_run_sequential(test_client_echo);
test_run_pipeline(test_client_echo);
test_run_parallel(test_client_echo);
test_end();
}
static void test_echo_server_blocking_sync(void)
{
test_begin("http payload echo (server blocking; 100-continue)");
blocking = TRUE;
request_100_continue = TRUE;
read_partial = 0;
test_run_sequential(test_client_echo);
test_run_pipeline(test_client_echo);
test_run_parallel(test_client_echo);
test_end();
}
static void test_echo_server_nonblocking_partial(void)
{
test_begin("http payload echo (server non-blocking; partial short)");
blocking = FALSE;
request_100_continue = FALSE;
read_partial = 1024;
test_run_sequential(test_client_echo);
test_run_pipeline(test_client_echo);
test_run_parallel(test_client_echo);
test_end();
test_begin("http payload echo (server non-blocking; partial long)");
read_partial = IO_BLOCK_SIZE + 1024;
test_run_sequential(test_client_echo);
test_run_pipeline(test_client_echo);
test_run_parallel(test_client_echo);
test_end();
}
static void test_echo_server_blocking_partial(void)
{
test_begin("http payload echo (server blocking; partial short)");
blocking = TRUE;
request_100_continue = FALSE;
read_partial = 1024;
test_run_sequential(test_client_echo);
test_run_pipeline(test_client_echo);
test_run_parallel(test_client_echo);
test_end();
test_begin("http payload echo (server blocking; partial long)");
read_partial = IO_BLOCK_SIZE + 1024;
test_run_sequential(test_client_echo);
test_run_pipeline(test_client_echo);
test_run_parallel(test_client_echo);
test_end();
}
static void (*test_functions[])(void) = {
test_download_server_nonblocking,
test_download_server_blocking,
test_echo_server_nonblocking,
test_echo_server_blocking,
test_echo_server_nonblocking_sync,
test_echo_server_blocking_sync,
test_echo_server_nonblocking_partial,
test_echo_server_blocking_partial,
NULL
};
/*
* Main
*/
int main(void)
{
/* listen on localhost */
memset(&bind_ip, 0, sizeof(bind_ip));
bind_ip.family = AF_INET;
bind_ip.u.ip4.s_addr = htonl(INADDR_LOOPBACK);
test_run(test_functions);
}