return has_next; // false if EOF or pipe broken
};
- // wire up the HTTP client
- // note: do NOT capture `this` pointer, as it may be destroyed before the thread ends
- httplib::ResponseHandler response_handler = [pipe, cli](const httplib::Response & response) {
+ // build the header message forwarded to the reader thread, stripping internal proxy headers
+ auto make_header_msg = [](const httplib::Response & response) {
msg_t msg;
msg.status = response.status;
for (const auto & [key, value] : response.headers) {
}
msg.headers[key] = value;
}
- return pipe->write(std::move(msg)); // send headers first
+ return msg;
+ };
+
+ // true once response_handler has already forwarded the headers
+ auto headers_sent = std::make_shared<std::atomic<bool>>(false);
+
+ // wire up the HTTP client
+ // note: do NOT capture `this` pointer, as it may be destroyed before the thread ends
+ httplib::ResponseHandler response_handler = [pipe, headers_sent, make_header_msg](const httplib::Response & response) {
+ headers_sent->store(true);
+ return pipe->write(make_header_msg(response)); // send headers first
};
httplib::ContentReceiverWithProgress content_receiver = [pipe](const char * data, size_t data_length, size_t, size_t) {
// send data chunks
// start the proxy thread
SRV_DBG("start proxy thread %s %s\n", req.method.c_str(), req.path.c_str());
- this->thread = std::thread([cli, pipe, req]() {
+ this->thread = std::thread([cli, pipe, req, headers_sent, make_header_msg]() {
auto result = cli->send(std::move(req));
if (result.error() != httplib::Error::Success) {
auto err_str = httplib::to_string(result.error());
SRV_ERR("http client error: %s\n", err_str.c_str());
pipe->write({{}, 500, "", ""}); // header
pipe->write({{}, 0, "proxy error: " + err_str, ""}); // body
+ } else if (!headers_sent->load()) {
+ // httplib skips response_handler for bodyless statuses like 204, send headers here instead
+ pipe->write(make_header_msg(*result));
}
pipe->close_write(); // signal EOF to reader
SRV_DBG("%s", "client request thread ended\n");
import pytest
from utils import *
+import threading
+from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
server = ServerPreset.tinyllama2()
res = server.make_request("GET", f"/cors-proxy?url=http://{server.server_host}:{server.server_port}/models")
assert res.status_code == 200
assert "data" in res.body
+
+
+def test_mcp_proxy_no_content():
+ # note: see issue #26598
+ class NoContentHandler(BaseHTTPRequestHandler):
+ def do_POST(self):
+ self.send_response(204)
+ self.end_headers()
+
+ def log_message(self, format, *args):
+ pass
+
+ target = ThreadingHTTPServer(("127.0.0.1", 0), NoContentHandler)
+ target_thread = threading.Thread(target=target.serve_forever, daemon=True)
+ target_thread.start()
+
+ try:
+ global server
+ server.ui_mcp_proxy = True
+ server.start()
+
+ res = server.make_request("POST", f"/cors-proxy?url=http://127.0.0.1:{target.server_port}/", data={})
+ assert res.status_code == 204
+ assert res.body in (None, b"", "")
+ finally:
+ target.shutdown()
+ target.server_close()
+