Issue
Java 11's new HttpClient
doesn't work with create-react-app
's React development server (which starts via npm start
). The client hangs indefinitely w/o any error.
It works with other URLs https://google.com
and other local servers like
- Node.js servers. for example with
http-server
(npm install http-server -g) - Python server (
python3 -m http.server 3000
)
Any idea why this happens with React development server?
To reproduce the state:
- create a react app via
npx create-react-app my-app
(you just need Node.js ,npx
is a part of it) - then run it
cd my-app
,npm start
. The development server starts onhttp://localhost:3000/
- then open Java console:
jshell
While React development server is working open a jShell
and copy/paste the following code:
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
var httpClient = HttpClient.newHttpClient();
var request = HttpRequest.newBuilder(URI.create("http://localhost:3000")).build();
var response = httpClient.send(request, HttpResponse.BodyHandlers.ofString());
after that it hangs there indefinitely.
When try this from a Java app in order to see the logs with -Djdk.internal.httpclient.debug=true
parameter the logs are as follows:
DEBUG: [http-nio-8080-exec-1] [327ms] HttpClientImpl(1) proxySelector is sun.net.spi.DefaultProxySelector@1ad21384 (user-supplied=false)
DEBUG: [http-nio-8080-exec-1] [453ms] HttpClientImpl(1) ClientImpl (async) send http://localhost:3000/ GET
DEBUG: [http-nio-8080-exec-1] [476ms] Exchange establishing exchange for http://localhost:3000/ GET,
proxy=null
DEBUG: [http-nio-8080-exec-1] [514ms] Http2ClientImpl not found in connection pool
DEBUG: [http-nio-8080-exec-1] [514ms] ExchangeImpl get: Trying to get HTTP/2 connection
DEBUG: [http-nio-8080-exec-1] [515ms] ExchangeImpl handling HTTP/2 connection creation result
DEBUG: [http-nio-8080-exec-1] [515ms] ExchangeImpl new Http1Exchange, try to upgrade
DEBUG: [http-nio-8080-exec-1] [535ms] PlainHttpConnection(?) Initial receive buffer size is: 65536
DEBUG: [http-nio-8080-exec-1] [564ms] Exchange checkFor407: all clear
DEBUG: [http-nio-8080-exec-1] [564ms] Http1Exchange Sending headers only
DEBUG: [http-nio-8080-exec-1] [610ms] Http1AsyncReceiver(SocketTube(1)) Subscribed pending jdk.internal.net.http.Http1Response$HeadersReader@19d6f6a9 queue.isEmpty: true
DEBUG: [http-nio-8080-exec-1] [655ms] Http1AsyncReceiver(SocketTube(1)) delegate is now jdk.internal.net.http.Http1Response$HeadersReader@19d6f6a9, demand=1, canRequestMore=true, queue.isEmpty=true
DEBUG: [http-nio-8080-exec-1] [656ms] Http1AsyncReceiver(SocketTube(1)) downstream subscription demand is 1
DEBUG: [http-nio-8080-exec-1] [667ms] Http1AsyncReceiver(SocketTube(1)) checkRequestMore: canRequestMore=true, hasDemand=true
DEBUG: [http-nio-8080-exec-1] [667ms] Http1AsyncReceiver(SocketTube(1)) downstream subscription demand is 1
DEBUG: [http-nio-8080-exec-1] [667ms] Http1AsyncReceiver(SocketTube(1)) checkRequestMore: canRequestMore=true, hasDemand=true
DEBUG: [http-nio-8080-exec-1] [667ms] Http1Exchange response created in advance
DEBUG: [http-nio-8080-exec-1] [667ms] Http1Exchange initiating connect async
DEBUG: [http-nio-8080-exec-1] [676ms] PlainHttpConnection(SocketTube(1)) registering connect event
DEBUG: [HttpClient-1-SelectorManager] [678ms] SelectorAttachment Registering jdk.internal.net.http.PlainHttpConnection$ConnectEvent@340a1657 for 8 (true)
DEBUG: [HttpClient-1-SelectorManager] [679ms] PlainHttpConnection(SocketTube(1)) ConnectEvent: finishing connect
DEBUG: [HttpClient-1-SelectorManager] [680ms] PlainHttpConnection(SocketTube(1)) ConnectEvent: connect finished: true Local addr: /127.0.0.1:58174
DEBUG: [HttpClient-1-Worker-0] [709ms] PlainHttpConnection(SocketTube(1)) finishConnect, setting connected=true
DEBUG: [HttpClient-1-Worker-0] [710ms] Http1Exchange SocketTube(1) connecting flows
DEBUG: [HttpClient-1-Worker-0] [710ms] SocketTube(1) connecting flows
DEBUG: [HttpClient-1-Worker-0] [711ms] SocketTube(1) read publisher got subscriber
DEBUG: [HttpClient-1-Worker-0] [711ms] SocketTube(1) registering subscribe event
DEBUG: [HttpClient-1-Worker-0] [711ms] SocketTube(1) leaving read.subscribe: Reading: [ops=0, demand=0, stopped=false], Writing: [ops=0, demand=0]
DEBUG: [HttpClient-1-Worker-0] [711ms] Http1Publisher(SocketTube(1)) got subscriber: SocketTube(1)
DEBUG: [HttpClient-1-Worker-0] [712ms] SocketTube(1) subscribed for writing
DEBUG: [HttpClient-1-Worker-0] [712ms] SocketTube(1) write: registering startSubscription event
DEBUG: [HttpClient-1-Worker-0] [712ms] Http1Exchange requestAction.headers
DEBUG: [HttpClient-1-Worker-0] [714ms] Http1Exchange setting outgoing with headers
DEBUG: [HttpClient-1-SelectorManager] [715ms] SocketTube(1) subscribe event raised
DEBUG: [HttpClient-1-SelectorManager] [715ms] SocketTube(1) handling pending subscription for jdk.internal.net.http.Http1AsyncReceiver$Http1TubeSubscriber@4c1c390e
DEBUG: [HttpClient-1-SelectorManager] [716ms] SocketTube(1) read demand reset to 0
DEBUG: [HttpClient-1-SelectorManager] [722ms] SocketTube(1) calling onSubscribe
DEBUG: [HttpClient-1-SelectorManager] [722ms] Http1AsyncReceiver(SocketTube(1)) Received onSubscribed from upstream
DEBUG: [HttpClient-1-SelectorManager] [732ms] SocketTube(1) onSubscribe called
DEBUG: [HttpClient-1-SelectorManager] [732ms] SocketTube(1) pending subscriber subscribed
DEBUG: [HttpClient-1-SelectorManager] [732ms] SocketTube(1) write: starting subscription
DEBUG: [HttpClient-1-SelectorManager] [732ms] SocketTube(1) write: offloading requestMore
DEBUG: [HttpClient-1-Worker-1] [733ms] Http1AsyncReceiver(SocketTube(1)) downstream subscription demand is 1
DEBUG: [HttpClient-1-Worker-1] [734ms] Http1AsyncReceiver(SocketTube(1)) checkRequestMore: canRequestMore=true, hasDemand=true
DEBUG: [HttpClient-1-Worker-1] [734ms] Http1AsyncReceiver(SocketTube(1)) Http1TubeSubscriber: requesting one more from upstream
DEBUG: [HttpClient-1-Worker-1] [734ms] SocketTube(1) got some demand for reading
DEBUG: [HttpClient-1-Worker-1] [734ms] SocketTube(1) resuming read event
DEBUG: [HttpClient-1-Worker-1] [735ms] SocketTube(1) leaving request(1): Reading: [ops=1, demand=1, stopped=false], Writing: [ops=0, demand=0]
DEBUG: [HttpClient-1-SelectorManager] [753ms] SelectorAttachment Registering jdk.internal.net.http.SocketTube$InternalReadPublisher$ReadEvent@3d7cf066 for 1 (true)
DEBUG: [HttpClient-1-Worker-2] [753ms] SocketTube(1) write: requesting more...
DEBUG: [HttpClient-1-Worker-2] [753ms] Http1Publisher(SocketTube(1)) subscription request(1), demand=1
DEBUG: [HttpClient-1-Worker-2] [753ms] Http1Publisher(SocketTube(1)) WriteTask
DEBUG: [HttpClient-1-Worker-0] [754ms] Http1Exchange appending to outgoing DataPair [data=[java.nio.HeapByteBuffer[pos=0 lim=205 cap=205]], throwable=null]
DEBUG: [HttpClient-1-Worker-2] [756ms] Http1Publisher(SocketTube(1)) hasOutgoing = false
DEBUG: [HttpClient-1-Worker-2] [757ms] Http1Exchange initiating completion of headersSentCF
DEBUG: [HttpClient-1-Worker-2] [757ms] Exchange checkFor407: all clear
DEBUG: [HttpClient-1-Worker-2] [757ms] Exchange sendRequestBody
DEBUG: [HttpClient-1-Worker-2] [757ms] Http1Exchange sendBodyAsync
DEBUG: [HttpClient-1-Worker-2] [757ms] Http1Exchange bodySubscriber is null
DEBUG: [HttpClient-1-Worker-2] [758ms] Http1Exchange appending to outgoing DataPair [data=[java.nio.HeapByteBuffer[pos=0 lim=0 cap=0]], throwable=null]
DEBUG: [HttpClient-1-Worker-2] [761ms] Http1Publisher(SocketTube(1)) onNext with 205 bytes
DEBUG: [HttpClient-1-Worker-2] [761ms] SocketTube(1) trying to write: 205
DEBUG: [HttpClient-1-Worker-2] [762ms] SocketTube(1) wrote: 205
DEBUG: [HttpClient-1-Worker-2] [762ms] SocketTube(1) write: requesting more...
DEBUG: [HttpClient-1-Worker-2] [763ms] Http1Publisher(SocketTube(1)) subscription request(1), demand=1
DEBUG: [HttpClient-1-Worker-2] [763ms] SocketTube(1) leaving requestMore: Reading: [ops=1, demand=1, stopped=false], Writing: [ops=0, demand=1]
DEBUG: [HttpClient-1-Worker-2] [763ms] SocketTube(1) leaving w.onNext Reading: [ops=1, demand=1, stopped=false], Writing: [ops=0, demand=1]
DEBUG: [HttpClient-1-Worker-2] [780ms] Http1Exchange initiating completion of bodySentCF
DEBUG: [HttpClient-1-Worker-2] [780ms] Http1Exchange sendBodyAsync completed successfully
DEBUG: [HttpClient-1-Worker-2] [780ms] Http1Exchange reading headers
DEBUG: [HttpClient-1-Worker-2] [814ms] Http1Response(id=1, PlainHttpConnection(SocketTube(1))) Reading Headers: (remaining: 0) READING_HEADERS
DEBUG: [HttpClient-1-Worker-2] [814ms] Http1Response(id=1, PlainHttpConnection(SocketTube(1))) First time around
DEBUG: [HttpClient-1-Worker-2] [815ms] Http1Response(id=1, PlainHttpConnection(SocketTube(1))) headersReader is not yet completed
DEBUG: [HttpClient-1-Worker-2] [816ms] Http1Publisher(SocketTube(1)) completed, stopping jdk.internal.net.http.common.SequentialScheduler@65a12c17
DEBUG: [HttpClient-1-Worker-2] [816ms] SocketTube(1) leaving requestMore: Reading: [ops=1, demand=1, stopped=false], Writing: [ops=0, demand=1]
Solution
Stumbled upon this today too! Looks like the Java client wants to try and upgrade from a HTTP/1.1 to a HTTP/2.0 connection (the header Connection: Upgrade, HTTP2-Settings
is passed to the dev server)
Forcing the client to stay HTTP/1.1 fixes the issues, here is the change for the HttpClient:
HttpClient.newBuilder().version(Version.HTTP_1_1).build()
Answered By - Erik L
Answer Checked By - Terry (JavaFixing Volunteer)