Sun, 15 Feb 2026 11:16:50 +0100
minimally working httpclient
--- a/src/server/proxy/httpclient.c Sat Feb 14 18:08:24 2026 +0100 +++ b/src/server/proxy/httpclient.c Sun Feb 15 11:16:50 2026 +0100 @@ -36,6 +36,7 @@ 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); @@ -58,6 +59,7 @@ memset(client, 0, sizeof(HttpClient)); client->ev = ev; + client->socketfd = -1; client->request_headers = req_headers; client->response_headers = resp_headers; @@ -242,8 +244,8 @@ } } - // make sure to receive read-ready events in the future - event->events |= EVENT_POLLIN; + // writing complete, switch to read events + event->events = EVENT_POLLIN; char *buffer; @@ -267,13 +269,14 @@ client->error = 1; return 0; } + client->statuscode = client->parser->status; client->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->parser->status, msg.ptr, client->response_start_userdata); + int ret = client->response_start(client, client->statuscode, msg.ptr, client->response_start_userdata); msg.ptr[msg.length] = t; // TODO: check ret @@ -304,6 +307,32 @@ client->buffer.cursize = 0; } + if(r < 0) { + if(errno == EAGAIN) { + return 1; + } else { + log_ereport(LOG_FAILURE, "http-client: IO error: %s", strerror(errno)); + } + } + + // request finished + if(client->response_finished) { + client->response_finished(client, client->response_finished_userdata); + } + + return 0; +} + +static int client_finished(EventHandler *ev, Event *event) { + HttpClient *client = event->cookie; + + close(client->socketfd); + client->socketfd = -1; + + // request finished + if(client->response_finished) { + client->response_finished(client, client->response_finished_userdata); + } return 0; }
--- a/src/server/proxy/httpclient.h Sat Feb 14 18:08:24 2026 +0100 +++ b/src/server/proxy/httpclient.h Sun Feb 15 11:16:50 2026 +0100 @@ -60,6 +60,9 @@ HeaderArray *request_headers; HeaderArray *response_headers; + int error; + int statuscode; + /* * Request body callback function * @@ -91,9 +94,12 @@ /* * Response finished callback * - * void response_finished(HttpClient *client, int error, void *userdata) + * After this callback, the client object is no longer used. The callback + * is allowed to free the client object or reuse it. + * + * void response_finished(HttpClient *client, void *userdata) */ - void (*response_finished)(HttpClient *, int, void *); + void (*response_finished)(HttpClient *, void *); void *response_finished_userdata; @@ -105,7 +111,6 @@ size_t req_buffer_len; size_t req_buffer_pos; - int error; int header_complete; Event readev;
--- a/src/server/safs/proxy.c Sat Feb 14 18:08:24 2026 +0100 +++ b/src/server/safs/proxy.c Sun Feb 15 11:16:50 2026 +0100 @@ -30,6 +30,8 @@ #include <sys/socket.h> #include <arpa/inet.h> +#include <ctype.h> +#include <string.h> #include "../util/pblock.h" #include "../proxy/httpclient.h" @@ -74,22 +76,83 @@ } -typedef struct ProxyData { +typedef struct ProxyRequest { Session *sn; Request *rq; -} ProxyData; + + /* + * request header rewrite map + * name: header name + * value: header value or an empty string, if the header should be removed + */ + pblock *request_header_rewrite; + + /* + * response header rewrite map + * name: header name + * value: header value or an empty string, if the header should be removed + */ + pblock *response_header_rewrite; + + /* + * Has the response started (proxy_response_start called) + */ + int response_started; +} ProxyRequest; static int proxy_response_start(HttpClient *client, int status, char *message, void *userdata) { - ProxyData *proxy = userdata; + ProxyRequest *proxy = userdata; + + HeaderArray *headers = client->response_headers; + while(headers) { + for(int i=0;i<headers->len;i++) { + cxmutstr name = headers->headers[i].name; + cxmutstr value = headers->headers[i].value; + // NSAPI uses lower case header names internally + for(int c=0;c<name.length;c++) { + name.ptr[c] = tolower(name.ptr[c]); + } + // HttpClient does not 0-terminate strings + name.ptr[name.length] = 0; + // check if this header should be modified + char *rewrite = pblock_findval(name.ptr, proxy->response_header_rewrite); + if(rewrite) { + value = cx_mutstr(rewrite); + if(value.length == 0) { + // empty header value -> skip + continue; + } + } + + // add header to response + pblock_nvlinsert(name.ptr, name.length, value.ptr, value.length, proxy->rq->srvhdrs); + } + headers = headers->next; + } protocol_status(proxy->sn, proxy->rq, status, message); protocol_start_response(proxy->sn, proxy->rq); + proxy->response_started = 1; return 0; } +static void proxy_response_finished(HttpClient *client, void *userdata) { + ProxyRequest *proxy = userdata; + + int ret = REQ_PROCEED; + if(!proxy->response_started) { + protocol_status(proxy->sn, proxy->rq, 502, NULL); + ret = REQ_ABORTED; + } + + http_client_free(client); + + nsapi_function_return(proxy->sn, proxy->rq, ret); +} + static ssize_t proxy_response_write(HttpClient *client, void *buf, size_t nbytes, void *userdata) { - ProxyData *proxy = userdata; + ProxyRequest *proxy = userdata; ssize_t ret = net_write(proxy->sn->csd, buf, nbytes); // TODO: handle errors return ret; @@ -105,6 +168,24 @@ return REQ_ABORTED; } + // remove some response headers, that were previously set by ObjectType + // or other SAFs + pblock_removekey(pb_key_content_type, rq->srvhdrs); + + ProxyRequest *proxy = pool_malloc(sn->pool, sizeof(ProxyRequest)); + proxy->sn = sn; + proxy->rq = rq; + proxy->request_header_rewrite = pblock_create_pool(sn->pool, 16); + proxy->response_header_rewrite = pblock_create_pool(sn->pool, 16); + proxy->response_started = 0; + + // some request/response headers should be removed or altered + // an empty string means, the header should be removed + pblock_nvinsert("host", "", proxy->request_header_rewrite); + pblock_nvinsert("connection", "", proxy->request_header_rewrite); + pblock_nvinsert("server", "", proxy->response_header_rewrite); + pblock_nvinsert("connection", "", proxy->response_header_rewrite); + // setup HttpClient HttpClient *client = http_client_new(ev); if(!client) { @@ -127,20 +208,28 @@ address.sin_family = AF_INET; address.sin_port = htons(8080); http_client_set_addr(client, (struct sockaddr*)&address, sizeof(address)); + http_client_add_request_header(client, cx_mutstr("host"), cx_mutstr("localhost:8080")); // add request headers to the client CxIterator i = pblock_iterator(rq->headers); cx_foreach(pb_entry*, entry, i) { - // TODO: don't pass all headers - if(http_client_add_request_header(client, cx_mutstr(entry->param->name), cx_mutstr(entry->param->value))) { + cxmutstr header_value; + char *rewrite_header = pblock_findval(entry->param->name, proxy->request_header_rewrite); + if(rewrite_header) { + header_value = cx_mutstr(rewrite_header); + if(header_value.length == 0) { + continue; + } + } else { + header_value = cx_mutstr(entry->param->value); + } + + if(http_client_add_request_header(client, cx_mutstr(entry->param->name), header_value)) { http_client_free(client); return REQ_ABORTED; } } - ProxyData *proxy = pool_malloc(sn->pool, sizeof(ProxyData)); - proxy->sn = sn; - proxy->rq = rq; client->response_start = proxy_response_start; client->response_start_userdata = proxy; client->response_body_write = proxy_response_write; @@ -158,6 +247,8 @@ +/* --------------------------------- Tests --------------------------------- */ + static CX_TEST(test_safs_proxy_get_uri_from_clfreq) { CX_TEST_DO { cxstring ret;