commit 9702a31471076bb244d8d5cf448053de7c23cdf2
parent 63c5ea79ca40beb4d6418c45dc78927897648ac4
Author: Christian Grothoff <christian@grothoff.org>
Date: Mon, 27 Apr 2026 00:12:04 +0200
remove -F option, also use PH_ctx instead of another curl multi loop for the reverse proxy curl interaction
Diffstat:
3 files changed, 87 insertions(+), 415 deletions(-)
diff --git a/src/backend/paivana-httpd.c b/src/backend/paivana-httpd.c
@@ -52,8 +52,6 @@ struct GNUNET_CURL_Context *PH_ctx;
int PH_no_check;
-int PH_force_complete_upload;
-
int PH_respect_forwarded_headers;
unsigned long long PH_request_buffer_max = 1024 * 1024;
@@ -314,12 +312,6 @@ main (int argc,
"trust X-Forwarded-For for the client address (only safe behind a trusted reverse proxy)"),
&PH_respect_forwarded_headers),
GNUNET_GETOPT_option_flag (
- 'F',
- "force-complete-upload",
- gettext_noop (
- "force Paivana to finish the upload even if the server has already started to respond"),
- &PH_force_complete_upload),
- GNUNET_GETOPT_option_flag (
'n',
"no-payment",
gettext_noop (
diff --git a/src/backend/paivana-httpd.h b/src/backend/paivana-httpd.h
@@ -65,15 +65,6 @@ extern struct GNUNET_CURL_Context *PH_ctx;
extern int PH_no_check;
/**
- * Force Paivana to complete an upload even if the
- * server already started sending a response. This MAY
- * fix some data integrity issues, but also could be
- * unsupported by some servers and be wasteful if the
- * server has already rejected the upload.
- */
-extern int PH_force_complete_upload;
-
-/**
* If set, derive the client address from the leftmost entry of the
* "X-Forwarded-For" request header (falling back to the socket
* address only when the header is absent). Only enable this when
diff --git a/src/backend/paivana-httpd_reverse.c b/src/backend/paivana-httpd_reverse.c
@@ -29,6 +29,7 @@
#include "platform.h"
#include <curl/curl.h>
#include <gnunet/gnunet_util_lib.h>
+#include <gnunet/gnunet_curl_lib.h>
#include <taler/taler_mhd_lib.h>
#include "paivana-httpd.h"
#include "paivana-httpd_reverse.h"
@@ -115,7 +116,13 @@ enum RequestState
* We've finished receiving download data from cURL; ready to
* build and queue the final response to the client.
*/
- REQUEST_STATE_PROXY_DOWNLOAD_DONE
+ REQUEST_STATE_PROXY_DOWNLOAD_DONE,
+
+ /**
+ * We've failed to download data from cURL; ready to
+ * build and queue the final response to the client.
+ */
+ REQUEST_STATE_PROXY_DOWNLOAD_FAILED
};
@@ -198,6 +205,11 @@ struct HttpRequest
CURL *curl;
/**
+ * Job handle for the CURL request in our event loop.
+ */
+ struct GNUNET_CURL_Job *job;
+
+ /**
* HTTP request headers for the curl request.
*/
struct curl_slist *headers;
@@ -244,11 +256,6 @@ struct HttpRequest
enum GNUNET_GenericReturnValue suspended;
/**
- * Did we pause CURL processing?
- */
- bool curl_paused;
-
- /**
* Concatenated value of the client's Via header(s), if any. Per
* RFC 9110 §7.6.3 a proxy must *append* its own entry to this
* list, not replace it; we capture the inbound value here before
@@ -298,16 +305,6 @@ static struct MHD_Response *upload_failure_response;
*/
static struct MHD_Response *internal_failure_response;
-/**
- * The cURL multi handle
- */
-static CURLM *curl_multi;
-
-/**
- * The cURL download task (curl multi API).
- */
-static struct GNUNET_SCHEDULER_Task *curl_download_task;
-
/**
* Create HTML response using @a body
@@ -363,18 +360,6 @@ PAIVANA_HTTPD_reverse_init (void)
"<p>The HTTP method specified is not allowed.</p>"
"</body></html>\n";
- if (0 != curl_global_init (CURL_GLOBAL_WIN32))
- {
- GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "cURL global init failed!\n");
- return false;
- }
- if (NULL == (curl_multi = curl_multi_init ()))
- {
- GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "Failed to create cURL multi handle!\n");
- return false;
- }
curl_failure_response
= make_html_response (curl_failure_body);
if (NULL == curl_failure_response)
@@ -420,16 +405,6 @@ PAIVANA_HTTPD_reverse_shutdown (void)
MHD_resume_connection (hr->con);
}
}
- if (NULL != curl_download_task)
- {
- GNUNET_SCHEDULER_cancel (curl_download_task);
- curl_download_task = NULL;
- }
- if (NULL != curl_multi)
- {
- curl_multi_cleanup (curl_multi);
- curl_multi = NULL;
- }
if (NULL != curl_failure_response)
{
MHD_destroy_response (curl_failure_response);
@@ -681,117 +656,66 @@ curl_check_hdr (void *buffer,
/**
- * Create the MHD response with CURL's as starting base;
- * mainly set the response code and parses the response into
- * JSON, if it is such.
+ * Handle response payload data from cURL.
*
- * @param hr pointer to where to store the new data. Despite
- * its name, the struct contains response data as well.
- * @return #GNUNET_OK if it succeeds.
+ * @param cls our `struct HttpRequest *`
+ * @param response_code HTTP status returned by the server
+ * @param body response body
+ * @param body_size number of blocks of data
*/
-static enum GNUNET_GenericReturnValue
-create_mhd_response_from_hr (struct HttpRequest *hr)
+static void
+curl_download_cb (void *cls,
+ long response_code,
+ const void *body,
+ size_t body_size)
{
- long resp_code;
+ struct HttpRequest *hr = cls;
- if (NULL != hr->response)
+ hr->job = NULL;
+ if (0 == response_code)
{
- GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "Response already set!\n");
- return GNUNET_SYSERR;
+ hr->state = REQUEST_STATE_PROXY_DOWNLOAD_FAILED;
}
- GNUNET_break (CURLE_OK ==
- curl_easy_getinfo (hr->curl,
- CURLINFO_RESPONSE_CODE,
- &resp_code));
- GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
- "Creating MHD response with code %u\n",
- (unsigned int) resp_code);
- hr->response_code = resp_code;
- if (GNUNET_YES == hr->suspended)
- {
- MHD_resume_connection (hr->con);
- hr->suspended = GNUNET_NO;
- }
- TALER_MHD_daemon_trigger ();
- return GNUNET_OK;
-}
-
-
-/**
- * Handle response payload data from cURL.
- * Copies it into our `io_buf` to make it available to MHD.
- *
- * @param ptr pointer to the data
- * @param size number of blocks of data
- * @param nmemb blocksize
- * @param ctx our `struct HttpRequest *`
- * @return number of bytes handled
- */
-static size_t
-curl_download_cb (void *ptr,
- size_t size,
- size_t nmemb,
- void *ctx)
-{
- struct HttpRequest *hr = ctx;
- size_t total = size * nmemb;
-
- GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
- "Curl download proceeding\n");
-
- if (REQUEST_STATE_PROXY_UPLOAD_STARTED == hr->state)
+ else
{
- if (0 == hr->io_len)
+ hr->response = MHD_create_response_from_buffer_copy (body_size,
+ body);
+ hr->response_code = response_code;
+ for (struct HttpResponseHeader *header = hr->header_head;
+ NULL != header;
+ header = header->next)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "Adding MHD response header %s->%s\n",
+ header->type,
+ header->value);
+ GNUNET_break (MHD_YES ==
+ MHD_add_response_header (hr->response,
+ header->type,
+ header->value));
+ }
+ if ( (REQUEST_STATE_PROXY_DOWNLOAD_STARTED == hr->state) ||
+ ( (REQUEST_STATE_PROXY_UPLOAD_STARTED == hr->state) &&
+ (0 == hr->io_len) ) )
{
/* Either the request body was empty (CURLOPT_POSTFIELDSIZE = 0,
so libcurl never called our read callback) or the upload
already drained but we have not yet entered upload_cb to
flip the state — either way, the upload is complete and we
can move straight on to consuming the response. */
- hr->state = REQUEST_STATE_PROXY_DOWNLOAD_STARTED;
- }
- else
- {
- /* HTTP/1.1 lets the upstream begin responding before we have
- finished sending the request body. Pause the download
- callback (libcurl will buffer the response bytes for us)
- and let curl_upload_cb finish draining io_buf; it will
- resume us via curl_easy_pause() once io_len hits zero. */
- GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
- "Pausing download cb, %u upload bytes still to send\n",
- (unsigned int) hr->io_len);
- hr->curl_paused = true;
- return CURL_WRITEFUNC_PAUSE;
+ hr->state = REQUEST_STATE_PROXY_DOWNLOAD_DONE;
}
}
- GNUNET_assert (REQUEST_STATE_PROXY_DOWNLOAD_STARTED ==
- hr->state);
- if (hr->io_size - hr->io_len < total)
+ if (GNUNET_YES == hr->suspended)
{
- GNUNET_assert (total + hr->io_size >= total);
- GNUNET_assert (hr->io_size * 2 + 1024 > hr->io_size);
- GNUNET_array_grow (hr->io_buf,
- hr->io_size,
- GNUNET_MAX (total + hr->io_len,
- hr->io_size * 2 + 1024));
+ hr->suspended = GNUNET_NO;
+ MHD_resume_connection (hr->con);
+ TALER_MHD_daemon_trigger ();
}
- GNUNET_memcpy (&hr->io_buf[hr->io_len],
- ptr,
- total);
- hr->io_len += total;
- return total;
}
/**
- * Ask cURL for the select() sets and schedule cURL operations.
- */
-static void
-curl_download_prepare (void);
-
-
-/**
* cURL callback for uploaded (PUT/POST) data.
* Copies from our `io_buf` to make it available to cURL.
*
@@ -853,26 +777,11 @@ curl_upload_cb (void *buf,
&hr->io_buf[to_copy],
hr->io_len - to_copy);
hr->io_len -= to_copy;
- if ( (hr->curl_paused) &&
- (! PH_force_complete_upload) )
- {
- hr->io_len = 0;
- }
if (0 == hr->io_len)
{
hr->state = REQUEST_STATE_PROXY_DOWNLOAD_STARTED;
GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
"Completed CURL UPLOAD\n");
- if (hr->curl_paused)
- {
- /* curl_download_cb pauses itself when an early upstream
- response arrives mid-upload; resume it now that the
- request body has been fully delivered. */
- hr->curl_paused = false;
- curl_easy_pause (hr->curl,
- CURLPAUSE_CONT);
- curl_download_prepare ();
- }
}
return to_copy;
}
@@ -914,93 +823,6 @@ build_host_header (const char *url)
/**
- * Task that is run when we are ready to receive more data
- * from curl
- *
- * @param cls closure
- */
-static void
-curl_task_download (void *cls);
-
-
-/**
- * Ask cURL for the select() sets and schedule cURL operations.
- */
-static void
-curl_download_prepare ()
-{
- CURLMcode mret;
- fd_set rs;
- fd_set ws;
- fd_set es;
- int max;
- struct GNUNET_NETWORK_FDSet *grs;
- struct GNUNET_NETWORK_FDSet *gws;
- long to;
- struct GNUNET_TIME_Relative rtime;
-
- if (NULL != curl_download_task)
- {
- GNUNET_SCHEDULER_cancel (curl_download_task);
- curl_download_task = NULL;
- }
- max = -1;
- FD_ZERO (&rs);
- FD_ZERO (&ws);
- FD_ZERO (&es);
- if (CURLM_OK != (mret = curl_multi_fdset (curl_multi,
- &rs,
- &ws,
- &es,
- &max)))
- {
- GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "%s failed at %s:%d: `%s'\n",
- "curl_multi_fdset",
- __FILE__,
- __LINE__,
- curl_multi_strerror (mret));
- return;
- }
- to = -1;
- GNUNET_break (CURLM_OK ==
- curl_multi_timeout (curl_multi,
- &to));
- if (-1 == to)
- rtime = GNUNET_TIME_UNIT_FOREVER_REL;
- else
- rtime = GNUNET_TIME_relative_multiply
- (GNUNET_TIME_UNIT_MILLISECONDS, to);
- if (-1 != max)
- {
- grs = GNUNET_NETWORK_fdset_create ();
- gws = GNUNET_NETWORK_fdset_create ();
- GNUNET_NETWORK_fdset_copy_native (grs,
- &rs,
- max + 1);
- GNUNET_NETWORK_fdset_copy_native (gws,
- &ws,
- max + 1);
- curl_download_task
- = GNUNET_SCHEDULER_add_select (
- GNUNET_SCHEDULER_PRIORITY_DEFAULT,
- rtime,
- grs, gws,
- &curl_task_download,
- curl_multi);
- GNUNET_NETWORK_fdset_destroy (gws);
- GNUNET_NETWORK_fdset_destroy (grs);
- }
- else
- {
- curl_download_task = GNUNET_SCHEDULER_add_delayed (rtime,
- &curl_task_download,
- curl_multi);
- }
-}
-
-
-/**
* "Filter" function that translates MHD request headers to
* cURL's.
*
@@ -1081,113 +903,6 @@ con_val_iter (void *cls,
}
-/**
- * Task that is run when we are ready to receive
- * more data from curl.
- *
- * @param cls closure, usually NULL.
- */
-static void
-curl_task_download (void *cls)
-{
- int running;
- int msgnum;
- struct CURLMsg *msg;
- CURLMcode mret;
- struct HttpRequest *hr;
-
- (void) cls;
- curl_download_task = NULL;
- do
- {
- running = 0;
- mret = curl_multi_perform (curl_multi,
- &running);
- while (NULL != (msg = curl_multi_info_read (curl_multi,
- &msgnum)))
- {
- GNUNET_break
- (CURLE_OK == curl_easy_getinfo
- (msg->easy_handle,
- CURLINFO_PRIVATE,
- (char **) &hr));
-
- if (NULL == hr)
- {
- GNUNET_break (0);
- continue;
- }
- switch (msg->msg)
- {
- case CURLMSG_NONE:
- /* documentation says this is not used */
- GNUNET_break (0);
- break;
- case CURLMSG_DONE:
- switch (msg->data.result)
- {
- case CURLE_OK:
- case CURLE_GOT_NOTHING:
- GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
- "CURL download completed.\n");
- hr->state = REQUEST_STATE_PROXY_DOWNLOAD_DONE;
- if (0 == hr->response_code)
- GNUNET_assert (GNUNET_OK ==
- create_mhd_response_from_hr (hr));
- break;
- default:
- GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
- "Download curl failed: %s\n",
- curl_easy_strerror (msg->data.result));
- hr->state = REQUEST_STATE_PROXY_DOWNLOAD_DONE;
- /* Surface upstream failures as 502 Bad Gateway. Drop
- any partial body we might have accumulated and let
- MHD return our pre-built failure page instead of
- half a truncated upstream response. */
- hr->response_code = MHD_HTTP_BAD_GATEWAY;
- hr->io_len = 0;
- hr->response = curl_failure_response;
- if (GNUNET_YES == hr->suspended)
- {
- MHD_resume_connection (hr->con);
- hr->suspended = GNUNET_NO;
- }
- break;
- } /* end switch (msg->data.result) */
- GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
- "Curl request for `%s' finished (got the response)\n",
- hr->url);
- TALER_MHD_daemon_trigger ();
- break;
- case CURLMSG_LAST:
- /* documentation says this is not used */
- GNUNET_break (0);
- break;
- default:
- /* unexpected status code */
- GNUNET_break (0);
- break;
- } /* end switch msg->msg */
- } /* end while (curl_multi_info_read()) */
- } while (mret == CURLM_CALL_MULTI_PERFORM);
- if (CURLM_OK != mret)
- GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "%s failed at %s:%d: `%s'\n",
- "curl_multi_perform",
- __FILE__,
- __LINE__,
- curl_multi_strerror (mret));
- if (0 == running)
- {
- GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
- "Suspending cURL multi loop,"
- " no more events pending\n");
- return; /* nothing more in progress */
- }
- curl_download_prepare ();
-}
-
-
struct HttpRequest *
PAIVANA_HTTPD_reverse_create (struct MHD_Connection *connection,
const char *url)
@@ -1214,12 +929,15 @@ PAIVANA_HTTPD_reverse_cleanup (struct HttpRequest *hr)
{
GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
"Resetting cURL handle\n");
- curl_multi_remove_handle (curl_multi,
- hr->curl);
curl_easy_cleanup (hr->curl);
hr->curl = NULL;
hr->io_len = 0;
}
+ if (NULL != hr->job)
+ {
+ GNUNET_CURL_job_cancel (hr->job);
+ hr->job = NULL;
+ }
if (NULL != hr->headers)
{
curl_slist_free_all (hr->headers);
@@ -1592,6 +1310,8 @@ start_curl_request (struct HttpRequest *hr,
if (NULL == hr->curl)
{
PAIVANA_LOG_ERROR ("Could not init the curl handle\n");
+ hr->suspended = GNUNET_NO;
+ MHD_resume_connection (hr->con);
return MHD_queue_response (con,
MHD_HTTP_INTERNAL_SERVER_ERROR,
internal_failure_response);
@@ -1636,16 +1356,6 @@ start_curl_request (struct HttpRequest *hr,
curl_easy_setopt (hr->curl,
CURLOPT_READDATA,
hr);
- curl_easy_setopt (hr->curl,
- CURLOPT_WRITEFUNCTION,
- &curl_download_cb);
- curl_easy_setopt (hr->curl,
- CURLOPT_WRITEDATA,
- hr);
- if (PH_force_complete_upload)
- curl_easy_setopt (hr->curl,
- CURLOPT_KEEP_SENDING_ON_ERROR,
- 1);
{
char *curlurl;
@@ -1677,20 +1387,11 @@ start_curl_request (struct HttpRequest *hr,
con,
meth);
if (NULL == hr->curl)
- return r; /* unsupported method: response already queued */
-
- if (CURLM_OK !=
- curl_multi_add_handle (curl_multi,
- hr->curl))
{
- GNUNET_break (0);
- curl_easy_cleanup (hr->curl);
- hr->curl = NULL;
- return MHD_queue_response (con,
- MHD_HTTP_BAD_GATEWAY,
- curl_failure_response);
+ hr->suspended = GNUNET_NO;
+ MHD_resume_connection (hr->con);
+ return r; /* unsupported method: response already queued */
}
-
/* First pass: collect Via / Connection so `con_val_iter` can
honor them (append to Via; drop headers named by Connection). */
MHD_get_connection_values (con,
@@ -1704,11 +1405,21 @@ start_curl_request (struct HttpRequest *hr,
append_forwarded_headers (hr,
con,
ver);
-
- curl_easy_setopt (hr->curl,
- CURLOPT_HTTPHEADER,
- hr->headers);
- curl_download_prepare ();
+ hr->job = GNUNET_CURL_job_add_raw (PH_ctx,
+ hr->curl,
+ hr->headers,
+ &curl_download_cb,
+ hr);
+ hr->curl = NULL;
+ if (NULL == hr->job)
+ {
+ GNUNET_break (0);
+ hr->suspended = GNUNET_NO;
+ MHD_resume_connection (hr->con);
+ return MHD_queue_response (con,
+ MHD_HTTP_BAD_GATEWAY,
+ curl_failure_response);
+ }
return MHD_YES;
}
@@ -1730,33 +1441,10 @@ finalize_response (struct HttpRequest *hr,
shared failure response. */
if (NULL == hr->response)
{
- hr->response
- = MHD_create_response_from_buffer_static (hr->io_len,
- hr->io_buf);
- if (NULL == hr->response)
- {
- GNUNET_break (0);
- hr->response_code = MHD_HTTP_INTERNAL_SERVER_ERROR;
- hr->response = internal_failure_response;
- }
- else
- {
- for (struct HttpResponseHeader *header = hr->header_head;
- NULL != header;
- header = header->next)
- {
- GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
- "Adding MHD response header %s->%s\n",
- header->type,
- header->value);
- GNUNET_break (MHD_YES ==
- MHD_add_response_header (hr->response,
- header->type,
- header->value));
- }
- }
+ GNUNET_break (0);
+ hr->response_code = MHD_HTTP_INTERNAL_SERVER_ERROR;
+ hr->response = internal_failure_response;
}
- TALER_MHD_daemon_trigger ();
return MHD_queue_response (con,
hr->response_code,
hr->response);
@@ -1854,18 +1542,15 @@ PAIVANA_HTTPD_reverse (struct HttpRequest *hr,
/* start_curl_request advances state to PROXY_UPLOAD_STARTED
or PROXY_DOWNLOAD_STARTED on success and returns MHD_YES;
on error it queues a response and returns its MHD_Result. */
+ GNUNET_assert (GNUNET_NO == hr->suspended);
+ MHD_suspend_connection (con);
+ hr->suspended = GNUNET_YES;
return start_curl_request (hr,
con,
meth,
ver);
case REQUEST_STATE_PROXY_UPLOAD_STARTED:
- /* curl is still working; suspend the connection until the
- curl task resumes us with state == PROXY_DOWNLOAD_DONE. */
- GNUNET_assert (GNUNET_NO == hr->suspended);
- MHD_suspend_connection (con);
- hr->suspended = GNUNET_YES;
- return MHD_YES;
case REQUEST_STATE_PROXY_UPLOAD_DONE:
case REQUEST_STATE_PROXY_DOWNLOAD_STARTED:
/* we should not have been resumed in this state,
@@ -1879,6 +1564,10 @@ PAIVANA_HTTPD_reverse (struct HttpRequest *hr,
case REQUEST_STATE_PROXY_DOWNLOAD_DONE:
return finalize_response (hr,
con);
+ case REQUEST_STATE_PROXY_DOWNLOAD_FAILED:
+ return MHD_queue_response (con,
+ MHD_HTTP_BAD_GATEWAY,
+ curl_failure_response);
}
GNUNET_assert (0); /* unreachable */
}