Ignore:
Timestamp:
2015-12-26T23:42:03+01:00 (5 years ago)
Author:
simon04
Message:

see #12231 - Use HttpClient for OSM API calls

This requires adaptors to the OAuth library: SignpostAdapters

File:
1 edited

Legend:

Unmodified
Added
Removed
  • trunk/src/org/openstreetmap/josm/tools/HttpClient.java

    r9171 r9172  
    1111import java.net.HttpURLConnection;
    1212import java.net.URL;
     13import java.util.List;
    1314import java.util.Map;
    1415import java.util.Scanner;
    15 import java.util.concurrent.ConcurrentHashMap;
     16import java.util.TreeMap;
     17import java.util.regex.Matcher;
     18import java.util.regex.Pattern;
    1619import java.util.zip.GZIPInputStream;
    1720
     
    3033    private int connectTimeout = Main.pref.getInteger("socket.timeout.connect", 15) * 1000;
    3134    private int readTimeout = Main.pref.getInteger("socket.timeout.read", 30) * 1000;
    32     private String accept;
    33     private String contentType;
    34     private String acceptEncoding = "gzip";
    35     private long contentLength;
    3635    private byte[] requestBody;
    3736    private long ifModifiedSince;
    38     private final Map<String, String> headers = new ConcurrentHashMap<>();
     37    private final Map<String, String> headers = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
    3938    private int maxRedirects = Main.pref.getInteger("socket.maxredirects", 5);
    4039    private boolean useCache;
    41     private boolean keepAlive;
     40    private String reasonForRequest;
    4241
    4342    private HttpClient(URL url, String requestMethod) {
    4443        this.url = url;
    4544        this.requestMethod = requestMethod;
     45        this.headers.put("Accept-Encoding", "gzip");
    4646    }
    4747
    4848    public Response connect() throws IOException {
    4949        final HttpURLConnection connection = (HttpURLConnection) url.openConnection();
     50        connection.setRequestMethod(requestMethod);
    5051        connection.setRequestProperty("User-Agent", Version.getInstance().getFullAgentString());
    5152        connection.setConnectTimeout(connectTimeout);
    5253        connection.setReadTimeout(readTimeout);
    53         if (accept != null) {
    54             connection.setRequestProperty("Accept", accept);
    55         }
    56         if (contentType != null) {
    57             connection.setRequestProperty("Content-Type", contentType);
    58         }
    59         if (acceptEncoding != null) {
    60             connection.setRequestProperty("Accept-Encoding", acceptEncoding);
    61         }
    62         if (contentLength > 0) {
    63             connection.setRequestProperty("Content-Length", String.valueOf(contentLength));
    64         }
     54        connection.setInstanceFollowRedirects(maxRedirects > 0);
     55        if (ifModifiedSince > 0) {
     56            connection.setIfModifiedSince(ifModifiedSince);
     57        }
     58        connection.setUseCaches(useCache);
     59        if (!useCache) {
     60            connection.setRequestProperty("Cache-Control", "no-cache");
     61        }
     62        for (Map.Entry<String, String> header : headers.entrySet()) {
     63            if (header.getValue() != null) {
     64                connection.setRequestProperty(header.getKey(), header.getValue());
     65            }
     66        }
     67
    6568        if ("PUT".equals(requestMethod) || "POST".equals(requestMethod) || "DELETE".equals(requestMethod)) {
     69            headers.put("Content-Length", String.valueOf(requestBody.length));
    6670            connection.setDoOutput(true);
    6771            try (OutputStream out = new BufferedOutputStream(connection.getOutputStream())) {
    6872                out.write(requestBody);
    6973            }
    70         }
    71         if (ifModifiedSince > 0) {
    72             connection.setIfModifiedSince(ifModifiedSince);
    73         }
    74         connection.setUseCaches(useCache);
    75         if (!useCache) {
    76             connection.setRequestProperty("Cache-Control", "no-cache");
    77         }
    78         if (!keepAlive) {
    79             connection.setRequestProperty("Connection", "close");
    80         }
    81         for (Map.Entry<String, String> header : headers.entrySet()) {
    82             connection.setRequestProperty(header.getKey(), header.getValue());
    8374        }
    8475
     
    8778            try {
    8879                connection.connect();
    89                 Main.info("{0} {1} => {2}", requestMethod, url, connection.getResponseCode());
     80                if (reasonForRequest != null && "".equalsIgnoreCase(reasonForRequest)) {
     81                    Main.info("{0} {1} ({2}) -> {3}", requestMethod, url, reasonForRequest, connection.getResponseCode());
     82                } else {
     83                    Main.info("{0} {1} -> {2}", requestMethod, url, connection.getResponseCode());
     84                }
     85                if (Main.isDebugEnabled()) {
     86                    Main.debug("RESPONSE: " + connection.getHeaderFields());
     87                }
    9088            } catch (IOException e) {
    9189                //noinspection ThrowableResultOfMethodCallIgnored
     
    105103                    Main.info(tr("Download redirected to ''{0}''", redirectLocation));
    106104                    return connect();
    107                 } else {
     105                } else if (maxRedirects == 0) {
    108106                    String msg = tr("Too many redirects to the download URL detected. Aborting.");
    109107                    throw new IOException(msg);
     
    126124        private final HttpURLConnection connection;
    127125        private final int responseCode;
     126        private final String responseMessage;
    128127        private boolean uncompress;
     128        private boolean uncompressAccordingToContentDisposition;
    129129
    130130        private Response(HttpURLConnection connection) throws IOException {
     131            CheckParameterUtil.ensureParameterNotNull(connection, "connection");
    131132            this.connection = connection;
    132133            this.responseCode = connection.getResponseCode();
     134            this.responseMessage = connection.getResponseMessage();
    133135        }
    134136
     
    144146        }
    145147
     148        public Response uncompressAccordingToContentDisposition(boolean uncompressAccordingToContentDisposition) {
     149            this.uncompressAccordingToContentDisposition = uncompressAccordingToContentDisposition;
     150            return this;
     151        }
     152
     153        /**
     154         * @see HttpURLConnection#getURL()
     155         */
     156        public URL getURL() {
     157            return connection.getURL();
     158        }
     159
     160        /**
     161         * @see HttpURLConnection#getRequestMethod()
     162         */
     163        public String getRequestMethod() {
     164            return connection.getRequestMethod();
     165        }
     166
    146167        /**
    147168         * Returns an input stream that reads from this HTTP connection, or,
    148169         * error stream if the connection failed but the server sent useful data.
     170         *
     171         * Note: the return value can be null, if both the input and the error stream are null.
     172         * Seems to be the case if the OSM server replies a 401 Unauthorized, see #3887
    149173         *
    150174         * @see HttpURLConnection#getInputStream()
     
    160184            in = "gzip".equalsIgnoreCase(getContentEncoding()) ? new GZIPInputStream(in) : in;
    161185            if (uncompress) {
    162                 return Compression.forContentType(getContentType()).getUncompressedInputStream(in);
    163             } else {
    164                 return in;
    165             }
     186                final String contentType = getContentType();
     187                Main.debug("Uncompressing input stream according to Content-Type header: {0}", contentType);
     188                in = Compression.forContentType(contentType).getUncompressedInputStream(in);
     189            }
     190            if (uncompressAccordingToContentDisposition) {
     191                final String contentDisposition = getHeaderField("Content-Disposition");
     192                final Matcher matcher = Pattern.compile("filename=\"([^\"]+)\"").matcher(contentDisposition);
     193                if (matcher.find()) {
     194                    Main.debug("Uncompressing input stream according to Content-Disposition header: {0}", contentDisposition);
     195                    in = Compression.byExtension(matcher.group(1)).getUncompressedInputStream(in);
     196                }
     197            }
     198            return in;
    166199        }
    167200
     
    183216         */
    184217        public String fetchContent() throws IOException {
    185             try (Scanner scanner = new Scanner(getContentReader())) {
    186                 return scanner.useDelimiter("\\A").next();
     218            try (Scanner scanner = new Scanner(getContentReader()).useDelimiter("\\A")) {
     219                return scanner.hasNext() ? scanner.next() : "";
    187220            }
    188221        }
     
    198231
    199232        /**
     233         * Gets the response message from this HTTP connection.
     234         *
     235         * @see HttpURLConnection#getResponseMessage()
     236         */
     237        public String getResponseMessage() {
     238            return responseMessage;
     239        }
     240
     241        /**
    200242         * Returns the {@code Content-Encoding} header.
    201243         */
     
    219261
    220262        /**
     263         * @see HttpURLConnection#getHeaderField(String)
     264         */
     265        public String getHeaderField(String name) {
     266            return connection.getHeaderField(name);
     267        }
     268
     269        /**
     270         * @see HttpURLConnection#getHeaderFields()
     271         */
     272        public List<String> getHeaderFields(String name) {
     273            return connection.getHeaderFields().get(name);
     274        }
     275
     276        /**
    221277         * @see HttpURLConnection#disconnect()
    222278         */
    223279        public void disconnect() {
     280            // TODO is this block necessary for disconnecting?
     281            // Fix upload aborts - see #263
     282            connection.setConnectTimeout(100);
     283            connection.setReadTimeout(100);
     284            try {
     285                Thread.sleep(100);
     286            } catch (InterruptedException ex) {
     287                Main.warn("InterruptedException in " + getClass().getSimpleName() + " during cancel");
     288            }
     289
    224290            connection.disconnect();
    225291        }
     
    239305     * Creates a new instance for the given URL and a {@code GET} request
    240306     *
    241      * @param url           the URL
     307     * @param url the URL
    242308     * @param requestMethod the HTTP request method to perform when calling
    243309     * @return a new instance
     
    245311    public static HttpClient create(URL url, String requestMethod) {
    246312        return new HttpClient(url, requestMethod);
     313    }
     314
     315    /**
     316     * Returns the URL set for this connection.
     317     * @see #create(URL)
     318     * @see #create(URL, String)
     319     */
     320    public URL getURL() {
     321        return url;
     322    }
     323
     324    /**
     325     * Returns the request method set for this connection.
     326     * @see #create(URL, String)
     327     */
     328    public String getRequestMethod() {
     329        return requestMethod;
     330    }
     331
     332    /**
     333     * Returns the set value for the given {@code header}.
     334     */
     335    public String getRequestHeader(String header) {
     336        return headers.get(header);
    247337    }
    248338
     
    268358     */
    269359    public HttpClient keepAlive(boolean keepAlive) {
    270         this.keepAlive = keepAlive;
    271         return this;
     360        return setHeader("Connection", keepAlive ? null : "close");
    272361    }
    273362
     
    297386     */
    298387    public HttpClient setAccept(String accept) {
    299         this.accept = accept;
    300         return this;
    301     }
    302 
    303     /**
    304      * Sets the {@code Content-Type} header.
    305      *
    306      * @return {@code this}
    307      */
    308     public HttpClient setContentType(String contentType) {
    309         this.contentType = contentType;
    310         return this;
    311     }
    312 
    313     /**
    314      * Sets the {@code Accept-Encoding} header.
    315      *
    316      * @return {@code this}
    317      */
    318     public HttpClient setAcceptEncoding(String acceptEncoding) {
    319         this.acceptEncoding = acceptEncoding;
    320         return this;
    321     }
    322 
    323     /**
    324      * Sets the {@code Content-Length} header for {@code PUT}/{@code POST} requests.
    325      *
    326      * @return {@code this}
    327      */
    328     public HttpClient setContentLength(long contentLength) {
    329         this.contentLength = contentLength;
    330         return this;
     388        return setHeader("Accept", accept);
    331389    }
    332390
     
    354412     * Sets the maximum number of redirections to follow.
    355413     *
     414     * Set {@code maxRedirects} to {@code -1} in order to ignore redirects, i.e.,
     415     * to not throw an {@link IOException} in {@link #connect()}.
     416     *
    356417     * @return {@code this}
    357418     */
     
    378439    public HttpClient setHeaders(Map<String, String> headers) {
    379440        this.headers.putAll(headers);
     441        return this;
     442    }
     443
     444    /**
     445     * Sets a reason to show on console. Can be {@code null} if no reason is given.
     446     */
     447    public HttpClient setReasonForRequest(String reasonForRequest) {
     448        this.reasonForRequest = reasonForRequest;
    380449        return this;
    381450    }
Note: See TracChangeset for help on using the changeset viewer.