https://redmine.lighttpd.net/https://redmine.lighttpd.net/favicon.ico?13667327412018-10-07T05:54:45Zlighty labsLighttpd - Bug #2913: Reverse proxy does not work with sandstormhttps://redmine.lighttpd.net/issues/2913?journal_id=115702018-10-07T05:54:45Zgstrauss
<ul></ul><p>lighttpd 1.4 mod_proxy is not an HTTP/1.1 proxy and does not handle Transfer-Encoding: chunked. Your backend is <strong>non-compliant</strong> (broken) if it is sending Transfer-Encoding: chunked in response to an HTTP/1.0 request. You don't have to do that at all, and you shouldn't. If you stream the data to lighttpd, lighttpd will chunk it back to the client if the client made an HTTP/1.1 request and you have set lighttpd.conf <code>server.stream-response-body = 1</code> (or 2)</p>
<p>Your patch makes lighttpd blindly pass bytes, which may break other things, and so the patch as-is is unlikely to be accepted.</p>
<p>The solution is for the backend to simply not send Transfer-Encoding: chunked. Just stream the response body as-is.</p> Lighttpd - Bug #2913: Reverse proxy does not work with sandstormhttps://redmine.lighttpd.net/issues/2913?journal_id=115712018-10-07T06:02:05Zgstrauss
<ul></ul><p>Transfer-Encoding is a hop-by-hop header, so mod_proxy would ideally validate the chunked encoding if mod_proxy were modified to send HTTP/1.1 requests to backend, and to handle Transfer-Encoding: chunked responses. If lighttpd is not streaming the response body back to the client, then there is no use for Transfer-Encoding: chunked, since lighttpd mod_proxy is going to wait for a completed response. lighttpd mod_proxy does not reuse connections to backends (no keep-alive), and sends HTTP/1.0 requests and sends Connection: close.</p> Lighttpd - Bug #2913: Reverse proxy does not work with sandstormhttps://redmine.lighttpd.net/issues/2913?journal_id=115722018-10-07T20:21:50Zgstrauss
<ul><li><strong>Status</strong> changed from <i>New</i> to <i>Invalid</i></li></ul><p>This is not a bug in lighttpd, though maybe I'll add some trace to lighttpd to log to error log that backend proxy is sending an invalid response to an HTTP/1.0 request.</p>
<p>Here's a <strong>completely untested</strong> patch for the <strong>bug in sandstorm</strong> which sends Transfer-Encoding: chunked in response to an HTTP/1.0 request. If this works for you, I'll submit a pull request upstream.<br /><pre>
diff --git a/src/sandstorm/sandstorm-http-bridge.c++ b/src/sandstorm/sandstorm-http-bridge.c++
index 65a835a1..d921c944 100644
--- a/src/sandstorm/sandstorm-http-bridge.c++
+++ b/src/sandstorm/sandstorm-http-bridge.c++
@@ -944,6 +944,7 @@ public:
// desires.
auto parser = kj::heap<HttpParser>(responseStream);
+ if (parser->http_major == 1 && parser->http_minor == 0) isChunked = false;
auto results = context.getResults();
return parser->readResponse(*stream).then(
@@ -1043,7 +1044,7 @@ private:
reqString.slice(0, reqString.size() - 2),
"Content-Length: ", *l, "\r\n"
"\r\n");
- } else {
+ } else if (isChunked) {
reqString = kj::str(
reqString.slice(0, reqString.size() - 2),
"Transfer-Encoding: chunked\r\n"
</pre></p> Lighttpd - Bug #2913: Reverse proxy does not work with sandstormhttps://redmine.lighttpd.net/issues/2913?journal_id=115732018-10-07T20:25:32Zgstrauss
<ul></ul><blockquote>
<p>I have pin pointed where in the code creates this issue and attached the patch with a fix.</p>
</blockquote>
<p>Just to be clear: that line that your patch removed was intentional and specific to proxying, or else it would not be in the code with such specificity.</p>
<p>While the solution you suggested was rejected, thank you for looking into the code and putting together your test case (even though it, too, was invalid for HTTP/1.0)</p> Lighttpd - Bug #2913: Reverse proxy does not work with sandstormhttps://redmine.lighttpd.net/issues/2913?journal_id=115742018-10-07T21:22:40Zblowfist
<ul></ul><p>gstrauss wrote:</p>
<blockquote>
<p>This is not a bug in lighttpd, though maybe I'll add some trace to lighttpd to log to error log that backend proxy is sending an invalid response to an HTTP/1.0 request.</p>
</blockquote>
<p>Yes, I think this would be the best course of action for this kind of situation, because otherwise we are led to think this is a bug in lighttpd due to the garbled result we get from a browser. Since otherwise the chunked packets (minus the header which states the encoding) are directly sent as is to the client.</p>
<p>I did check sandstorm's code to see how they implemented this and it turns out they just hardcoded HTTP/1.1 responses...</p>
<p>Maybe we could also send an error page to the client which clearly states the situation too? (or just a default error so as to make the admin check the error logs)</p> Lighttpd - Bug #2913: Reverse proxy does not work with sandstormhttps://redmine.lighttpd.net/issues/2913?journal_id=115762018-10-08T05:41:07Zgstrauss
<ul></ul><p>The code I committed to lighttpd makes this situation an error, sending 502 Bad Gateway to the client, and will send trace to the error log "proxy backend sent invalid response header (Transfer-Encoding) to HTTP/1.0 request"</p>
<p>It is valid for sandstorm to send HTTP/1.1 back in the status line, even if the request from lighttpd is HTTP/1.0.<br />However, it is not compliant with HTTP RFCs for sandstorm to send Transfer-Encoding: chunked in response to an HTTP/1.0 request.</p> Lighttpd - Bug #2913: Reverse proxy does not work with sandstormhttps://redmine.lighttpd.net/issues/2913?journal_id=115782018-10-08T20:40:33Zblowfist
<ul></ul><p>gstrauss wrote:</p>
<blockquote>
<p>The code I committed to lighttpd makes this situation an error, sending 502 Bad Gateway to the client, and will send trace to the error log "proxy backend sent invalid response header (Transfer-Encoding) to HTTP/1.0 request"</p>
<p>It is valid for sandstorm to send HTTP/1.1 back in the status line, even if the request from lighttpd is HTTP/1.0.<br />However, it is not compliant with HTTP RFCs for sandstorm to send Transfer-Encoding: chunked in response to an HTTP/1.0 request.</p>
</blockquote>
<p>Ah that's very good to know.</p>
<p>gstrauss wrote:</p>
<blockquote>
<p>Here's a <strong>completely untested</strong> patch for the <strong>bug in sandstorm</strong> which sends Transfer-Encoding: chunked in response to an HTTP/1.0 request. If this works for you, I'll submit a pull request upstream.<br />[...]</p>
</blockquote>
<p>I wasn't expecting a patch for sandstorm, this is very nice, thank you. I have had issues compiling sandstorm manually but I finally managed to today. Then I applied your patch and recompiled and it gave some errors... It seems that the base class http_parser is private so it won't allow access to http_major and http_minor, here's the full output :<br />(I'd try to fix it but I'm a C coder, not a C++ one, so I'm flat footed there... But I'll still try to find a way to fix this)</p>
<pre><code class="text syntaxhl" data-language="text">✘ install: sandstorm/sandstorm.ekam-manifest
sandstorm/sandstorm-http-bridge: not found
✘ compile: sandstorm/sandstorm-http-bridge.c++
/ekam-provider/canonical/sandstorm/sandstorm-http-bridge.c++:947:9: error: cannot cast '
sandstorm::HttpParser' to its private base class 'http_parser'
if (parser->http_major == 1 && parser->http_minor == 0) isChunked = false;
^
/ekam-provider/canonical/sandstorm/sandstorm-http-bridge.c++:159:19: note: declared private here
private http_parser,
^~~~~~~~~~~~~~~~~~~
/ekam-provider/canonical/sandstorm/sandstorm-http-bridge.c++:947:17: error: 'http_major' is a
private member of 'http_parser'
if (parser->http_major == 1 && parser->http_minor == 0) isChunked = false;
^
/ekam-provider/canonical/sandstorm/sandstorm-http-bridge.c++:159:19: note: constrained by
private inheritance here
private http_parser,
^~~~~~~~~~~~~~~~~~~
/ekam-provider/c++header/joyent-http/http_parser.h:206:18: note: member is declared here
unsigned short http_major;
^
/ekam-provider/canonical/sandstorm/sandstorm-http-bridge.c++:947:36: error: cannot cast '
sandstorm::HttpParser' to its private base class 'http_parser'
if (parser->http_major == 1 && parser->http_minor == 0) isChunked = false;
^
/ekam-provider/canonical/sandstorm/sandstorm-http-bridge.c++:159:19: note: declared private here
private http_parser,
^~~~~~~~~~~~~~~~~~~
/ekam-provider/canonical/sandstorm/sandstorm-http-bridge.c++:947:44: error: 'http_minor' is a
private member of 'http_parser'
if (parser->http_major == 1 && parser->http_minor == 0) isChunked = false;
^
/ekam-provider/canonical/sandstorm/sandstorm-http-bridge.c++:159:19: note: constrained by
...(log truncated; use -l to increase log limit)...
make: *** [Makefile:256: tmp/.ekam-run] Error 1
</code></pre> Lighttpd - Bug #2913: Reverse proxy does not work with sandstormhttps://redmine.lighttpd.net/issues/2913?journal_id=115802018-10-09T03:14:45Zgstrauss
<ul></ul><p>Let's break some encapsulation. WCGW? Change the 'private' to 'public'. There should be little impact elsewhere as this class definition is inside the .c++ file, not in a header file.</p>
<pre>
diff --git a/src/sandstorm/sandstorm-http-bridge.c++ b/src/sandstorm/sandstorm-http-bridge.c++
index 65a835a1..ee893e29 100644
--- a/src/sandstorm/sandstorm-http-bridge.c++
+++ b/src/sandstorm/sandstorm-http-bridge.c++
@@ -156,7 +156,7 @@ const HeaderWhitelist RESPONSE_HEADER_WHITELIST(*WebSession::Response::HEADER_WH
#pragma clang diagnostic pop
class HttpParser: public sandstorm::Handle::Server,
- private http_parser,
+ public http_parser,
private kj::TaskSet::ErrorHandler {
public:
HttpParser(sandstorm::ByteStream::Client responseStream)
@@ -944,6 +944,7 @@ public:
// desires.
auto parser = kj::heap<HttpParser>(responseStream);
+ if (parser->http_major == 1 && parser->http_minor == 0) isChunked = false;
auto results = context.getResults();
return parser->readResponse(*stream).then(
@@ -1043,7 +1045,7 @@ private:
reqString.slice(0, reqString.size() - 2),
"Content-Length: ", *l, "\r\n"
"\r\n");
- } else {
+ } else if (isChunked) {
reqString = kj::str(
reqString.slice(0, reqString.size() - 2),
"Transfer-Encoding: chunked\r\n"
</pre> Lighttpd - Bug #2913: Reverse proxy does not work with sandstormhttps://redmine.lighttpd.net/issues/2913?journal_id=115812018-10-09T03:24:50Zblowfist
<ul></ul><p>gstrauss wrote:</p>
<blockquote>
<p>Let's break some encapsulation. WCGW? Change the 'private' to 'public'. There should be little impact elsewhere as this class definition is inside the .c++ file, not in a header file.</p>
<p>[...]</p>
</blockquote>
<p>yeah, I did something similar :</p>
<pre><code class="text syntaxhl" data-language="text">diff --git a/src/sandstorm/sandstorm-http-bridge.c++ b/src/sandstorm/sandstorm-http-bridge.c++
index 65a835a1..b0319378 100644
--- a/src/sandstorm/sandstorm-http-bridge.c++
+++ b/src/sandstorm/sandstorm-http-bridge.c++
@@ -221,6 +221,14 @@ public:
});
}
+ unsigned short getResponseHTTPMajor() {
+ return this->http_major;
+ }
+
+ unsigned short getResponseHTTPMinor() {
+ return this->http_minor;
+ }
+
@@ -944,6 +952,16 @@ public:
// desires.
auto parser = kj::heap<HttpParser>(responseStream);
+ if (parser->getResponseHTTPMajor() == 1 && parser->getResponseHTTPMinor() == 0) {
+ isChunked = false;
+ KJ_LOG(WARNING, "HTTP/1.0 detected, deactivating chunked encoding");
+ }
</code></pre>
<p>I tested many different things, even bypassing the functions to no avail... The resulting HTML is always served with Transfer-Encoding: chunked. So I think this bridge code is only used for a specific purpose instead of all the packets passing through it. I think the actual implementation uses either joyent http in nodejs or something similar (like meteor).</p>
<p>I also have no idea how this logging system works, there is no way to get these outputs from anywhere I looked...</p>
<p>I really doubt a web server like the one provided by nodejs would have such a blatant bug though.</p>
<p>edit: <br />Oh and by the way I tested your patch and it works great :) (hopefully my little python code helped testing this)</p>
<p>2nd edit:<br />I meant your lighttpd patch</p> Lighttpd - Bug #2913: Reverse proxy does not work with sandstormhttps://redmine.lighttpd.net/issues/2913?journal_id=115822018-10-09T05:07:38Zgstrauss
<ul></ul><p>That the lighttpd patch rejects the response from sandstorm is probably not the solution you were going for when you filed this ticket. At least there is a message in the error log.</p>
<p>sandstorm is truckload of code and dependencies, and as C++, takes forever to build. Sorry that I won't be able to dig further.</p> Lighttpd - Bug #2913: Reverse proxy does not work with sandstormhttps://redmine.lighttpd.net/issues/2913?journal_id=117192019-03-15T19:45:28Zjacmet
<ul></ul><p>gstrauss wrote:</p>
<blockquote>
<p>That the lighttpd patch rejects the response from sandstorm is probably not the solution you were going for when you filed this ticket. At least there is a message in the error log.</p>
<p>sandstorm is truckload of code and dependencies, and as C++, takes forever to build. Sorry that I won't be able to dig further.</p>
</blockquote>
<p>Notice: This also breaks proxying websocket servers written using python-aiohttp, which also sends an uncoditional Transfer-Encoding: Chunked header:</p>
<p><a class="external" href="https://github.com/aio-libs/aiohttp/blob/master/aiohttp/web_ws.py#L182">https://github.com/aio-libs/aiohttp/blob/master/aiohttp/web_ws.py#L182</a></p>
<p>Maybe a more pragmatic solution is just to ignore this header, like it was done earlier?</p> Lighttpd - Bug #2913: Reverse proxy does not work with sandstormhttps://redmine.lighttpd.net/issues/2913?journal_id=117212019-03-16T00:50:23Zgstrauss
<ul></ul><p>An HTTP server which responds to an HTTP/1.0 request with an HTTP/1.1 header such as Transfer-Encoding: chunked is <strong>broken</strong>. FULL STOP. Not lighttpd.</p>
<p>lighttpd is not broken. lighttpd sends HTTP/1.0 requests to backends and expects HTTP/1.0-compliant responses. That's how protocols work. That's how RFC-compliance works. lighttpd will not be made broken by violating the HTTP specifications (your self-described "pragmatic" solution fails the test). If you would like to implement HTTP/1.1 support for mod_proxy, please do so and I'll be happy to review the patches, but not in this ticket.</p>
<p>This bug is invalid, as has been clearly stated multiple times, and further illogical posts here will be ignored.</p>
<p>Based on the original poster's description, sandstorm violates the HTTP/1.0 RFC.</p>
<p>websockets is a different protocol which follows Connection: upgrade, Upgrade: websocket. lighttpd mod_proxy makes an exception and sends HTTP/1.1 requests to backends only when mod_proxy is <em>explicitly configured</em> to pass the Upgrade header, since Upgrade is part of the HTTP/1.1 protocol, not HTTP/1.0. If python-aiohttp response to Upgrade: websocket requires Transfer-Encoding: chunked, then that would be unfortunate, since a successfully upgraded connection will be running the upgraded protocol (e.g. websockets) after a short, affirmative HTTP response that the connection has been upgraded.</p>
<p>Most people using websockets are encapsulating a different RESTful protocol, and can instead use lighttpd mod_wstunnel to be the websocket encapsulation endpoint.</p> Lighttpd - Bug #2913: Reverse proxy does not work with sandstormhttps://redmine.lighttpd.net/issues/2913?journal_id=117232019-03-16T16:35:48Zjacmet
<ul></ul><p>gstrauss wrote:</p>
<blockquote>
<p>An HTTP server which responds to an HTTP/1.0 request with an HTTP/1.1 header such as Transfer-Encoding: chunked is <strong>broken</strong>. FULL STOP.</p>
</blockquote>
<p>Completely agreed, my comment was explicitly about websocket and not about sandstorm,</p>
<blockquote>
<p>websockets is a different protocol which follows Connection: upgrade, Upgrade: websocket. lighttpd mod_proxy makes an exception and sends HTTP/1.1 requests to backends only when mod_proxy is <em>explicitly configured</em> to pass the Upgrade header, since Upgrade is part of the HTTP/1.1 protocol, not HTTP/1.0. If python-aiohttp response to Upgrade: websocket requires Transfer-Encoding: chunked, then that would be unfortunate, since a successfully upgraded connection will be running the upgraded protocol (e.g. websockets) after a short, affirmative HTTP response that the connection has been upgraded.</p>
</blockquote>
<p>Indeed. With upgrade=enabled, connection to an aiohttp based backed works with older lighttpd versions but is broken by the more strict check introduced in 1.4.50 as the response to the Connection: upgrade request contains Transfer-Encoding: chunked</p>
<p><a class="external" href="https://github.com/aio-libs/aiohttp/blob/master/aiohttp/web_ws.py#L182">https://github.com/aio-libs/aiohttp/blob/master/aiohttp/web_ws.py#L182</a></p>
<blockquote>
<p>Most people using websockets are encapsulating a different RESTful protocol, and can instead use lighttpd mod_wstunnel to be the websocket encapsulation endpoint.</p>
</blockquote>
<p>This backend unfortunately provides a mix of HTTP and websocket, so that is somewhat more cumbersome to do here.</p> Lighttpd - Bug #2913: Reverse proxy does not work with sandstormhttps://redmine.lighttpd.net/issues/2913?journal_id=118822019-09-27T21:57:37ZShulyaka
<ul></ul><p>Hi! Any solution for aiohttp-based websockets? The fix looks like to be trivial, just remove the validation of HTTP_HEADER_TRANSFER_ENCODING.</p> Lighttpd - Bug #2913: Reverse proxy does not work with sandstormhttps://redmine.lighttpd.net/issues/2913?journal_id=118832019-09-27T22:01:22ZShulyaka
<ul></ul><p>Update: looks like my version of aiohttp is old, they already removed the header (<a class="external" href="https://github.com/aio-libs/aiohttp/commit/8b8f6b8a8b57db1ba20b402d0fbb0d3d3749d466#diff-3add5885ec3edaeee3d5cf9cef4cae69">https://github.com/aio-libs/aiohttp/commit/8b8f6b8a8b57db1ba20b402d0fbb0d3d3749d466#diff-3add5885ec3edaeee3d5cf9cef4cae69</a>)</p>