Ticket #12231: 12231-alpha.patch
File 12231-alpha.patch, 23.8 KB (added by , 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 3 3 4 4 import java.io.BufferedReader; 5 5 import java.io.IOException; 6 import java.io.InputStreamReader;7 import java.net.HttpURLConnection;8 6 import java.net.MalformedURLException; 9 7 import java.net.URL; 10 import java.nio.charset.StandardCharsets;11 8 12 import org.openstreetmap.josm.Main; 13 import org.openstreetmap.josm.tools.Utils; 9 import org.openstreetmap.josm.tools.HttpClient; 14 10 import org.openstreetmap.josm.tools.WikiReader; 15 11 16 12 /** … … public HelpContentReader(String baseUrl) { 45 41 public String fetchHelpTopicContent(String helpTopicUrl, boolean dotest) throws HelpContentReaderException { 46 42 if (helpTopicUrl == null) 47 43 throw new MissingHelpContentException("helpTopicUrl is null"); 48 Http URLConnectioncon = null;44 HttpClient.Response con = null; 49 45 try { 50 46 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()) { 54 49 return prepareHelpContent(in, dotest, u); 55 50 } 56 51 } catch (MalformedURLException e) { … … public String fetchHelpTopicContent(String helpTopicUrl, boolean dotest) throws 58 53 } catch (IOException e) { 59 54 HelpContentReaderException ex = new HelpContentReaderException(e); 60 55 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()); 69 57 } 70 58 throw ex; 71 59 } -
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 20 20 import java.util.Enumeration; 21 21 import java.util.List; 22 22 import java.util.Map; 23 import java.util.Map.Entry;24 23 import java.util.concurrent.ConcurrentHashMap; 25 24 import java.util.zip.ZipEntry; 26 25 import java.util.zip.ZipFile; 27 26 28 27 import org.openstreetmap.josm.Main; 29 import org.openstreetmap.josm.tools. CheckParameterUtil;28 import org.openstreetmap.josm.tools.HttpClient; 30 29 import org.openstreetmap.josm.tools.Pair; 31 30 import org.openstreetmap.josm.tools.Utils; 32 31 … … private File checkLocal(URL url) throws IOException { 414 413 String localPath = "mirror_" + a; 415 414 destDirFile = new File(destDir, localPath + ".tmp"); 416 415 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(); 418 421 if (ifModifiedSince != null && con.getResponseCode() == HttpURLConnection.HTTP_NOT_MODIFIED) { 419 422 if (Main.isDebugEnabled()) { 420 423 Main.debug("304 Not Modified ("+urlStr+')'); … … private File checkLocal(URL url) throws IOException { 426 429 return localFile; 427 430 } 428 431 try ( 429 InputStream bis = new BufferedInputStream(con.get InputStream());432 InputStream bis = new BufferedInputStream(con.getContent()); 430 433 OutputStream fos = new FileOutputStream(destDirFile); 431 434 OutputStream bos = new BufferedOutputStream(fos) 432 435 ) { … … private static void checkOfflineAccess(String urlString) { 461 464 OnlineResource.OSM_API.checkOfflineAccess(urlString, Main.pref.get("osm-server.url", OsmApi.DEFAULT_API_URL)); 462 465 } 463 466 464 /**465 * Opens a connection for downloading a resource.466 * <p>467 * Manually follows redirects because468 * {@link HttpURLConnection#setFollowRedirects(boolean)} fails if the redirect469 * 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 download474 * @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, optional476 * @return The HTTP connection effectively linked to the resource, after all potential redirections477 * @throws MalformedURLException If a redirected URL is wrong478 * @throws IOException If any I/O operation goes wrong479 * @throws OfflineAccessException if resource is accessed in offline mode, in any protocol480 * @since 6867481 */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 because491 * {@link HttpURLConnection#setFollowRedirects(boolean)} fails if the redirect492 * 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 download497 * @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, optional499 * @param headers http headers to be sent together with http request500 * @return The HTTP connection effectively linked to the resource, after all potential redirections501 * @throws MalformedURLException If a redirected URL is wrong502 * @throws IOException If any I/O operation goes wrong503 * @throws OfflineAccessException if resource is accessed in offline mode, in any protocol504 * @since TODO505 */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 happens561 // to occur for whatever reason562 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 }575 467 } -
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 9 9 import java.io.IOException; 10 10 import java.io.InputStream; 11 11 import java.io.OutputStream; 12 import java.net.HttpURLConnection;13 12 import java.net.MalformedURLException; 14 13 import java.net.URL; 15 14 import java.util.Collection; … … 21 20 import org.openstreetmap.josm.gui.PleaseWaitRunnable; 22 21 import org.openstreetmap.josm.gui.progress.NullProgressMonitor; 23 22 import org.openstreetmap.josm.gui.progress.ProgressMonitor; 24 import org.openstreetmap.josm.io.CachedFile;25 23 import org.openstreetmap.josm.tools.CheckParameterUtil; 24 import org.openstreetmap.josm.tools.HttpClient; 26 25 import org.xml.sax.SAXException; 27 26 28 27 /** … … 44 43 private final Collection<PluginInformation> failed = new LinkedList<>(); 45 44 private final Collection<PluginInformation> downloaded = new LinkedList<>(); 46 45 private boolean canceled; 47 private Http URLConnectiondownloadConnection;46 private HttpClient.Response downloadConnection; 48 47 49 48 /** 50 49 * Creates the download task … … protected void download(PluginInformation pi, File file) throws PluginDownloadEx 123 122 } 124 123 URL url = new URL(pi.downloadlink); 125 124 synchronized (this) { 126 downloadConnection = CachedFile.connectFollowingRedirect(url, PLUGIN_MIME_TYPES, null); 125 downloadConnection = HttpClient.create(url) 126 .setAccept(PLUGIN_MIME_TYPES) 127 .connect(); 127 128 } 128 129 try ( 129 InputStream in = downloadConnection.get InputStream();130 InputStream in = downloadConnection.getContent(); 130 131 OutputStream out = new FileOutputStream(file) 131 132 ) { 132 133 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. 2 package org.openstreetmap.josm.tools; 3 4 import static org.openstreetmap.josm.tools.I18n.tr; 5 6 import java.io.BufferedOutputStream; 7 import java.io.BufferedReader; 8 import java.io.IOException; 9 import java.io.InputStream; 10 import java.io.InputStreamReader; 11 import java.io.OutputStream; 12 import java.net.HttpURLConnection; 13 import java.net.URL; 14 import java.nio.charset.StandardCharsets; 15 import java.util.Map; 16 import java.util.concurrent.ConcurrentHashMap; 17 import java.util.zip.GZIPInputStream; 18 19 import org.openstreetmap.josm.Main; 20 import 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 */ 25 public 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() { 52 52 */ 53 53 public String read(String url) throws IOException { 54 54 URL u = new URL(url); 55 try (BufferedReader in = Utils.openURLReader(u)) {55 try (BufferedReader in = HttpClient.create(u).connect().getContentReader()) { 56 56 boolean txt = url.endsWith("?format=txt"); 57 57 if (url.startsWith(getBaseUrlWiki()) && !txt) 58 58 return readFromTrac(in, u); … … public String readLang(String text) throws IOException { 97 97 } 98 98 99 99 private String readLang(URL url) throws IOException { 100 try (BufferedReader in = Utils.openURLReader(url)) {100 try (BufferedReader in = HttpClient.create(url).connect().getContentReader()) { 101 101 return readFromTrac(in, url); 102 } catch (IOException e) {103 Main.addNetworkError(url, Utils.getRootCause(e));104 throw e;105 102 } 106 103 } 107 104