Changeset 15229 in josm


Ignore:
Timestamp:
2019-07-07T23:06:26+02:00 (5 years ago)
Author:
Don-vip
Message:

see #17861 - Refactor HTTP client to support HTTP/2 in a new plugin requiring Java 11

Location:
trunk/src/org/openstreetmap/josm
Files:
1 added
2 edited

Legend:

Unmodified
Added
Removed
  • trunk/src/org/openstreetmap/josm/gui/MainApplication.java

    r15222 r15229  
    150150import org.openstreetmap.josm.tools.FontsManager;
    151151import org.openstreetmap.josm.tools.GBC;
     152import org.openstreetmap.josm.tools.Http1Client;
     153import org.openstreetmap.josm.tools.HttpClient;
    152154import org.openstreetmap.josm.tools.I18n;
    153155import org.openstreetmap.josm.tools.ImageProvider;
     
    10421044
    10431045    static void setupCallbacks() {
     1046        HttpClient.setFactory(Http1Client::new);
    10441047        OsmConnection.setOAuthAccessTokenFetcher(OAuthAuthorizationWizard::obtainAccessToken);
    10451048        AbstractCredentialsAgent.setCredentialsProvider(CredentialDialog::promptCredentials);
  • trunk/src/org/openstreetmap/josm/tools/HttpClient.java

    r14522 r15229  
    44import static org.openstreetmap.josm.tools.I18n.tr;
    55
    6 import java.io.BufferedOutputStream;
    76import java.io.BufferedReader;
    8 import java.io.ByteArrayInputStream;
    97import java.io.IOException;
    108import java.io.InputStream;
    11 import java.io.OutputStream;
    129import java.net.CookieHandler;
    1310import java.net.CookieManager;
     
    1613import java.net.URL;
    1714import java.nio.charset.StandardCharsets;
    18 import java.util.Collections;
    1915import java.util.List;
    2016import java.util.Locale;
    2117import java.util.Map;
    22 import java.util.Map.Entry;
    23 import java.util.NoSuchElementException;
    24 import java.util.Optional;
     18import java.util.Objects;
    2519import java.util.Scanner;
    2620import java.util.TreeMap;
     
    3024import java.util.zip.GZIPInputStream;
    3125
    32 import org.openstreetmap.josm.data.Version;
    3326import org.openstreetmap.josm.data.validation.routines.DomainValidator;
    3427import org.openstreetmap.josm.gui.progress.NullProgressMonitor;
     
    3730import org.openstreetmap.josm.io.NetworkManager;
    3831import org.openstreetmap.josm.io.ProgressInputStream;
    39 import org.openstreetmap.josm.io.ProgressOutputStream;
    4032import org.openstreetmap.josm.io.UTFInputStreamReader;
    4133import org.openstreetmap.josm.io.auth.DefaultAuthenticator;
     
    4638 * @since 9168
    4739 */
    48 public final class HttpClient {
     40public abstract class HttpClient {
     41
     42    /**
     43     * HTTP client factory.
     44     * @since 15229
     45     */
     46    @FunctionalInterface
     47    public interface HttpClientFactory {
     48        /**
     49         * Creates a new instance for the given URL and a {@code GET} request
     50         *
     51         * @param url the URL
     52         * @param requestMethod the HTTP request method to perform when calling
     53         * @return a new instance
     54         */
     55        HttpClient create(URL url, String requestMethod);
     56    }
    4957
    5058    private URL url;
     
    5967    private String reasonForRequest;
    6068    private String outputMessage = tr("Uploading data ...");
    61     private HttpURLConnection connection; // to allow disconnecting before `response` is set
    6269    private Response response;
    6370    private boolean finishOnCloseOutput = true;
     
    7178        Pattern.CASE_INSENSITIVE);
    7279
     80    private static HttpClientFactory factory;
     81
    7382    static {
    7483        try {
     
    7988    }
    8089
    81     private HttpClient(URL url, String requestMethod) {
     90    /**
     91     * Registers a new HTTP client factory.
     92     * @param newFactory new HTTP client factory
     93     * @since 15229
     94     */
     95    public static void setFactory(HttpClientFactory newFactory) {
     96        factory = Objects.requireNonNull(newFactory);
     97    }
     98
     99    /**
     100     * Constructs a new {@code HttpClient}.
     101     * @param url URL to access
     102     * @param requestMethod HTTP request method (GET, POST, PUT, DELETE...)
     103     */
     104    protected HttpClient(URL url, String requestMethod) {
    82105        try {
    83106            String host = url.getHost();
     
    96119     * @throws IOException if any I/O error occurs
    97120     */
    98     public Response connect() throws IOException {
     121    public final Response connect() throws IOException {
    99122        return connect(null);
    100123    }
     
    107130     * @since 9179
    108131     */
    109     public Response connect(ProgressMonitor progressMonitor) throws IOException {
     132    public final Response connect(ProgressMonitor progressMonitor) throws IOException {
    110133        if (progressMonitor == null) {
    111134            progressMonitor = NullProgressMonitor.INSTANCE;
    112135        }
    113         final HttpURLConnection connection = (HttpURLConnection) url.openConnection();
    114         this.connection = connection;
    115         connection.setRequestMethod(requestMethod);
    116         connection.setRequestProperty("User-Agent", Version.getInstance().getFullAgentString());
    117         connection.setConnectTimeout(connectTimeout);
    118         connection.setReadTimeout(readTimeout);
    119         connection.setInstanceFollowRedirects(false); // we do that ourselves
    120         if (ifModifiedSince > 0) {
    121             connection.setIfModifiedSince(ifModifiedSince);
    122         }
    123         connection.setUseCaches(useCache);
    124         if (!useCache) {
    125             connection.setRequestProperty("Cache-Control", "no-cache");
    126         }
    127         for (Map.Entry<String, String> header : headers.entrySet()) {
    128             if (header.getValue() != null) {
    129                 connection.setRequestProperty(header.getKey(), header.getValue());
    130             }
    131         }
    132 
    133         progressMonitor.beginTask(tr("Contacting Server..."), 1);
    134         progressMonitor.indeterminateSubTask(null);
    135 
    136         if ("PUT".equals(requestMethod) || "POST".equals(requestMethod) || "DELETE".equals(requestMethod)) {
    137             Logging.info("{0} {1} ({2}) ...", requestMethod, url, Utils.getSizeString(requestBody.length, Locale.getDefault()));
    138             if (Logging.isTraceEnabled() && requestBody.length > 0) {
    139                 Logging.trace("BODY: {0}", new String(requestBody, StandardCharsets.UTF_8));
    140             }
    141             connection.setFixedLengthStreamingMode(requestBody.length);
    142             connection.setDoOutput(true);
    143             try (OutputStream out = new BufferedOutputStream(
    144                     new ProgressOutputStream(connection.getOutputStream(), requestBody.length,
    145                             progressMonitor, outputMessage, finishOnCloseOutput))) {
    146                 out.write(requestBody);
    147             }
    148         }
     136        setupConnection(progressMonitor);
    149137
    150138        boolean successfulConnection = false;
    151139        try {
     140            ConnectionResponse cr;
    152141            try {
    153                 connection.connect();
     142                cr = performConnection();
    154143                final boolean hasReason = reasonForRequest != null && !reasonForRequest.isEmpty();
    155                 Logging.info("{0} {1}{2} -> {3}{4}",
    156                         requestMethod, url, hasReason ? (" (" + reasonForRequest + ')') : "",
    157                         connection.getResponseCode(),
    158                         connection.getContentLengthLong() > 0
    159                                 ? (" (" + Utils.getSizeString(connection.getContentLengthLong(), Locale.getDefault()) + ')')
     144                Logging.info("{0} {1}{2} -> {3} {4}{5}",
     145                        getRequestMethod(), getURL(), hasReason ? (" (" + reasonForRequest + ')') : "",
     146                        cr.getResponseVersion(), cr.getResponseCode(),
     147                        cr.getContentLengthLong() > 0
     148                                ? (" (" + Utils.getSizeString(cr.getContentLengthLong(), Locale.getDefault()) + ')')
    160149                                : ""
    161150                );
    162151                if (Logging.isDebugEnabled()) {
    163152                    try {
    164                         Logging.debug("RESPONSE: {0}", connection.getHeaderFields());
     153                        Logging.debug("RESPONSE: {0}", cr.getHeaderFields());
    165154                    } catch (IllegalArgumentException e) {
    166155                        Logging.warn(e);
    167156                    }
    168157                }
    169                 if (DefaultAuthenticator.getInstance().isEnabled() && connection.getResponseCode() == HttpURLConnection.HTTP_UNAUTHORIZED) {
     158                if (DefaultAuthenticator.getInstance().isEnabled() && cr.getResponseCode() == HttpURLConnection.HTTP_UNAUTHORIZED) {
    170159                    DefaultAuthenticator.getInstance().addFailedCredentialHost(url.getHost());
    171160                }
    172             } catch (IOException | IllegalArgumentException | NoSuchElementException e) {
     161            } catch (IOException | RuntimeException e) {
    173162                Logging.info("{0} {1} -> !!!", requestMethod, url);
    174163                Logging.warn(e);
     
    177166                throw e;
    178167            }
    179             if (isRedirect(connection.getResponseCode())) {
    180                 final String redirectLocation = connection.getHeaderField("Location");
     168            if (isRedirect(cr.getResponseCode())) {
     169                final String redirectLocation = cr.getHeaderField("Location");
    181170                if (redirectLocation == null) {
    182171                    /* I18n: argument is HTTP response code */
    183172                    throw new IOException(tr("Unexpected response from HTTP server. Got {0} response without ''Location'' header." +
    184                             " Can''t redirect. Aborting.", connection.getResponseCode()));
     173                            " Can''t redirect. Aborting.", cr.getResponseCode()));
    185174                } else if (maxRedirects > 0) {
    186175                    url = new URL(url, redirectLocation);
     
    193182                }
    194183            }
    195             response = new Response(connection, progressMonitor);
     184            response = buildResponse(progressMonitor);
    196185            successfulConnection = true;
    197186            return response;
    198187        } finally {
    199188            if (!successfulConnection) {
    200                 connection.disconnect();
     189                performDisconnection();
    201190            }
     191        }
     192    }
     193
     194    protected abstract void setupConnection(ProgressMonitor progressMonitor) throws IOException;
     195
     196    protected abstract ConnectionResponse performConnection() throws IOException;
     197
     198    protected abstract void performDisconnection() throws IOException;
     199
     200    protected abstract Response buildResponse(ProgressMonitor progressMonitor) throws IOException;
     201
     202    protected final void notifyConnect(ProgressMonitor progressMonitor) {
     203        progressMonitor.beginTask(tr("Contacting Server..."), 1);
     204        progressMonitor.indeterminateSubTask(null);
     205    }
     206
     207    protected final void logRequestBody() {
     208        Logging.info("{0} {1} ({2}) ...", requestMethod, url, Utils.getSizeString(requestBody.length, Locale.getDefault()));
     209        if (Logging.isTraceEnabled() && hasRequestBody()) {
     210            Logging.trace("BODY: {0}", new String(requestBody, StandardCharsets.UTF_8));
    202211        }
    203212    }
     
    210219     * @since 9309
    211220     */
    212     public Response getResponse() {
     221    public final Response getResponse() {
    213222        return response;
    214223    }
    215224
    216225    /**
     226     * A wrapper for the HTTP connection response.
     227     * @since 15229
     228     */
     229    public interface ConnectionResponse {
     230        /**
     231         * Gets the HTTP version from the HTTP response.
     232         * @return the HTTP version from the HTTP response
     233         */
     234        String getResponseVersion();
     235
     236        /**
     237         * Gets the status code from an HTTP response message.
     238         * For example, in the case of the following status lines:
     239         * <PRE>
     240         * HTTP/1.0 200 OK
     241         * HTTP/1.0 401 Unauthorized
     242         * </PRE>
     243         * It will return 200 and 401 respectively.
     244         * Returns -1 if no code can be discerned
     245         * from the response (i.e., the response is not valid HTTP).
     246         * @return the HTTP Status-Code, or -1
     247         * @throws IOException if an error occurred connecting to the server.
     248         */
     249        int getResponseCode() throws IOException;
     250
     251        /**
     252         * Returns the value of the {@code content-length} header field as a long.
     253         *
     254         * @return  the content length of the resource that this connection's URL
     255         *          references, or {@code -1} if the content length is not known.
     256         */
     257        long getContentLengthLong();
     258
     259        /**
     260         * Returns an unmodifiable Map of the header fields.
     261         * The Map keys are Strings that represent the response-header field names.
     262         * Each Map value is an unmodifiable List of Strings that represents
     263         * the corresponding field values.
     264         *
     265         * @return a Map of header fields
     266         */
     267        Map<String, List<String>> getHeaderFields();
     268
     269        /**
     270         * Returns the value of the named header field.
     271         * @param name the name of a header field.
     272         * @return the value of the named header field, or {@code null}
     273         *          if there is no such field in the header.
     274         */
     275        String getHeaderField(String name);
     276    }
     277
     278    /**
    217279     * A wrapper for the HTTP response.
    218280     */
    219     public static final class Response {
    220         private final HttpURLConnection connection;
     281    public abstract static class Response {
    221282        private final ProgressMonitor monitor;
    222283        private final int responseCode;
     
    226287        private String responseData;
    227288
    228         private Response(HttpURLConnection connection, ProgressMonitor monitor) throws IOException {
    229             CheckParameterUtil.ensureParameterNotNull(connection, "connection");
    230             CheckParameterUtil.ensureParameterNotNull(monitor, "monitor");
    231             this.connection = connection;
    232             this.monitor = monitor;
    233             this.responseCode = connection.getResponseCode();
    234             this.responseMessage = connection.getResponseMessage();
    235             if (this.responseCode >= 300) {
     289        protected Response(ProgressMonitor monitor, int responseCode, String responseMessage) {
     290            this.monitor = Objects.requireNonNull(monitor, "monitor");
     291            this.responseCode = responseCode;
     292            this.responseMessage = responseMessage;
     293        }
     294
     295        protected final void debugRedirect() throws IOException {
     296            if (responseCode >= 300) {
    236297                String contentType = getContentType();
    237                 if (contentType == null || (
    238                         contentType.contains("text") ||
    239                         contentType.contains("html") ||
    240                         contentType.contains("xml"))
    241                         ) {
    242                     String content = this.fetchContent();
    243                     if (content.isEmpty()) {
    244                         Logging.debug("Server did not return any body");
    245                     } else {
    246                         Logging.debug("Response body: ");
    247                         Logging.debug(this.fetchContent());
    248                     }
     298                if (contentType == null ||
     299                    contentType.contains("text") ||
     300                    contentType.contains("html") ||
     301                    contentType.contains("xml")
     302                ) {
     303                    String content = fetchContent();
     304                    Logging.debug(content.isEmpty() ? "Server did not return any body" : "Response body: \n" + content);
    249305                } else {
    250                     Logging.debug("Server returned content: {0} of length: {1}. Not printing.", contentType, this.getContentLength());
     306                    Logging.debug("Server returned content: {0} of length: {1}. Not printing.", contentType, getContentLength());
    251307                }
    252308            }
     
    259315         * @return {@code this}
    260316         */
    261         public Response uncompress(boolean uncompress) {
     317        public final Response uncompress(boolean uncompress) {
    262318            this.uncompress = uncompress;
    263319            return this;
     
    272328         * @since 9172
    273329         */
    274         public Response uncompressAccordingToContentDisposition(boolean uncompressAccordingToContentDisposition) {
     330        public final Response uncompressAccordingToContentDisposition(boolean uncompressAccordingToContentDisposition) {
    275331            this.uncompressAccordingToContentDisposition = uncompressAccordingToContentDisposition;
    276332            return this;
     
    283339         * @since 9172
    284340         */
    285         public URL getURL() {
    286             return connection.getURL();
    287         }
     341        public abstract URL getURL();
    288342
    289343        /**
     
    293347         * @since 9172
    294348         */
    295         public String getRequestMethod() {
    296             return connection.getRequestMethod();
    297         }
     349        public abstract String getRequestMethod();
    298350
    299351        /**
     
    310362         */
    311363        @SuppressWarnings("resource")
    312         public InputStream getContent() throws IOException {
    313             InputStream in;
    314             try {
    315                 in = connection.getInputStream();
    316             } catch (IOException ioe) {
    317                 Logging.debug(ioe);
    318                 in = Optional.ofNullable(connection.getErrorStream()).orElseGet(() -> new ByteArrayInputStream(new byte[]{}));
    319             }
     364        public final InputStream getContent() throws IOException {
     365            InputStream in = getInputStream();
    320366            in = new ProgressInputStream(in, getContentLength(), monitor);
    321367            in = "gzip".equalsIgnoreCase(getContentEncoding()) ? new GZIPInputStream(in) : in;
     
    339385        }
    340386
     387        protected abstract InputStream getInputStream() throws IOException;
     388
    341389        /**
    342390         * Returns {@link #getContent()} wrapped in a buffered reader.
     
    346394         * @throws IOException if any I/O error occurs
    347395         */
    348         public BufferedReader getContentReader() throws IOException {
     396        public final BufferedReader getContentReader() throws IOException {
    349397            return new BufferedReader(
    350398                    UTFInputStreamReader.create(getContent())
     
    357405         * @throws IOException if any I/O error occurs
    358406         */
    359         public synchronized String fetchContent() throws IOException {
     407        public final synchronized String fetchContent() throws IOException {
    360408            if (responseData == null) {
    361409                try (Scanner scanner = new Scanner(getContentReader()).useDelimiter("\\A")) { // \A - beginning of input
     
    372420         * @see HttpURLConnection#getResponseCode()
    373421         */
    374         public int getResponseCode() {
     422        public final int getResponseCode() {
    375423            return responseCode;
    376424        }
     
    383431         * @since 9172
    384432         */
    385         public String getResponseMessage() {
     433        public final String getResponseMessage() {
    386434            return responseMessage;
    387435        }
     
    392440         * @see HttpURLConnection#getContentEncoding()
    393441         */
    394         public String getContentEncoding() {
    395             return connection.getContentEncoding();
    396         }
     442        public abstract String getContentEncoding();
    397443
    398444        /**
    399445         * Returns the {@code Content-Type} header.
    400446         * @return {@code Content-Type} HTTP header
    401          */
    402         public String getContentType() {
    403             return connection.getHeaderField("Content-Type");
    404         }
     447         * @see HttpURLConnection#getContentType()
     448         */
     449        public abstract String getContentType();
    405450
    406451        /**
     
    410455         * @since 9232
    411456         */
    412         public long getExpiration() {
    413             return connection.getExpiration();
    414         }
     457        public abstract long getExpiration();
    415458
    416459        /**
     
    420463         * @since 9232
    421464         */
    422         public long getLastModified() {
    423             return connection.getLastModified();
    424         }
     465        public abstract long getLastModified();
    425466
    426467        /**
     
    429470         * @see HttpURLConnection#getContentLengthLong()
    430471         */
    431         public long getContentLength() {
    432             return connection.getContentLengthLong();
    433         }
     472        public abstract long getContentLength();
    434473
    435474        /**
     
    440479         * @since 9172
    441480         */
    442         public String getHeaderField(String name) {
    443             return connection.getHeaderField(name);
    444         }
     481        public abstract String getHeaderField(String name);
    445482
    446483        /**
     
    451488         * @since 9232
    452489         */
    453         public Map<String, List<String>> getHeaderFields() {
    454             // returned map from HttpUrlConnection is case sensitive, use case insensitive TreeMap to conform to RFC 2616
    455             Map<String, List<String>> ret = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
    456             for (Entry<String, List<String>> e: connection.getHeaderFields().entrySet()) {
    457                 if (e.getKey() != null) {
    458                     ret.put(e.getKey(), e.getValue());
    459                 }
    460             }
    461             return Collections.unmodifiableMap(ret);
    462         }
     490        public abstract Map<String, List<String>> getHeaderFields();
    463491
    464492        /**
    465493         * @see HttpURLConnection#disconnect()
    466494         */
    467         public void disconnect() {
    468             HttpClient.disconnect(connection);
    469         }
     495        public abstract void disconnect();
    470496    }
    471497
     
    488514     */
    489515    public static HttpClient create(URL url, String requestMethod) {
    490         return new HttpClient(url, requestMethod);
     516        return factory.create(url, requestMethod);
    491517    }
    492518
     
    498524     * @since 9172
    499525     */
    500     public URL getURL() {
     526    public final URL getURL() {
    501527        return url;
     528    }
     529
     530    /**
     531     * Returns the request body set for this connection.
     532     * @return the HTTP request body, or null
     533     * @since 15229
     534     */
     535    public final byte[] getRequestBody() {
     536        return requestBody;
     537    }
     538
     539    /**
     540     * Determines if a non-empty request body has been set for this connection.
     541     * @return {@code true} if the request body is set and non-empty
     542     * @since 15229
     543     */
     544    public final boolean hasRequestBody() {
     545        return requestBody != null && requestBody.length > 0;
     546    }
     547
     548    /**
     549     * Determines if the underlying HTTP method requires a body.
     550     * @return {@code true} if the underlying HTTP method requires a body
     551     * @since 15229
     552     */
     553    public final boolean requiresBody() {
     554        return "PUT".equals(requestMethod) || "POST".equals(requestMethod) || "DELETE".equals(requestMethod);
    502555    }
    503556
     
    508561     * @since 9172
    509562     */
    510     public String getRequestMethod() {
     563    public final String getRequestMethod() {
    511564        return requestMethod;
    512565    }
     
    518571     * @since 9172
    519572     */
    520     public String getRequestHeader(String header) {
     573    public final String getRequestHeader(String header) {
    521574        return headers.get(header);
    522575    }
    523576
    524577    /**
     578     * Returns the connect timeout.
     579     * @return the connect timeout, in milliseconds
     580     * @since 15229
     581     */
     582    public final int getConnectTimeout() {
     583        return connectTimeout;
     584    }
     585
     586    /**
     587     * Returns the read timeout.
     588     * @return the read timeout, in milliseconds
     589     * @since 15229
     590     */
     591    public final int getReadTimeout() {
     592        return readTimeout;
     593    }
     594
     595    /**
     596     * Returns the {@code If-Modified-Since} header value.
     597     * @return the {@code If-Modified-Since} header value
     598     * @since 15229
     599     */
     600    public final long getIfModifiedSince() {
     601        return ifModifiedSince;
     602    }
     603
     604    /**
     605     * Determines whether not to set header {@code Cache-Control=no-cache}
     606     * @return whether not to set header {@code Cache-Control=no-cache}
     607     * @since 15229
     608     */
     609    public final boolean isUseCache() {
     610        return useCache;
     611    }
     612
     613    /**
     614     * Returns the headers.
     615     * @return the headers
     616     * @since 15229
     617     */
     618    public final Map<String, String> getHeaders() {
     619        return headers;
     620    }
     621
     622    /**
     623     * Returns the reason for request.
     624     * @return the reason for request
     625     * @since 15229
     626     */
     627    public final String getReasonForRequest() {
     628        return reasonForRequest;
     629    }
     630
     631    /**
     632     * Returns the output message.
     633     * @return the output message
     634     */
     635    protected final String getOutputMessage() {
     636        return outputMessage;
     637    }
     638
     639    /**
     640     * Determines whether the progress monitor task will be finished when the output stream is closed. {@code true} by default.
     641     * @return the finishOnCloseOutput
     642     */
     643    protected final boolean isFinishOnCloseOutput() {
     644        return finishOnCloseOutput;
     645    }
     646
     647    /**
    525648     * Sets whether not to set header {@code Cache-Control=no-cache}
    526649     *
     
    529652     * @see HttpURLConnection#setUseCaches(boolean)
    530653     */
    531     public HttpClient useCache(boolean useCache) {
     654    public final HttpClient useCache(boolean useCache) {
    532655        this.useCache = useCache;
    533656        return this;
     
    543666     * @return {@code this}
    544667     */
    545     public HttpClient keepAlive(boolean keepAlive) {
     668    public final HttpClient keepAlive(boolean keepAlive) {
    546669        return setHeader("Connection", keepAlive ? null : "close");
    547670    }
     
    555678     * @see HttpURLConnection#setConnectTimeout(int)
    556679     */
    557     public HttpClient setConnectTimeout(int connectTimeout) {
     680    public final HttpClient setConnectTimeout(int connectTimeout) {
    558681        this.connectTimeout = connectTimeout;
    559682        return this;
     
    568691     * @see HttpURLConnection#setReadTimeout(int)
    569692     */
    570     public HttpClient setReadTimeout(int readTimeout) {
     693    public final HttpClient setReadTimeout(int readTimeout) {
    571694        this.readTimeout = readTimeout;
    572695        return this;
     
    579702     * @return {@code this}
    580703     */
    581     public HttpClient setAccept(String accept) {
     704    public final HttpClient setAccept(String accept) {
    582705        return setHeader("Accept", accept);
    583706    }
     
    589712     * @return {@code this}
    590713     */
    591     public HttpClient setRequestBody(byte[] requestBody) {
     714    public final HttpClient setRequestBody(byte[] requestBody) {
    592715        this.requestBody = Utils.copyArray(requestBody);
    593716        return this;
     
    600723     * @return {@code this}
    601724     */
    602     public HttpClient setIfModifiedSince(long ifModifiedSince) {
     725    public final HttpClient setIfModifiedSince(long ifModifiedSince) {
    603726        this.ifModifiedSince = ifModifiedSince;
    604727        return this;
     
    614737     * @return {@code this}
    615738     */
    616     public HttpClient setMaxRedirects(int maxRedirects) {
     739    public final HttpClient setMaxRedirects(int maxRedirects) {
    617740        this.maxRedirects = maxRedirects;
    618741        return this;
     
    626749     * @return {@code this}
    627750     */
    628     public HttpClient setHeader(String key, String value) {
     751    public final HttpClient setHeader(String key, String value) {
    629752        this.headers.put(key, value);
    630753        return this;
     
    637760     * @return {@code this}
    638761     */
    639     public HttpClient setHeaders(Map<String, String> headers) {
     762    public final HttpClient setHeaders(Map<String, String> headers) {
    640763        this.headers.putAll(headers);
    641764        return this;
     
    648771     * @since 9172
    649772     */
    650     public HttpClient setReasonForRequest(String reasonForRequest) {
     773    public final HttpClient setReasonForRequest(String reasonForRequest) {
    651774        this.reasonForRequest = reasonForRequest;
    652775        return this;
     
    660783     * @since 12711
    661784     */
    662     public HttpClient setOutputMessage(String outputMessage) {
     785    public final HttpClient setOutputMessage(String outputMessage) {
    663786        this.outputMessage = outputMessage;
    664787        return this;
     
    671794     * @since 10302
    672795     */
    673     public HttpClient setFinishOnCloseOutput(boolean finishOnCloseOutput) {
     796    public final HttpClient setFinishOnCloseOutput(boolean finishOnCloseOutput) {
    674797        this.finishOnCloseOutput = finishOnCloseOutput;
    675798        return this;
     
    690813
    691814    /**
     815     * Disconnect client.
    692816     * @see HttpURLConnection#disconnect()
    693817     * @since 9309
    694818     */
    695     public void disconnect() {
    696         HttpClient.disconnect(connection);
    697     }
    698 
    699     private static void disconnect(final HttpURLConnection connection) {
    700         if (connection != null) {
    701             // Fix upload aborts - see #263
    702             connection.setConnectTimeout(100);
    703             connection.setReadTimeout(100);
    704             try {
    705                 Thread.sleep(100);
    706             } catch (InterruptedException ex) {
    707                 Logging.warn("InterruptedException in " + HttpClient.class + " during cancel");
    708                 Thread.currentThread().interrupt();
    709             }
    710             connection.disconnect();
    711         }
    712     }
     819    public abstract void disconnect();
    713820
    714821    /**
Note: See TracChangeset for help on using the changeset viewer.