Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Unexpected and random content-length in HTTP/1.0 responses with filter bwlim-out and 3.0-dev7-50d8c1-78 #2536

Open
rnsanchez opened this issue Apr 17, 2024 · 17 comments
Labels
2.9 This issue affects the HAProxy 2.9 stable branch. status: fixed This issue is a now-fixed bug. type: bug This issue describes a bug.

Comments

@rnsanchez
Copy link
Contributor

rnsanchez commented Apr 17, 2024

Detailed Description of the Problem

I was preparing to use HAProxy's bandwidth throttling for a testcase, and noticed an incorrect and unexpected behavior:

Notice content-length: 0:

$ curl --trace-time -v 'http://localhost.ring/icons/backend.mp2t' --unix-socket /tmp/vai-para-lighttpd.sock --http1.0 -Y 100000 -y 10 -o /dev/null
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
  0     0    0     0    0     0      0      0 --:--:-- --:--:-- --:--:--     021:23:13.175224 *   Trying /tmp/vai-para-lighttpd.sock:0...
21:23:13.175366 * Connected to localhost.ring (/tmp/vai-para-lighttpd.sock) port 80
21:23:13.175445 > GET /icons/backend.mp2t HTTP/1.0
21:23:13.175445 > Host: localhost.ring
21:23:13.175445 > User-Agent: curl/8.6.0
21:23:13.175445 > Accept: */*
21:23:13.175445 > 
21:23:13.176181 * HTTP 1.0, assume close after body
21:23:13.176316 < HTTP/1.0 200 OK
21:23:13.176455 < content-type: application/octet-stream
21:23:13.176548 < etag: "292088603"
21:23:13.176646 < last-modified: Fri, 22 Sep 2023 14:49:48 GMT
21:23:13.176750 < date: Wed, 17 Apr 2024 00:23:13 GMT
21:23:13.176856 < server: lighttpd/1.4.58
21:23:13.176961 < content-length: 0
21:23:13.177066 < 
  0     0    0     0    0     0      0      0 --:--:-- --:--:-- --:--:--     0
21:23:13.177612 * Closing connection

Repeating; notice how close the content-length is to an internal buffer size:

21:24:47.466638 * Connected to localhost.ring (/tmp/vai-para-lighttpd.sock) port 80
21:24:47.466757 > GET /icons/backend.mp2t HTTP/1.0
21:24:47.466757 > Host: localhost.ring
21:24:47.466757 > User-Agent: curl/8.6.0
21:24:47.466757 > Accept: */*
21:24:47.466757 > 
21:24:47.468639 * HTTP 1.0, assume close after body
21:24:47.468732 < HTTP/1.0 200 OK
21:24:47.468830 < content-type: application/octet-stream
21:24:47.468933 < etag: "292088603"
21:24:47.469044 < last-modified: Fri, 22 Sep 2023 14:49:48 GMT
21:24:47.469142 < date: Wed, 17 Apr 2024 00:24:47 GMT
21:24:47.469245 < server: lighttpd/1.4.58
21:24:47.469352 < content-length: 15037
21:24:47.469452 < 
21:24:47.469551 { [15037 bytes data]
100 15037  100 15037    0     0  3218k      0 --:--:-- --:--:-- --:--:-- 3671k
21:24:47.470233 * Closing connection

21:29:03.187966 * Connected to localhost.ring (/tmp/vai-para-lighttpd.sock) port 80
21:29:03.188123 > GET /icons/backend.mp2t HTTP/1.0
21:29:03.188123 > Host: localhost.ring
21:29:03.188123 > User-Agent: curl/8.6.0
21:29:03.188123 > Accept: */*
21:29:03.188123 > 
21:29:03.189152 * HTTP 1.0, assume close after body
21:29:03.189266 < HTTP/1.0 200 OK
21:29:03.189385 < content-type: application/octet-stream
21:29:03.189512 < etag: "292088603"
21:29:03.189616 < last-modified: Fri, 22 Sep 2023 14:49:48 GMT
21:29:03.189730 < date: Wed, 17 Apr 2024 00:29:02 GMT
21:29:03.189847 < server: lighttpd/1.4.58
21:29:03.189947 < content-length: 15037
21:29:03.190059 < 
21:29:03.190165 { [15037 bytes data]
100 15037  100 15037    0     0  3330k      0 --:--:-- --:--:-- --:--:-- 3671k
21:29:03.190981 * Closing connection

Now simply disabling bwlimit filter, as suggested by @felipewd (the throttling is from lighttpd; I forgot to disable it, and I use it at 500KB/s), works fine:

21:29:33.621190 * Connected to localhost.ring (/tmp/vai-para-lighttpd.sock) port 80
21:29:33.621411 > GET /icons/backend.mp2t HTTP/1.0
21:29:33.621411 > Host: localhost.ring
21:29:33.621411 > User-Agent: curl/8.6.0
21:29:33.621411 > Accept: */*
21:29:33.621411 > 
21:29:33.622587 * HTTP 1.0, assume close after body
21:29:33.622699 < HTTP/1.0 200 OK
21:29:33.622821 < content-type: application/octet-stream
21:29:33.622916 < etag: "292088603"
21:29:33.623023 < last-modified: Fri, 22 Sep 2023 14:49:48 GMT
21:29:33.623125 < date: Wed, 17 Apr 2024 00:29:33 GMT
21:29:33.623242 < server: lighttpd/1.4.58
21:29:33.623359 < content-length: 37842944
21:29:33.623471 < 
21:29:33.623586 { [102188 bytes data]
100 36.0M  100 36.0M    0     0   503k      0  0:01:13  0:01:13 --:--:--  577k
21:30:47.032450 * Closing connection

HAProxy's output, upon repeating the requests above:

(bad ones)
Using epoll() as the polling mechanism.
00000000:fe-Lighttpd.accept(000c)=0012 from [unix:3] ALPN=<none>
00000000:fe-Lighttpd.clireq[0012:ffffffff]: GET /icons/backend.mp2t HTTP/1.0
00000000:fe-Lighttpd.clihdr[0012:ffffffff]: host: localhost.ring
00000000:fe-Lighttpd.clihdr[0012:ffffffff]: user-agent: curl/8.6.0
00000000:fe-Lighttpd.clihdr[0012:ffffffff]: accept: */*
00000000:Lighttpd.srvrep[0012:0013]: HTTP/1.0 200 OK
00000000:Lighttpd.srvhdr[0012:0013]: content-type: application/octet-stream
00000000:Lighttpd.srvhdr[0012:0013]: etag: "292088603"
00000000:Lighttpd.srvhdr[0012:0013]: last-modified: Fri, 22 Sep 2023 14:49:48 GMT
00000000:Lighttpd.srvhdr[0012:0013]: content-length: 37842944
00000000:Lighttpd.srvhdr[0012:0013]: date: Wed, 17 Apr 2024 00:46:42 GMT
00000000:Lighttpd.srvhdr[0012:0013]: server: lighttpd/1.4.58
00000000:Lighttpd.clicls[0012:0013]
00000000:Lighttpd.srvcls[0012:0013]
00000000:Lighttpd.closed[0012:0013]
00000001:fe-Lighttpd.accept(000c)=0012 from [unix:3] ALPN=<none>
00000001:fe-Lighttpd.clireq[0012:ffffffff]: GET /icons/backend.mp2t HTTP/1.0
00000001:fe-Lighttpd.clihdr[0012:ffffffff]: host: localhost.ring
00000001:fe-Lighttpd.clihdr[0012:ffffffff]: user-agent: curl/8.6.0
00000001:fe-Lighttpd.clihdr[0012:ffffffff]: accept: */*
00000001:Lighttpd.srvrep[0012:0013]: HTTP/1.0 200 OK
00000001:Lighttpd.srvhdr[0012:0013]: content-type: application/octet-stream
00000001:Lighttpd.srvhdr[0012:0013]: etag: "292088603"
00000001:Lighttpd.srvhdr[0012:0013]: last-modified: Fri, 22 Sep 2023 14:49:48 GMT
00000001:Lighttpd.srvhdr[0012:0013]: content-length: 37842944
00000001:Lighttpd.srvhdr[0012:0013]: date: Wed, 17 Apr 2024 00:46:44 GMT
00000001:Lighttpd.srvhdr[0012:0013]: server: lighttpd/1.4.58
00000001:Lighttpd.srvcls[0012:0013]
00000001:Lighttpd.clicls[0012:0013]
00000001:Lighttpd.closed[0012:0013]

(good one; no bwlim-out filter)
Using epoll() as the polling mechanism.
00000000:fe-Lighttpd.accept(000c)=0012 from [unix:3] ALPN=<none>
00000000:fe-Lighttpd.clireq[0012:ffffffff]: GET /icons/backend.mp2t HTTP/1.0
00000000:fe-Lighttpd.clihdr[0012:ffffffff]: host: localhost.ring
00000000:fe-Lighttpd.clihdr[0012:ffffffff]: user-agent: curl/8.6.0
00000000:fe-Lighttpd.clihdr[0012:ffffffff]: accept: */*
00000000:Lighttpd.srvrep[0012:0013]: HTTP/1.0 200 OK
00000000:Lighttpd.srvhdr[0012:0013]: content-type: application/octet-stream
00000000:Lighttpd.srvhdr[0012:0013]: etag: "292088603"
00000000:Lighttpd.srvhdr[0012:0013]: last-modified: Fri, 22 Sep 2023 14:49:48 GMT
00000000:Lighttpd.srvhdr[0012:0013]: content-length: 37842944
00000000:Lighttpd.srvhdr[0012:0013]: date: Wed, 17 Apr 2024 00:47:03 GMT
00000000:Lighttpd.srvhdr[0012:0013]: server: lighttpd/1.4.58
00000000:Lighttpd.srvcls[0012:0013]
00000000:Lighttpd.clicls[0012:0013]
00000000:Lighttpd.closed[0012:0013]

Now here's what strace witnessed (cropped; last line is particularly important):

16030 21:49:06.032955 sendto(19, "GET /icons/backend.mp2t HTTP/1.0\r\nhost: localhost.ring\r\nuser-agent: curl/8.6.0\r\naccept: */*\r\n..
16030 21:49:06.033047 recvfrom(19, 0x1ceafd0, 15360, 0, NULL, NULL) = -1 EAGAIN (Resource temporarily unavailable) <0.000031>
16030 21:49:06.033146 epoll_ctl(4, EPOLL_CTL_ADD, 18, {events=EPOLLIN|EPOLLRDHUP, data={u32=18, u64=18}}) = 0 <0.000036>
16030 21:49:06.033224 epoll_ctl(4, EPOLL_CTL_ADD, 19, {events=EPOLLIN|EPOLLRDHUP, data={u32=19, u64=19}}) = 0 <0.000015>
16030 21:49:06.033296 clock_gettime(CLOCK_THREAD_CPUTIME_ID, {tv_sec=0, tv_nsec=10484934}) = 0 <0.000014>
16030 21:49:06.033366 epoll_wait(4, [{events=EPOLLIN, data={u32=19, u64=19}}], 200, 60000) = 1 <0.000013>
16030 21:49:06.033418 clock_gettime(CLOCK_THREAD_CPUTIME_ID, {tv_sec=0, tv_nsec=10509472}) = 0 <0.000013>
16030 21:49:06.033471 recvfrom(19, "HTTP/1.0 200 OK\r\nContent-Type: application/octet-stream\r\nETag: \"292088603\"\r\nLast-Modified: Fri, 22 Sep 2023 14:49:48 GMT\r\nContent-Length: 37842944\r\nConnection: close\r\nDate: ...
16030 21:49:06.033570 recvfrom(19, "\362S\370\210\311\321E\303\370\233\2458\337\263$\4\6_\7+\367\331\214u\203\256M\239...
16030 21:49:06.033657 sendto(18, "HTTP/1.0 200 OK\r\ncontent-type: application/octet-stream\r\netag: \"292088603\"\r\nlast-modified: Fri, 22 Sep 2023 14:49:48 GMT\r\ndate: Wed, 17 Apr 2024 00:49:06 GMT\r\nserver: lighttpd/1.4.58\r\ncontent-length: 15037\r\n\r\n

Expected Behavior

I'd expect 1) a rate-limited response but more importantly 2) a sane response (no content-length, connection: close).

Steps to Reproduce the Behavior

  1. Set up a rate-limited source, probably slower than what will be set in HAProxy
  2. Set up a bwlim-out filter
  3. Try a HTTP/1.0 request

Do you have any idea what may have caused this?

@felipewd guessed bwlim-out. I must agree, though I have not used it up until now.

Do you have an idea how to solve the issue?

No response

What is your configuration?

global
	nbthread		2
	hard-stop-after		1m
	unix-bind		mode 666
	maxconn			700
	ulimit-n		2048
	log			/dev/log local0
	stats socket		/tmp/haproxy-stats.sock mode 666 level admin

defaults
	mode			http
	option			abortonclose
	option			dontlog-normal
	retries			1

	log-format		%Ts.%ms\ %HM\ %ST\ %fc\ %bc\ %sc\ %ci:%cp\ %B\ %U\ %sslv\ %sslc\ %HU\ %ts

	option			splice-response
	timeout connect		1h
	timeout client		1h
	timeout server		1h

frontend fe-Lighttpd
	bind			::1:8888
	bind			127.0.0.1:8888
	bind			/tmp/vai-para-lighttpd.sock
	log			global

	filter bwlim-out	limite default-limit 625000 default-period 1s

	acl MP2T		path -m sub .mp2t

	http-response		set-bandwidth-limit limite

	use_backend Lighttpd	if MP2T
	default_backend		Lighttpd-limpo

backend Lighttpd
	server lighttpd		/var/run/lighttpd.sock
	log			global

	acl mp2t		path_sub .mp2t

	http-request		set-header connection close if mp2t

	http-after-response	del-header content-length


backend Lighttpd-limpo
	server lighttpd		/var/run/lighttpd.sock
	log			global

Output of haproxy -vv

HAProxy version 3.0-dev7-50d8c1-78 2024/04/16 - https://haproxy.org/
Status: development branch - not safe for use in production.
Known bugs: https://github.com/haproxy/haproxy/issues?q=is:issue+is:open
Running on: Linux 6.6.0-rc1 #1 SMP PREEMPT_DYNAMIC Mon Sep 11 08:38:38 -03 2023 x86_64
Build options :
  TARGET  = linux-glibc
  CC      = cc
  CFLAGS  = -O2 -g -fwrapv
  OPTIONS = USE_SYSTEMD=0
  DEBUG   = 

Feature list : -51DEGREES +ACCEPT4 +BACKTRACE -CLOSEFROM +CPU_AFFINITY +CRYPT_H -DEVICEATLAS +DL -ENGINE +EPOLL -EVPORTS +GETADDRINFO -KQUEUE -LIBATOMIC +LIBCRYPT +LINUX_CAP +LINUX_SPLICE +LINUX_TPROXY -LUA -MATH -MEMORY_PROFILING +NETFILTER +NS -OBSOLETE_LINKER -OPENSSL -OPENSSL_AWSLC -OPENSSL_WOLFSSL -OT -PCRE -PCRE2 -PCRE2_JIT -PCRE_JIT +POLL +PRCTL -PROCCTL -PROMEX -PTHREAD_EMULATION -QUIC -QUIC_OPENSSL_COMPAT +RT +SHM_OPEN +SLZ -SSL -STATIC_PCRE -STATIC_PCRE2 -SYSTEMD +TFO +THREAD +THREAD_DUMP +TPROXY -WURFL -ZLIB

Default settings :
  bufsize = 16384, maxrewrite = 1024, maxpollevents = 200

Built with multi-threading support (MAX_TGROUPS=16, MAX_THREADS=256, default=12).
Built with network namespace support.
Built with libslz for stateless compression.
Compression algorithms supported : identity("identity"), deflate("deflate"), raw-deflate("deflate"), gzip("gzip")
Built with transparent proxy support using: IP_TRANSPARENT IPV6_TRANSPARENT IP_FREEBIND
Built without PCRE or PCRE2 support (using libc's regex instead)
Encrypted password support via crypt(3): yes
Built with gcc compiler version 13.2.0

Available polling systems :
      epoll : pref=300,  test result OK
       poll : pref=200,  test result OK
     select : pref=150,  test result OK
Total: 3 (3 usable), will use epoll.

Available multiplexer protocols :
(protocols marked as <default> cannot be specified using 'proto' keyword)
         h2 : mode=HTTP  side=FE|BE  mux=H2    flags=HTX|HOL_RISK|NO_UPG
       fcgi : mode=HTTP  side=BE     mux=FCGI  flags=HTX|HOL_RISK|NO_UPG
  <default> : mode=HTTP  side=FE|BE  mux=H1    flags=HTX
         h1 : mode=HTTP  side=FE|BE  mux=H1    flags=HTX|NO_UPG
  <default> : mode=TCP   side=FE|BE  mux=PASS  flags=
       none : mode=TCP   side=FE|BE  mux=PASS  flags=NO_UPG

Available services : none

Available filters :
	[BWLIM] bwlim-in
	[BWLIM] bwlim-out
	[CACHE] cache
	[COMP] compression
	[FCGI] fcgi-app
	[SPOE] spoe
	[TRACE] trace

Last Outputs and Backtraces

No response

Additional Information

No response

@rnsanchez rnsanchez added status: needs-triage This issue needs to be triaged. type: bug This issue describes a bug. labels Apr 17, 2024
@rnsanchez
Copy link
Contributor Author

Doing some rollbacks:

  • HAProxy version 2.9.7-a67b18-14 2024/04/08: broken
  • HAProxy version 2.8.9 2024/04/05: WORKS!

@capflam
Copy link
Member

capflam commented Apr 17, 2024

The issue here is about the bwlim filter mixed with http-after-response del-header content-length rule. So there is an unexpected side effect here and I'm annoyed because I don't know yet how to fix it. Is there any good reason to delete the content-length header ?

@capflam capflam added status: reviewed This issue was reviewed. A fix is required. and removed status: needs-triage This issue needs to be triaged. labels Apr 17, 2024
@rnsanchez
Copy link
Contributor Author

Is there any good reason to delete the content-length header ?

I'm doing this in order to stay on HTTP/1.0 + Connection: close, a common case we see. I understand that having the actual length from the backend may create some anxiety when replying at the frontend, where I am instructing HAProxy to do the more difficult thing. But such is the scenario, proxying to a much less capable HTTP user.

@capflam
Copy link
Member

capflam commented Apr 18, 2024

HTTP/1.0 without explicit connection: keep-alive header is always in close mode. In HAProxy, it is unexpected to change or remove the content-length header. The same is true with transfer-encoding header. There are some internal information that are based on these headers, retrieved during the message parsing. I must confess the actual behavior is not perfect and I will try to find a better way to deal with all that stuff. But it remains unexpected for now to modify these headers, with or without filters. For now, I suggest to remove the action, because the header will re-added anyway.

@rnsanchez
Copy link
Contributor Author

When interfacing with Lighttpd, I have to manually remove, in order to get closer to our frequent use-case (end users in HTTP/1.0 mode, with unknown content-length). The case in this ticket is a major simplification of what we might see in production (where "might happen" usually turns out to be "millions of times" in our experience, so I try to not poke Murphy too much).

What really caught my attention is that this usually worked without issues and, after recently going do 3.0dev, I found myself chasing ghosts until I had to check everything from the ground up---that's when I first caught the weird content-length. Rolling back to latest 2.8, working as usual.

Right now, I have 2.8 interfacing an nc process that will simply send an identical response every single time. A similar HTTP/1.0 response as in this ticket, except that it doesn't have a content-length header; trying to mimick what we see in production for a very specific use-case.

@capflam
Copy link
Member

capflam commented Apr 18, 2024

The behavior about updates of transfer-encoding and content-length headers was indeed changed during 2.9 dev cycle. Mainly because there are foggy areas on older versions if such changes are performed. During the message parsing, these headers are used to define the internal state of the message. Changing it without updating this internal state will produce unexpected result. And the only way to allow these changes is to parse every header modification.

Here, the scenario you are describing seems reasonable but with no special processing about these during headers modifications, there are many way to produce invalid messages. For instance, changing the content-length value or removing it by a tranfer-encoding: chunked header or just removing it for a client in keep-alive mode. I know your config is just a simple reproducer, but with this config, my last exemple is an issue.

I will try to review all this part to mitigate this kind of issue, but I doubt it will be possible to catch all cases where these headers may be modified without changing the internal HTTP message state. And at the end, it seems safest to simply forbid these changes.

What is exactly the issue by keeping this header in HTTP/1.0 in close mode ?

EDIT: I asked because if there is a true use-case, may be we could add an option to workaround it. And I know you are not doing this kind of thing for arbitrary reason :)

@rnsanchez
Copy link
Contributor Author

In my testing, I actually may have a content-length: header because I cannot completely reproduce our production environment where we easily see HTTP/1.0 responses relying solely on connection: close. In production it usually happens with simpler/older end users (e.g., odd application-level requirements, some ancient smartphone or a very low-cost device), and some are close to impossible for us to test them. Although I have the payload for some of these cases, the way it is transported/proxied is actually almost more important in order to get closer to what actually happened.

If I could suggest anything with no regard to how much work this could cause, having a "http 1.0 mode" could be useful. Or at least some way of instructing HAProxy to trust its buffers while disregarding (removing) a content-length header. It would have been just fine if the content-length header had not been reinserted (and with a bogus value), because of the connection: close. Or, well, insert instead a connection: close implicitly since I expressed a wish to not announce a length. :-)

@capflam
Copy link
Member

capflam commented Apr 18, 2024

Well it is not so easy because a regex may be used to remove a header or it may be removed from lua for instance. So in the end, it means name of every header removed must be tested. It is possible but a bit annoying. Everyone will pay for a marginal case. In this case, I prefer to add an option for this specific use-case. And in the same time, I should also improve the config parsing to trigger errors on this unexpected actions.

So, if I understand well, you remove the header to reproduce an issue on your production when there is no content-length header, right ? Or just to reproduce this case for testing purpose ?

@rnsanchez
Copy link
Contributor Author

Well it is not so easy because a regex may be used to remove a header or it may be removed from lua for instance. So in the end, it means name of every header removed must be tested. It is possible but a bit annoying. Everyone will pay for a marginal case. In this case, I prefer to add an option for this specific use-case. And in the same time, I should also improve the config parsing to trigger errors on this unexpected actions.

I was afraid it turned out to be more complex (and indeed it did).

So, if I understand well, you remove the header to reproduce an issue on your production when there is no content-length header, right ? Or just to reproduce this case for testing purpose ?

The former, yes: to reproduce locally what I cannot get even close as to what we see in production. It will be that odd-ball request/response that will shake things unexpectedly, and having it captured, I can pretend I have the same setup that unknown users A and B used in their exchange, while still being proxied by HAProxy.

@capflam
Copy link
Member

capflam commented Apr 19, 2024

Just to let you know that we will talk about this issue with Willy on the next Monday to decide how we want to handle modifications of content-length and transfer-encoding headers. The subject is a bit complex and we must be sure to make the right choices.

@gstrauss
Copy link
Contributor

server: lighttpd/1.4.58 Wow! That's old! lighttpd 1.4.58 was released Dec 2020, almost 3 1/2 years ago.

Please test with the latest lighttpd release, lighttpd 1.4.76.

Among other things, HAProxy can use HTTP/2 to connect to lighttpd (which first added HTTP/2 support in lighttpd 1.4.56, but HTTP/2 is not enabled by default in lighttpd until lighttpd 1.4.59, and some HTTP/2 bugs in lighttpd have been fixed in the intervening 3 1/2 years since lighttpd 1.4.58)

@capflam
Copy link
Member

capflam commented Apr 29, 2024

@rnsanchez, We discussed with Willy about this issue last week. We probably may improve the situation by allowing rewrites of the content-length and transfer-encoding headers with some limitations. I must review carefully all this stuff to properly identify the perimeter of changes. For instance, such changes on a message will disable the splicing/direct forwarding support for the message.

@capflam capflam added dev This issue affects the HAProxy development branch. 2.9 This issue affects the HAProxy 2.9 stable branch. labels Apr 29, 2024
@rnsanchez
Copy link
Contributor Author

Got it. On our end, we also face challenges when we have to adjust either of those (a lot more challenging for us when deadling with TE, though).

haproxy-mirror pushed a commit that referenced this issue May 17, 2024
…ages

During the 2.9 dev cycle, to be able to support zero-copy data forwarding, a
change on the H1 mux was performed to ignore the headers modifications about
payload representation (Content-Length and Transfer-Encoding headers).

It appears there are some use-cases where it could be handy to change values
of these headers or just remove them. For instance, we can imagine to remove
these headers on a server response to force the old HTTP/1.0 close mode
behavior. So thaks to this patch, the rules are relaxed. It is now possible
to remove these headers. When this happens, the following rules are applied:

 * If "Content-Length" header is removed but a "Transfer-Encoding: chunked"
   header is found, no special processing is performed. The message remains
   chunked. However the close mode is not forced.

 * If "Transfer-Encoding" header is removed but a "Content-Length" header is
   found, no special processing is performed. The payload length must comply
   to the specified content length.

 * If one of them is removed and the other one is not found, a response is
   switch the close mode and a "Content-Length: 0" header is forced on a
   request.

With these rules, we fit the best to the user expectations.

This patch depends on the following commit:

  * MINOR: mux-h1: Add a flag to ignore the request payload

This patch should fix the issue #2536. It should be backported it to 2.9
with the commit above.
@capflam
Copy link
Member

capflam commented May 17, 2024

Just to let you know, I pushed a patch for this issue. It is planned to backport it to 2.9. You should now be able to remove content-length or transfer-encoding headers via a del-header action. If this happens on a request, on server side, a content-length: 0 header is added and the payload is skipped. For the response, on client side, we fallback on the close mode.

It only affect the message representation on the outgoing side. On incoming side, the producer must still comply to the announced headers.

@capflam
Copy link
Member

capflam commented May 17, 2024

As a side note, this only concerns the H1 multiplexer. I must still check other HTTP muxes (for instance FCGI), to be sure there is no issue for them. In addition, for the 3.1, I will try to invest some time to support, as far as possible, changes about these headers (For instance, by reducing the content-length value). But, this part remains a big foggy for now.

@capflam capflam added status: feedback required The developers are waiting for a reply from the reporter. status: fixed This issue is a now-fixed bug. and removed status: reviewed This issue was reviewed. A fix is required. labels May 17, 2024
@rnsanchez
Copy link
Contributor Author

Fix confirmed on v3.0-dev11-53-gd33a5f8e1.

Thank you for your great work, as usual! :-)

@capflam
Copy link
Member

capflam commented May 21, 2024

Thanks for the feedback, as usual ! :-)

@capflam capflam removed dev This issue affects the HAProxy development branch. status: feedback required The developers are waiting for a reply from the reporter. labels May 21, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
2.9 This issue affects the HAProxy 2.9 stable branch. status: fixed This issue is a now-fixed bug. type: bug This issue describes a bug.
Projects
None yet
Development

No branches or pull requests

3 participants