Ticket #12231: 12231-alpha.patch

File 12231-alpha.patch, 23.8 KB (added by simon04, 10 years ago)
  • src/org/openstreetmap/josm/gui/help/HelpContentReader.java

    commit 3cfa8ac9b9e4b07a78ad0daa16e1f640eba36ff5
    Author: Simon Legner <Simon.Legner@gmail.com>
    Date:   Sat Dec 26 00:34:55 2015 +0100
    
        Uniform access to HTTP resources
    
    diff --git a/src/org/openstreetmap/josm/gui/help/HelpContentReader.java b/src/org/openstreetmap/josm/gui/help/HelpContentReader.java
    index 14c569c..29c886f 100644
    a b  
    33
    44import java.io.BufferedReader;
    55import java.io.IOException;
    6 import java.io.InputStreamReader;
    7 import java.net.HttpURLConnection;
    86import java.net.MalformedURLException;
    97import java.net.URL;
    10 import java.nio.charset.StandardCharsets;
    118
    12 import org.openstreetmap.josm.Main;
    13 import org.openstreetmap.josm.tools.Utils;
     9import org.openstreetmap.josm.tools.HttpClient;
    1410import org.openstreetmap.josm.tools.WikiReader;
    1511
    1612/**
    public HelpContentReader(String baseUrl) {  
    4541    public String fetchHelpTopicContent(String helpTopicUrl, boolean dotest) throws HelpContentReaderException {
    4642        if (helpTopicUrl == null)
    4743            throw new MissingHelpContentException("helpTopicUrl is null");
    48         HttpURLConnection con = null;
     44        HttpClient.Response con = null;
    4945        try {
    5046            URL u = new URL(helpTopicUrl);
    51             con = Utils.openHttpConnection(u);
    52             con.connect();
    53             try (BufferedReader in = new BufferedReader(new InputStreamReader(con.getInputStream(), StandardCharsets.UTF_8))) {
     47            con = HttpClient.create(u).connect();
     48            try (BufferedReader in = con.getContentReader()) {
    5449                return prepareHelpContent(in, dotest, u);
    5550            }
    5651        } catch (MalformedURLException e) {
    public String fetchHelpTopicContent(String helpTopicUrl, boolean dotest) throws  
    5853        } catch (IOException e) {
    5954            HelpContentReaderException ex = new HelpContentReaderException(e);
    6055            if (con != null) {
    61                 try {
    62                     ex.setResponseCode(con.getResponseCode());
    63                 } catch (IOException e1) {
    64                     // ignore
    65                     if (Main.isTraceEnabled()) {
    66                         Main.trace(e1.getMessage());
    67                     }
    68                 }
     56                ex.setResponseCode(con.getResponseCode());
    6957            }
    7058            throw ex;
    7159        }
  • src/org/openstreetmap/josm/io/CachedFile.java

    diff --git a/src/org/openstreetmap/josm/io/CachedFile.java b/src/org/openstreetmap/josm/io/CachedFile.java
    index e8090a7..7cb490a 100644
    a b  
    2020import java.util.Enumeration;
    2121import java.util.List;
    2222import java.util.Map;
    23 import java.util.Map.Entry;
    2423import java.util.concurrent.ConcurrentHashMap;
    2524import java.util.zip.ZipEntry;
    2625import java.util.zip.ZipFile;
    2726
    2827import org.openstreetmap.josm.Main;
    29 import org.openstreetmap.josm.tools.CheckParameterUtil;
     28import org.openstreetmap.josm.tools.HttpClient;
    3029import org.openstreetmap.josm.tools.Pair;
    3130import org.openstreetmap.josm.tools.Utils;
    3231
    private File checkLocal(URL url) throws IOException {  
    414413        String localPath = "mirror_" + a;
    415414        destDirFile = new File(destDir, localPath + ".tmp");
    416415        try {
    417             HttpURLConnection con = connectFollowingRedirect(url, httpAccept, ifModifiedSince, httpHeaders);
     416            final HttpClient.Response con = HttpClient.create(url)
     417                    .setAccept(httpAccept)
     418                    .setIfModifiedSince(ifModifiedSince == null ? 0L : ifModifiedSince)
     419                    .setHeaders(httpHeaders)
     420                    .connect();
    418421            if (ifModifiedSince != null && con.getResponseCode() == HttpURLConnection.HTTP_NOT_MODIFIED) {
    419422                if (Main.isDebugEnabled()) {
    420423                    Main.debug("304 Not Modified ("+urlStr+')');
    private File checkLocal(URL url) throws IOException {  
    426429                return localFile;
    427430            }
    428431            try (
    429                 InputStream bis = new BufferedInputStream(con.getInputStream());
     432                InputStream bis = new BufferedInputStream(con.getContent());
    430433                OutputStream fos = new FileOutputStream(destDirFile);
    431434                OutputStream bos = new BufferedOutputStream(fos)
    432435            ) {
    private static void checkOfflineAccess(String urlString) {  
    461464        OnlineResource.OSM_API.checkOfflineAccess(urlString, Main.pref.get("osm-server.url", OsmApi.DEFAULT_API_URL));
    462465    }
    463466
    464     /**
    465      * Opens a connection for downloading a resource.
    466      * <p>
    467      * Manually follows redirects because
    468      * {@link HttpURLConnection#setFollowRedirects(boolean)} fails if the redirect
    469      * is going from a http to a https URL, see <a href="https://bugs.openjdk.java.net/browse/JDK-4620571">bug report</a>.
    470      * <p>
    471      * This can cause problems when downloading from certain GitHub URLs.
    472      *
    473      * @param downloadUrl The resource URL to download
    474      * @param httpAccept The accepted MIME types sent in the HTTP Accept header. Can be {@code null}
    475      * @param ifModifiedSince The download time of the cache file, optional
    476      * @return The HTTP connection effectively linked to the resource, after all potential redirections
    477      * @throws MalformedURLException If a redirected URL is wrong
    478      * @throws IOException If any I/O operation goes wrong
    479      * @throws OfflineAccessException if resource is accessed in offline mode, in any protocol
    480      * @since 6867
    481      */
    482     public static HttpURLConnection connectFollowingRedirect(URL downloadUrl, String httpAccept, Long ifModifiedSince)
    483             throws MalformedURLException, IOException {
    484         return connectFollowingRedirect(downloadUrl, httpAccept, ifModifiedSince, null);
    485     }
    486 
    487     /**
    488      * Opens a connection for downloading a resource.
    489      * <p>
    490      * Manually follows redirects because
    491      * {@link HttpURLConnection#setFollowRedirects(boolean)} fails if the redirect
    492      * is going from a http to a https URL, see <a href="https://bugs.openjdk.java.net/browse/JDK-4620571">bug report</a>.
    493      * <p>
    494      * This can cause problems when downloading from certain GitHub URLs.
    495      *
    496      * @param downloadUrl The resource URL to download
    497      * @param httpAccept The accepted MIME types sent in the HTTP Accept header. Can be {@code null}
    498      * @param ifModifiedSince The download time of the cache file, optional
    499      * @param headers http headers to be sent together with http request
    500      * @return The HTTP connection effectively linked to the resource, after all potential redirections
    501      * @throws MalformedURLException If a redirected URL is wrong
    502      * @throws IOException If any I/O operation goes wrong
    503      * @throws OfflineAccessException if resource is accessed in offline mode, in any protocol
    504      * @since TODO
    505      */
    506     public static HttpURLConnection connectFollowingRedirect(URL downloadUrl, String httpAccept, Long ifModifiedSince,
    507             Map<String, String> headers) throws MalformedURLException, IOException {
    508         CheckParameterUtil.ensureParameterNotNull(downloadUrl, "downloadUrl");
    509         String downloadString = downloadUrl.toExternalForm();
    510 
    511         checkOfflineAccess(downloadString);
    512 
    513         int numRedirects = 0;
    514         while (true) {
    515             HttpURLConnection con = Utils.openHttpConnection(downloadUrl);
    516             if (ifModifiedSince != null) {
    517                 con.setIfModifiedSince(ifModifiedSince);
    518             }
    519             if (headers != null) {
    520                 for (Entry<String, String> header: headers.entrySet()) {
    521                     con.setRequestProperty(header.getKey(), header.getValue());
    522                 }
    523             }
    524             con.setInstanceFollowRedirects(false);
    525             con.setConnectTimeout(Main.pref.getInteger("socket.timeout.connect", 15)*1000);
    526             con.setReadTimeout(Main.pref.getInteger("socket.timeout.read", 30)*1000);
    527             if (Main.isDebugEnabled()) {
    528                 Main.debug("GET "+downloadString);
    529             }
    530             if (httpAccept != null) {
    531                 if (Main.isTraceEnabled()) {
    532                     Main.trace("Accept: "+httpAccept);
    533                 }
    534                 con.setRequestProperty("Accept", httpAccept);
    535             }
    536             try {
    537                 con.connect();
    538             } catch (IOException e) {
    539                 Main.addNetworkError(downloadUrl, Utils.getRootCause(e));
    540                 throw e;
    541             }
    542             switch(con.getResponseCode()) {
    543             case HttpURLConnection.HTTP_OK:
    544                 return con;
    545             case HttpURLConnection.HTTP_NOT_MODIFIED:
    546                 if (ifModifiedSince != null)
    547                     return con;
    548             case HttpURLConnection.HTTP_MOVED_PERM:
    549             case HttpURLConnection.HTTP_MOVED_TEMP:
    550             case HttpURLConnection.HTTP_SEE_OTHER:
    551                 String redirectLocation = con.getHeaderField("Location");
    552                 if (redirectLocation == null) {
    553                     /* I18n: argument is HTTP response code */
    554                     String msg = tr("Unexpected response from HTTP server. Got {0} response without ''Location'' header."+
    555                             " Can''t redirect. Aborting.", con.getResponseCode());
    556                     throw new IOException(msg);
    557                 }
    558                 downloadUrl = new URL(redirectLocation);
    559                 downloadString = downloadUrl.toExternalForm();
    560                 // keep track of redirect attempts to break a redirect loops if it happens
    561                 // to occur for whatever reason
    562                 numRedirects++;
    563                 if (numRedirects >= Main.pref.getInteger("socket.maxredirects", 5)) {
    564                     String msg = tr("Too many redirects to the download URL detected. Aborting.");
    565                     throw new IOException(msg);
    566                 }
    567                 Main.info(tr("Download redirected to ''{0}''", downloadString));
    568                 break;
    569             default:
    570                 String msg = tr("Failed to read from ''{0}''. Server responded with status code {1}.", downloadString, con.getResponseCode());
    571                 throw new IOException(msg);
    572             }
    573         }
    574     }
    575467}
  • src/org/openstreetmap/josm/plugins/PluginDownloadTask.java

    diff --git a/src/org/openstreetmap/josm/plugins/PluginDownloadTask.java b/src/org/openstreetmap/josm/plugins/PluginDownloadTask.java
    index 33e411b..3dfe19a 100644
    a b  
    99import java.io.IOException;
    1010import java.io.InputStream;
    1111import java.io.OutputStream;
    12 import java.net.HttpURLConnection;
    1312import java.net.MalformedURLException;
    1413import java.net.URL;
    1514import java.util.Collection;
     
    2120import org.openstreetmap.josm.gui.PleaseWaitRunnable;
    2221import org.openstreetmap.josm.gui.progress.NullProgressMonitor;
    2322import org.openstreetmap.josm.gui.progress.ProgressMonitor;
    24 import org.openstreetmap.josm.io.CachedFile;
    2523import org.openstreetmap.josm.tools.CheckParameterUtil;
     24import org.openstreetmap.josm.tools.HttpClient;
    2625import org.xml.sax.SAXException;
    2726
    2827/**
     
    4443    private final Collection<PluginInformation> failed = new LinkedList<>();
    4544    private final Collection<PluginInformation> downloaded = new LinkedList<>();
    4645    private boolean canceled;
    47     private HttpURLConnection downloadConnection;
     46    private HttpClient.Response downloadConnection;
    4847
    4948    /**
    5049     * Creates the download task
    protected void download(PluginInformation pi, File file) throws PluginDownloadEx  
    123122            }
    124123            URL url = new URL(pi.downloadlink);
    125124            synchronized (this) {
    126                 downloadConnection = CachedFile.connectFollowingRedirect(url, PLUGIN_MIME_TYPES, null);
     125                downloadConnection = HttpClient.create(url)
     126                        .setAccept(PLUGIN_MIME_TYPES)
     127                        .connect();
    127128            }
    128129            try (
    129                 InputStream in = downloadConnection.getInputStream();
     130                InputStream in = downloadConnection.getContent();
    130131                OutputStream out = new FileOutputStream(file)
    131132            ) {
    132133                byte[] buffer = new byte[8192];
  • new file src/org/openstreetmap/josm/tools/HttpClient.java

    diff --git a/src/org/openstreetmap/josm/tools/HttpClient.java b/src/org/openstreetmap/josm/tools/HttpClient.java
    new file mode 100644
    index 0000000..ab44a17
    - +  
     1// License: GPL. For details, see LICENSE file.
     2package org.openstreetmap.josm.tools;
     3
     4import static org.openstreetmap.josm.tools.I18n.tr;
     5
     6import java.io.BufferedOutputStream;
     7import java.io.BufferedReader;
     8import java.io.IOException;
     9import java.io.InputStream;
     10import java.io.InputStreamReader;
     11import java.io.OutputStream;
     12import java.net.HttpURLConnection;
     13import java.net.URL;
     14import java.nio.charset.StandardCharsets;
     15import java.util.Map;
     16import java.util.concurrent.ConcurrentHashMap;
     17import java.util.zip.GZIPInputStream;
     18
     19import org.openstreetmap.josm.Main;
     20import org.openstreetmap.josm.data.Version;
     21
     22/**
     23 * Provides a uniform access for a HTTP/HTTPS server. This class should be used in favour of {@link HttpURLConnection}.
     24 */
     25public class HttpClient {
     26
     27    private URL url;
     28    private final String requestMethod;
     29    private int connectTimeout = Main.pref.getInteger("socket.timeout.connect", 15) * 1000;
     30    private int readTimeout = Main.pref.getInteger("socket.timeout.read", 30) * 1000;
     31    private String accept;
     32    private String contentType;
     33    private String acceptEncoding = "gzip";
     34    private long contentLength;
     35    private byte[] requestBody;
     36    private long ifModifiedSince;
     37    private final Map<String, String> headers = new ConcurrentHashMap<>();
     38    private int maxRedirects = Main.pref.getInteger("socket.maxredirects", 5);
     39
     40    private HttpClient(URL url, String requestMethod) {
     41        this.url = url;
     42        this.requestMethod = requestMethod;
     43    }
     44
     45    public Response connect() throws IOException {
     46        final HttpURLConnection connection = (HttpURLConnection) url.openConnection();
     47        connection.setRequestProperty("User-Agent", Version.getInstance().getFullAgentString());
     48        connection.setConnectTimeout(connectTimeout);
     49        connection.setReadTimeout(readTimeout);
     50        if (accept != null) {
     51            connection.setRequestProperty("Accept", accept);
     52        }
     53        if (contentType != null) {
     54            connection.setRequestProperty("Content-Type", contentType);
     55        }
     56        if (acceptEncoding != null) {
     57            connection.setRequestProperty("Accept-Encoding", acceptEncoding);
     58        }
     59        if (contentLength > 0) {
     60            connection.setRequestProperty("Content-Length", String.valueOf(contentLength));
     61        }
     62        if ("PUT".equals(requestMethod) || "POST".equals(requestMethod) || "DELETE".equals(requestMethod)) {
     63            connection.setDoOutput(true);
     64            try (OutputStream out = new BufferedOutputStream(connection.getOutputStream())) {
     65                out.write(requestBody);
     66            }
     67        }
     68        if (ifModifiedSince > 0) {
     69            connection.setIfModifiedSince(ifModifiedSince);
     70        }
     71        for (Map.Entry<String, String> header : headers.entrySet()) {
     72            connection.setRequestProperty(header.getKey(), header.getValue());
     73        }
     74
     75        boolean successfulConnection = false;
     76        try {
     77            try {
     78                connection.connect();
     79            } catch (IOException e) {
     80                //noinspection ThrowableResultOfMethodCallIgnored
     81                Main.addNetworkError(url, Utils.getRootCause(e));
     82                throw e;
     83            }
     84            if (isRedirect(connection.getResponseCode())) {
     85                final String redirectLocation = connection.getHeaderField("Location");
     86                if (redirectLocation == null) {
     87                    /* I18n: argument is HTTP response code */
     88                    String msg = tr("Unexpected response from HTTP server. Got {0} response without ''Location'' header." +
     89                            " Can''t redirect. Aborting.", connection.getResponseCode());
     90                    throw new IOException(msg);
     91                } else if (maxRedirects > 0) {
     92                    url = new URL(redirectLocation);
     93                    maxRedirects--;
     94                    Main.info(tr("Download redirected to ''{0}''", redirectLocation));
     95                    return connect();
     96                } else {
     97                    String msg = tr("Too many redirects to the download URL detected. Aborting.");
     98                    throw new IOException(msg);
     99                }
     100            }
     101            Response response = new Response(connection);
     102            successfulConnection = true;
     103            return response;
     104        } finally {
     105            if (!successfulConnection) {
     106                connection.disconnect();
     107            }
     108        }
     109    }
     110
     111    /**
     112     * A wrapper for the HTTP response.
     113     */
     114    public static class Response {
     115        private final HttpURLConnection connection;
     116        private final int responseCode;
     117
     118        private Response(HttpURLConnection connection) throws IOException {
     119            this.connection = connection;
     120            this.responseCode = connection.getResponseCode();
     121        }
     122
     123        /**
     124         * Returns an input stream that reads from this HTTP connection, or,
     125         * error stream if the connection failed but the server sent useful data.
     126         *
     127         * @see HttpURLConnection#getInputStream()
     128         * @see HttpURLConnection#getErrorStream()
     129         */
     130        public InputStream getContent() throws IOException {
     131            InputStream in;
     132            try {
     133                in = connection.getInputStream();
     134            } catch (IOException ioe) {
     135                in = connection.getErrorStream();
     136            }
     137            return "gzip".equalsIgnoreCase(getContentEncoding()) ? new GZIPInputStream(in) : in;
     138        }
     139
     140        /**
     141         * Returns {@link #getContent()} wrapped in a buffered reader
     142         */
     143        public BufferedReader getContentReader() throws IOException {
     144            return new BufferedReader(new InputStreamReader(getContent(), StandardCharsets.UTF_8));
     145        }
     146
     147        /**
     148         * Gets the response code from this HTTP connection.
     149         *
     150         * @see HttpURLConnection#getResponseCode()
     151         */
     152        public int getResponseCode() {
     153            return responseCode;
     154        }
     155
     156        /**
     157         * Returns the {@code Content-Encoding} header.
     158         */
     159        public String getContentEncoding() {
     160            return connection.getContentEncoding();
     161        }
     162
     163        /**
     164         * Returns the {@code Content-Type} header.
     165         */
     166        public String getContentType() {
     167            return connection.getHeaderField("Content-Type");
     168        }
     169
     170        /**
     171         * @see HttpURLConnection#disconnect()
     172         */
     173        public void disconnect() {
     174            connection.disconnect();
     175        }
     176    }
     177
     178    /**
     179     * Creates a new instance for the given URL and a {@code GET} request
     180     *
     181     * @param url the URL
     182     * @return a new instance
     183     */
     184    public static HttpClient create(URL url) {
     185        return create(url, "GET");
     186    }
     187
     188    /**
     189     * Creates a new instance for the given URL and a {@code GET} request
     190     *
     191     * @param url           the URL
     192     * @param requestMethod the HTTP request method to perform when calling
     193     * @return a new instance
     194     */
     195    public static HttpClient create(URL url, String requestMethod) {
     196        return new HttpClient(url, requestMethod);
     197    }
     198
     199    /**
     200     * @return {@code this}
     201     * @see HttpURLConnection#setConnectTimeout(int)
     202     */
     203    public HttpClient setConnectTimeout(int connectTimeout) {
     204        this.connectTimeout = connectTimeout;
     205        return this;
     206    }
     207
     208    /**
     209     * @return {@code this}
     210     * @see HttpURLConnection#setReadTimeout(int) (int)
     211     */
     212
     213    public HttpClient setReadTimeout(int readTimeout) {
     214        this.readTimeout = readTimeout;
     215        return this;
     216    }
     217
     218    /**
     219     * Sets the {@code Accept} header.
     220     *
     221     * @return {@code this}
     222     */
     223    public HttpClient setAccept(String accept) {
     224        this.accept = accept;
     225        return this;
     226    }
     227
     228    /**
     229     * Sets the {@code Content-Type} header.
     230     *
     231     * @return {@code this}
     232     */
     233    public HttpClient setContentType(String contentType) {
     234        this.contentType = contentType;
     235        return this;
     236    }
     237
     238    /**
     239     * Sets the {@code Accept-Encoding} header.
     240     *
     241     * @return {@code this}
     242     */
     243    public HttpClient setAcceptEncoding(String acceptEncoding) {
     244        this.acceptEncoding = acceptEncoding;
     245        return this;
     246    }
     247
     248    /**
     249     * Sets the {@code Content-Length} header for {@code PUT}/{@code POST} requests.
     250     *
     251     * @return {@code this}
     252     */
     253    public HttpClient setContentLength(long contentLength) {
     254        this.contentLength = contentLength;
     255        return this;
     256    }
     257
     258    /**
     259     * Sets the request body for {@code PUT}/{@code POST} requests.
     260     *
     261     * @return {@code this}
     262     */
     263    public HttpClient setRequestBody(byte[] requestBody) {
     264        this.requestBody = requestBody;
     265        return this;
     266    }
     267
     268    /**
     269     * Sets the {@code If-Modified-Since} header.
     270     *
     271     * @return {@code this}
     272     */
     273    public HttpClient setIfModifiedSince(long ifModifiedSince) {
     274        this.ifModifiedSince = ifModifiedSince;
     275        return this;
     276    }
     277
     278    /**
     279     * Sets the maximum number of redirections to follow.
     280     *
     281     * @return {@code this}
     282     */
     283    public HttpClient setMaxRedirects(int maxRedirects) {
     284        this.maxRedirects = maxRedirects;
     285        return this;
     286    }
     287
     288    /**
     289     * Sets an arbitrary HTTP header.
     290     *
     291     * @return {@code this}
     292     */
     293    public HttpClient setHeader(String key, String value) {
     294        this.headers.put(key, value);
     295        return this;
     296    }
     297
     298    /**
     299     * Sets arbitrary HTTP headers.
     300     *
     301     * @return {@code this}
     302     */
     303    public HttpClient setHeaders(Map<String, String> headers) {
     304        this.headers.putAll(headers);
     305        return this;
     306    }
     307
     308    private static boolean isRedirect(final int statusCode) {
     309        switch (statusCode) {
     310            case HttpURLConnection.HTTP_MOVED_PERM: // 301
     311            case HttpURLConnection.HTTP_MOVED_TEMP: // 302
     312            case HttpURLConnection.HTTP_SEE_OTHER: // 303
     313            case 307: // TEMPORARY_REDIRECT:
     314            case 308: // PERMANENT_REDIRECT:
     315                return true;
     316            default:
     317                return false;
     318        }
     319    }
     320
     321}
  • src/org/openstreetmap/josm/tools/WikiReader.java

    diff --git a/src/org/openstreetmap/josm/tools/WikiReader.java b/src/org/openstreetmap/josm/tools/WikiReader.java
    index 3e6527a..97d7c7e 100644
    a b public final String getBaseUrlWiki() {  
    5252     */
    5353    public String read(String url) throws IOException {
    5454        URL u = new URL(url);
    55         try (BufferedReader in = Utils.openURLReader(u)) {
     55        try (BufferedReader in = HttpClient.create(u).connect().getContentReader()) {
    5656            boolean txt = url.endsWith("?format=txt");
    5757            if (url.startsWith(getBaseUrlWiki()) && !txt)
    5858                return readFromTrac(in, u);
    public String readLang(String text) throws IOException {  
    9797    }
    9898
    9999    private String readLang(URL url) throws IOException {
    100         try (BufferedReader in = Utils.openURLReader(url)) {
     100        try (BufferedReader in = HttpClient.create(url).connect().getContentReader()) {
    101101            return readFromTrac(in, url);
    102         } catch (IOException e) {
    103             Main.addNetworkError(url, Utils.getRootCause(e));
    104             throw e;
    105102        }
    106103    }
    107104