If you’ve ever tried to integrate Spring’s WebClient
with Hoverfly for mocking or capturing HTTP requests, you might have run into a baffling problem: seemingly correct HTTP calls (http://...
) resulting in cryptic TLS handshake errors in your proxy logs, and no traffic showing up in Hoverfly’s journal. What gives?
This isn’t a bug; it’s a fascinating case of protocol expectation mismatch between how WebClient
(powered by Reactor Netty) talks to a proxy, and how Hoverfly (written in Go) initially expects that conversation to begin.
Hoverfly’s Expectation (Go-based Philosophy):
Hoverfly, being a sophisticated proxy written in Go, operates on clear, traditional HTTP proxy protocols:
- For plain HTTP requests (e.g., to
http://example.com
), Hoverfly expects the client to send a direct, plain HTTP request line to the proxy (e.g.,GET http://example.com/path HTTP/1.1
). - For HTTPS requests (e.g., to
https://example.com
), Hoverfly expects the client to first send anHTTP CONNECT
request (e.g.,CONNECT example.com:443 HTTP/1.1
). This signals the client’s intent to establish a secure tunnel through the proxy, after which a TLS handshake will occur.
This is how Hoverfly differentiates between simply forwarding an HTTP request and setting up a secure tunnel for HTTPS.
Spring WebClient’s Reality (Netty’s Behavior):
On the other hand, Spring’s WebClient
, which leverages Reactor Netty as its underlying HTTP client, has a slightly different default behavior for proxying. Even for plain HTTP requests, Netty often sends an HTTP CONNECT
request to the proxy first.
This is the core of the problem!
The Misunderstanding: HTTP Request, HTTPS Assumption
When your WebClient
tries to make an HTTP call (say, to http://localhost:8090
or http://httpbin.org/anything
) through Hoverfly:
WebClient
(via Netty) sends aCONNECT
request to Hoverfly.- Hoverfly receives this
CONNECT
request. Based on its default expectations, it interprets thisCONNECT
as a signal that the client intends to establish an HTTPS tunnel. - Hoverfly then expects the next bytes it receives to be the start of a TLS handshake.
- However, your
WebClient
(for an HTTP target) immediately starts sending plain HTTP request bytes after theCONNECT
.
This mismatch leads to the confusing error you see in Hoverfly’s logs:
WARN: Cannot handshake client httpbin.org:80 tls: first record does not look like a TLS handshake
The Elegant Solution: --plain-http-tunneling
Recognizing this common client behavior (where some clients send CONNECT
for HTTP targets), the Hoverfly team thoughtfully added a flag to accommodate it.
The --plain-http-tunneling
flag tells Hoverfly to be more lenient. When this flag is enabled, Hoverfly will:
- Accept an incoming
CONNECT
request. - If the subsequent bytes are a TLS handshake, it proceeds as a normal HTTPS tunnel (MITM).
- If the subsequent bytes are a plain HTTP request (after the
CONNECT
), it will also accept them and proxy them as plain HTTP.
Reference: https://github.com/SpectoLabs/hoverfly/issues/650
docker run -d \
-p 30050:8500 \
-p 30088:8888 \
--name hoverfly \
--rm \
spectolabs/hoverfly:latest \
--proxy-port 8500 \
--admin-port 8888 \
--mode capture \
--log-level debug \
--plain-http-tunneling # <--- The game-changer flag