Hi,
I've discovered it is possible to make haproxy (2.0.9 and 2.0.10, at least)
produce duplicate headers using the set-header directive (which is defined as
"This does the same as "http-request add-header" except that the header name
is first removed if it existed.").
This only happens when HTX is enabled. The essential part is that the variable
substitution uses values that are not set.
Here is my configuration, narrowed down to its essentials;
defaults
mode http
option http-use-htx
timeout client 30s
timeout connect 5s
timeout queue 5s
timeout server 30s
timeout tarpit 10s
frontend http-in
bind 127.0.0.1:5984
acl header_is_set hdr_len(X-Header) gt 0
http-request set-header X-Header %[hdr(x-foo)] unless header_is_set
http-request set-header X-Header %[hdr(x-bar)] unless header_is_set
http-request set-header X-Header baz unless header_is_set
default_backend http-out
backend http-out
server db1 127.0.0.1:15984 check inter 1s
Here is the non-HTX output;
GET / HTTP/1.1
Host: localhost:5984
User-Agent: curl/7.64.1
Accept: */*
X-Header: baz
Here is the HTX output;
GET / HTTP/1.1
host: localhost:5984
user-agent: curl/7.64.1
accept: */*
x-header:
x-header:
x-header: baz
and my version info in detail;
HA-Proxy version 2.0.10 2019/11/25 - https://haproxy.org/
Build options :
TARGET = linux-glibc
CPU = generic
CC = gcc
CFLAGS = -m64 -march=x86-64 -O2 -g -fno-strict-aliasing
-Wdeclaration-after-statement -fwrapv -Wno-unused-label -Wno-sign-compare
-Wno-unused-parameter -Wno-old-style-declaration -Wno-ignored-qualifiers
-Wno-clobbered -Wno-missing-field-initializers -Wtype-limits
OPTIONS = USE_PCRE2=1 USE_PCRE2_JIT=1 USE_THREAD=1 USE_PTHREAD_PSHARED=1
USE_REGPARM=1 USE_STATIC_PCRE2=1 USE_OPENSSL=1 USE_LUA=1 USE_SLZ=1 USE_TFO=1
USE_SYSTEMD=1
Feature list : +EPOLL -KQUEUE -MY_EPOLL -MY_SPLICE +NETFILTER -PCRE -PCRE_JIT
+PCRE2 +PCRE2_JIT +POLL -PRIVATE_CACHE +THREAD +PTHREAD_PSHARED +REGPARM
-STATIC_PCRE +STATIC_PCRE2 +TPROXY +LINUX_TPROXY +LINUX_SPLICE +LIBCRYPT
+CRYPT_H -VSYSCALL +GETADDRINFO +OPENSSL +LUA +FUTEX +ACCEPT4 -MY_ACCEPT4 -ZLIB
+SLZ +CPU_AFFINITY +TFO +NS +DL +RT -DEVICEATLAS -51DEGREES -WURFL +SYSTEMD
-OBSOLETE_LINKER +PRCTL +THREAD_DUMP -EVPORTS
Default settings :
bufsize = 16384, maxrewrite = 1024, maxpollevents = 200
Built with multi-threading support (MAX_THREADS=64, default=8).
Built with OpenSSL version : OpenSSL 1.1.1d 10 Sep 2019
Running on OpenSSL version : OpenSSL 1.1.1d 10 Sep 2019
OpenSSL library supports TLS extensions : yes
OpenSSL library supports SNI : yes
OpenSSL library supports : TLSv1.0 TLSv1.1 TLSv1.2 TLSv1.3
Built with Lua version : Lua 5.3.4
Built with network namespace support.
Built with transparent proxy support using: IP_TRANSPARENT IPV6_TRANSPARENT
IP_FREEBIND
Built with libslz for stateless compression.
Compression algorithms supported : identity("identity"), deflate("deflate"),
raw-deflate("deflate"), gzip("gzip")
Built with PCRE2 version : 10.30 2017-08-14
PCRE2 library supports JIT : yes
Encrypted password support via crypt(3): yes
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 cannot be specified using 'proto' keyword)
h2 : mode=HTXside=FE|BE mux=H2
h2 : mode=HTTP side=FEmux=H2
: mode=HTXside=FE|BE mux=H1
: mode=TCP|HTTP side=FE|BE mux=PASS
Available services : none
Available filters :
[SPOE] spoe
[COMP] compression
[CACHE] cache
[TRACE] trace