minimally working httpclient

Sun, 15 Feb 2026 11:16:50 +0100

author
Olaf Wintermann <olaf.wintermann@gmail.com>
date
Sun, 15 Feb 2026 11:16:50 +0100
changeset 672
226bfd584075
parent 671
879005903b2b
child 673
144bdc33fdb6

minimally working httpclient

src/server/proxy/httpclient.c file | annotate | diff | comparison | revisions
src/server/proxy/httpclient.h file | annotate | diff | comparison | revisions
src/server/safs/proxy.c file | annotate | diff | comparison | revisions
--- 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;

mercurial