HttpURLConnection automatic retry mechanism causes the request to be repeated twice.
Problem Analysis And Positioning
HttpURLConnection uses a Sun private HTTP protocol implementation class: HttpClient.java
The key is the following method of sending a request and parsing the response header:
569 /** Parse the first line of the HTTP request. It usually looks
570 something like: "HTTP/1.0 <number> comment\r\n". */
571
572 public boolean parseHTTP(MessageHeader responses, ProgressSource pi, HttpURLConnection httpuc)
573 throws IOException {
574 /* If "HTTP/*" is found in the beginning, return true. Let
575 * HttpURLConnection parse the mime header itself.
576 *
577 * If this isn't valid HTTP, then we don't try to parse a header
578 * out of the beginning of the response into the responses,
579 * and instead just queue up the output stream to it's very beginning.
580 * This seems most reasonable, and is what the NN browser does.
581 */
582
583 try {
584 serverInput = serverSocket.getInputStream();
585 if (capture != null) {
586 serverInput = new HttpCaptureInputStream(serverInput, capture);
587 }
588 serverInput = new BufferedInputStream(serverInput);
589 return (parseHTTPHeader(responses, pi, httpuc));
590 } catch (SocketTimeoutException stex) {
591 // We don't want to retry the request when the app. sets a timeout
592 // but don't close the server if timeout while waiting for 100-continue
593 if (ignoreContinue) {
594 closeServer();
595 }
596 throw stex;
597 } catch (IOException e) {
598 closeServer();
599 cachedHttpClient = false;
600 if (!failedOnce && requests != null) {
601 failedOnce = true;
602 if (httpuc.getRequestMethod().equals("POST") && (!retryPostProp || streaming)) {
603 // do not retry the request
604 } else {
605 // try once more
606 openServer();
607 if (needsTunneling()) {
608 httpuc.doTunneling();
609 }
610 afterConnect();
611 writeRequests(requests, poster);
612 return parseHTTP(responses, pi, httpuc);
613 }
614 }
615 throw e;
616 }
617
618 }
In the code on lines 600 - 614:
failedOnce is false by default, indicating whether it has failed once. This also limits sending a maximum of 2 requests.
httpuc is request related information.
The default value of retryPostProp is true, and the value can be specified by command line parameter (-Dsun.net.http.retryPost=false).
streaming: Default false. true if we are in streaming mode (fixed length or chunked).
Use the Linux command socat tcp4-listen:8080,fork,reuseaddr system:“sleep 1”!!stdout to establish an HTTP server that only receives requests and does not return responses.
For a POST request, after the first request is sent, the parsing response will encounter an early end of the stream, which is a SocketException: Unexpected end of file from server. After parseHTTP captures it and finds that the above conditions are met, it will retry. The server will receive the second request.
Solution
Disable the retry mechanism of HttpURLConnection.
Via launcher command parameter -Dsun.net.http.retryPost=false
or code to set System.setProperty("sun.net.http.retryPost", "false")
Use the Apache HttpComponents library.
By default, HttpClient attempts to automatically recover from I/O exceptions. This automatic recovery mechanism is limited to a few exceptions that are considered safe.
HttpClient will not attempt to recover from any logical or HTTP protocol errors;
HttpClient will automatically retry methods that are considered idempotent;
HttpClient will automatically retry methods that still fail to send HTTP requests to the target server. (For example, the request has not been fully transmitted to the server).