From 63f36b31cc335caf56320d65aeb6d1397cf75bb5 Mon Sep 17 00:00:00 2001 From: Vhati Date: Sat, 6 Jan 2018 10:30:30 -0500 Subject: [PATCH] Migrated to the Apache HttpComponents library --- pom.xml | 5 + readme_developers.txt | 3 + skel_common/backup/auto_update.json | 1 + skel_common/readme_changelog.txt | 1 + .../vhati/modmanager/core/AutoUpdateInfo.java | 12 +- .../modmanager/core/ComparableVersion.java | 16 +-- .../json/JacksonAutoUpdateReader.java | 13 +- .../modmanager/json/JacksonCatalogReader.java | 7 +- .../net/vhati/modmanager/json/URLFetcher.java | 129 +++++++++++------- 9 files changed, 111 insertions(+), 76 deletions(-) diff --git a/pom.xml b/pom.xml index fdd4309..a0ca0a2 100644 --- a/pom.xml +++ b/pom.xml @@ -59,6 +59,11 @@ jdom2 2.0.6 + + org.apache.httpcomponents + httpclient + 4.5.4 + info.picocli picocli diff --git a/readme_developers.txt b/readme_developers.txt index e615cc9..7a9c7b1 100644 --- a/readme_developers.txt +++ b/readme_developers.txt @@ -32,6 +32,9 @@ To build, run "mvn clean package" in this folder. This project depends on the following libraries. +- Apache HttpComponents + https://hc.apache.org/ + (For JavaDocs, click HttpCore or HttpClient, then again under "Project reports".) - Jackson JSON Processor 2.x http://jackson.codehaus.org/Home (For JavaDocs, look right.) diff --git a/skel_common/backup/auto_update.json b/skel_common/backup/auto_update.json index 99613ba..bda8914 100644 --- a/skel_common/backup/auto_update.json +++ b/skel_common/backup/auto_update.json @@ -23,6 +23,7 @@ "Disabled XML escaping when reencoding to ensure invalid chars cause an error", "Changed logging framework to SLF4J/Logback", "Changed command line parser to picocli", + "Migrated to the Apache HttpComponents library", "Made launcher scripts on OSX find java the recommended way" ] }, diff --git a/skel_common/readme_changelog.txt b/skel_common/readme_changelog.txt index 356c110..a471fa3 100644 --- a/skel_common/readme_changelog.txt +++ b/skel_common/readme_changelog.txt @@ -9,6 +9,7 @@ Changelog - Added a Validate warning about FTL 1.5.13 for chars outside windows-1252 - Changed logging framework to SLF4J/Logback - Changed command line parser to picocli +- Migrated to the Apache HttpComponents library - Made launcher scripts on OSX find java the recommended way 1.9: diff --git a/src/main/java/net/vhati/modmanager/core/AutoUpdateInfo.java b/src/main/java/net/vhati/modmanager/core/AutoUpdateInfo.java index 1c3b1ca..adbcf28 100644 --- a/src/main/java/net/vhati/modmanager/core/AutoUpdateInfo.java +++ b/src/main/java/net/vhati/modmanager/core/AutoUpdateInfo.java @@ -13,9 +13,9 @@ import net.vhati.modmanager.core.ComparableVersion; */ public class AutoUpdateInfo { private ComparableVersion latestVersion = null; - private Map latestURLs = new TreeMap(); + private Map latestURLs = new TreeMap(); private String notice = null; - private Map> changelog = new TreeMap>( Collections.reverseOrder() ); + private Map> changelog = new TreeMap>( Collections.reverseOrder() ); public void setLatestVersion( ComparableVersion version ) { @@ -26,7 +26,6 @@ public class AutoUpdateInfo { return latestVersion; } - public void setNotice( String s ) { notice = s; } @@ -35,7 +34,6 @@ public class AutoUpdateInfo { return notice; } - public void putLatestURL( String os, String url ) { latestURLs.put( os, url ); } @@ -44,13 +42,11 @@ public class AutoUpdateInfo { changelog.put( version, changeList ); } - - - public Map getLatestURLs() { + public Map getLatestURLs() { return latestURLs; } - public Map> getChangelog() { + public Map> getChangelog() { return changelog; } } diff --git a/src/main/java/net/vhati/modmanager/core/ComparableVersion.java b/src/main/java/net/vhati/modmanager/core/ComparableVersion.java index f09e980..15b371d 100644 --- a/src/main/java/net/vhati/modmanager/core/ComparableVersion.java +++ b/src/main/java/net/vhati/modmanager/core/ComparableVersion.java @@ -12,11 +12,9 @@ import java.util.regex.Pattern; * It is composed of three parts: * - A series of period-separated positive ints. * - * - The numbers may be immediately followed by a short - * suffix string. + * - The numbers may be immediately followed by a short suffix string. * - * - Finally, a string comment, separated from the rest - * by a space. + * - Finally, a string comment, separated from the rest by a space. * * The (numbers + suffix) or comment may appear alone. * @@ -38,7 +36,7 @@ public class ComparableVersion implements Comparable { public ComparableVersion( int[] numbers, String suffix, String comment ) { this.numbers = numbers; - setSuffix( suffix ); + setSuffix( suffix ); setComment( comment ); } @@ -148,16 +146,14 @@ public class ComparableVersion implements Comparable { Matcher m = suffixPtn.matcher( s ); if ( m.matches() ) { - suffix = s; - // Matched groups 1 and 2... or 3. - if ( m.group(1) != null ) { + if ( m.group( 1 ) != null ) { suffixDivider = m.group( 1 ); } - if ( m.group(2) != null ) { - suffixNum = Integer.parseInt( m.group(2) ); + if ( m.group( 2 ) != null ) { + suffixNum = Integer.parseInt( m.group( 2 ) ); } else { suffixNum = -1; } diff --git a/src/main/java/net/vhati/modmanager/json/JacksonAutoUpdateReader.java b/src/main/java/net/vhati/modmanager/json/JacksonAutoUpdateReader.java index 17667d7..51b9b84 100644 --- a/src/main/java/net/vhati/modmanager/json/JacksonAutoUpdateReader.java +++ b/src/main/java/net/vhati/modmanager/json/JacksonAutoUpdateReader.java @@ -3,7 +3,6 @@ package net.vhati.modmanager.json; import java.io.File; import java.io.IOException; import java.util.ArrayList; -import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; @@ -28,10 +27,11 @@ public class JacksonAutoUpdateReader { public static AutoUpdateInfo parse( File jsonFile ) { - AutoUpdateInfo aui = new AutoUpdateInfo(); Exception exception = null; try { + AutoUpdateInfo aui = new AutoUpdateInfo(); + ObjectMapper mapper = new ObjectMapper(); mapper.configure( JsonParser.Feature.ALLOW_SINGLE_QUOTES, true ); mapper.setVisibility( PropertyAccessor.FIELD, Visibility.ANY ); @@ -43,9 +43,9 @@ public class JacksonAutoUpdateReader { JsonNode latestNode = historyNode.get( "latest" ); aui.setLatestVersion( new ComparableVersion( latestNode.get( "version" ).textValue() ) ); - Iterator> fieldIt = latestNode.get( "urls" ).fields(); + Iterator> fieldIt = latestNode.get( "urls" ).fields(); while ( fieldIt.hasNext() ) { - Map.Entry entry = fieldIt.next(); + Map.Entry entry = fieldIt.next(); aui.putLatestURL( entry.getKey(), entry.getValue().textValue() ); } @@ -67,6 +67,8 @@ public class JacksonAutoUpdateReader { } aui.putChanges( new ComparableVersion( releaseVersion ), changeList ); } + + return aui; } catch ( JsonProcessingException e ) { exception = e; @@ -76,9 +78,8 @@ public class JacksonAutoUpdateReader { } if ( exception != null ) { log.error( "Failed to parse info about available updates", exception ); - return null; } - return aui; + return null; } } diff --git a/src/main/java/net/vhati/modmanager/json/JacksonCatalogReader.java b/src/main/java/net/vhati/modmanager/json/JacksonCatalogReader.java index d1e3228..6da9c47 100644 --- a/src/main/java/net/vhati/modmanager/json/JacksonCatalogReader.java +++ b/src/main/java/net/vhati/modmanager/json/JacksonCatalogReader.java @@ -23,10 +23,11 @@ public class JacksonCatalogReader { public static ModDB parse( File jsonFile ) { - ModDB modDB = new ModDB(); Exception exception = null; try { + ModDB modDB = new ModDB(); + ObjectMapper mapper = new ObjectMapper(); mapper.configure( JsonParser.Feature.ALLOW_SINGLE_QUOTES, true ); mapper.setVisibility( PropertyAccessor.FIELD, Visibility.ANY ); @@ -53,6 +54,8 @@ public class JacksonCatalogReader { modDB.addMod( modInfo ); } } + + return modDB; } catch ( JsonProcessingException e ) { exception = e; @@ -65,6 +68,6 @@ public class JacksonCatalogReader { return null; } - return modDB; + return null; } } diff --git a/src/main/java/net/vhati/modmanager/json/URLFetcher.java b/src/main/java/net/vhati/modmanager/json/URLFetcher.java index b0d64ec..c0395ee 100644 --- a/src/main/java/net/vhati/modmanager/json/URLFetcher.java +++ b/src/main/java/net/vhati/modmanager/json/URLFetcher.java @@ -10,10 +10,6 @@ import java.io.InputStreamReader; import java.io.IOException; import java.io.OutputStream; import java.io.OutputStreamWriter; -import java.net.HttpURLConnection; -import java.net.MalformedURLException; -import java.net.URL; -import java.net.URLConnection; import java.util.Date; import java.util.List; import java.util.Map; @@ -21,6 +17,14 @@ import java.util.Map; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.apache.http.HttpEntity; +import org.apache.http.HttpResponse; +import org.apache.http.client.ClientProtocolException; +import org.apache.http.client.config.RequestConfig; +import org.apache.http.client.methods.HttpGet; +import org.apache.http.impl.client.CloseableHttpClient; +import org.apache.http.impl.client.HttpClientBuilder; + public class URLFetcher { @@ -28,104 +32,129 @@ public class URLFetcher { /** - * Downloads content from a url into a file, if the remote content has changed. + * Downloads content from a url to one file, and its ETag to another. + * + * If the ETag files exists, it will be read to inform the GET request. + * + * If the content has not changed, the destination file's modified date + * will be reset to the present time. + * + * If the content has changed, it will be written to the file, as will the + * new ETag. * * @return true if successfully downloaded, false otherwise */ public static boolean refetchURL( String url, File localFile, File eTagFile ) { String localETag = null; - log.debug( String.format( "Attempting to download the latest \"%s\".", localFile.getName() ) ); + log.debug( String.format( "Attempting to download the latest \"%s\"", localFile.getName() ) ); if ( eTagFile.exists() ) { // Load the old eTag. InputStream etagIn = null; + BufferedReader etagReader = null; try { etagIn = new FileInputStream( eTagFile ); - BufferedReader br = new BufferedReader( new InputStreamReader( etagIn, "UTF-8" ) ); - String line = br.readLine(); - if ( line.length() > 0 ) + etagReader = new BufferedReader( new InputStreamReader( etagIn, "UTF-8" ) ); + String line = etagReader.readLine(); + if ( line.length() > 0 ) { localETag = line; + } } catch ( IOException e ) { // Not serious enough to be a real error. - log.debug( String.format( "Error reading eTag from \"%s\".", eTagFile.getName() ), e ); + log.debug( String.format( "Error reading eTag from \"%s\"", eTagFile.getName() ), e ); } finally { + try {if ( etagReader != null ) etagReader.close();} + catch ( IOException e ) {} + try {if ( etagIn != null ) etagIn.close();} catch ( IOException e ) {} } } - String remoteETag = null; - InputStream urlIn = null; + HttpGet request = null; OutputStream localOut = null; + String remoteETag = null; + + RequestConfig requestConfig = RequestConfig.custom() + .setConnectionRequestTimeout( 5000 ) + .setConnectTimeout( 5000 ) + .setSocketTimeout( 10000 ) + .setRedirectsEnabled( true ) + .build(); + + CloseableHttpClient httpClient = HttpClientBuilder.create() + .setDefaultRequestConfig( requestConfig ) + .disableAuthCaching() + .disableAutomaticRetries() + .disableConnectionState() + .disableCookieManagement() + //.setUserAgent( "" ) + .build(); + try { - URLConnection conn = new URL( url ).openConnection(); + request = new HttpGet( url ); - if ( conn instanceof HttpURLConnection == false ) { - log.error( String.format( "Non-Http(s) URL given for fetching: %s", url ) ); - return false; + HttpResponse response = httpClient.execute( request ); + + int status = response.getStatusLine().getStatusCode(); + if ( status >= 200 && status < 300 ) { + + HttpEntity entity = response.getEntity(); + if ( entity != null ) { + localOut = new FileOutputStream( localFile ); + entity.writeTo( localOut ); + } + + if ( response.containsHeader( "ETag" ) ) { + remoteETag = response.getLastHeader( "ETag" ).getValue(); + } } - HttpURLConnection httpConn = (HttpURLConnection)conn; - - httpConn.setReadTimeout( 10000 ); - if ( localETag != null ) - httpConn.setRequestProperty( "If-None-Match", localETag ); - httpConn.connect(); - - int responseCode = httpConn.getResponseCode(); - - if ( responseCode == HttpURLConnection.HTTP_NOT_MODIFIED ) { - log.debug( String.format( "No need to update \"%s\", the server's copy has not been modified since the previous check", localFile.getName() ) ); + else if ( status == 304 ) { // Not modified. + log.debug( String.format( "No need to download \"%s\", the server's copy has not been modified", localFile.getName() ) ); // Update the local file's timestamp as if it had downloaded. localFile.setLastModified( new Date().getTime() ); - return false; } - else if ( responseCode == HttpURLConnection.HTTP_OK ) { - Map> headerMap = httpConn.getHeaderFields(); - List eTagValues = headerMap.get( "ETag" ); - if ( eTagValues != null && eTagValues.size() > 0 ) - remoteETag = eTagValues.get( 0 ); - - urlIn = httpConn.getInputStream(); - localOut = new FileOutputStream( localFile ); - byte[] buf = new byte[4096]; - int len; - while ( (len = urlIn.read(buf)) >= 0 ) { - localOut.write( buf, 0, len ); - } - } else { - log.error( String.format( "Download request failed for \"%s\": HTTP Code %d (%s)", httpConn.getURL(), responseCode, httpConn.getResponseMessage() ) ); - return false; + throw new ClientProtocolException( "Unexpected response status: "+ status ); } } + catch ( ClientProtocolException e ) { + log.error( "GET request failed for url: "+ request.getURI().toString(), e ); + return false; + } catch ( IOException e ) { - log.error( String.format( "Error downloading the latest \"%s\"", localFile.getName() ), e ); + log.error( "Download failed for url: "+ request.getURI().toString(), e ); + return false; } finally { - try {if ( urlIn != null ) urlIn.close();} + try {if ( localOut != null ) localOut.close();} catch ( IOException e ) {} - try {if ( localOut != null ) localOut.close();} + try {httpClient.close();} catch ( IOException e ) {} } if ( remoteETag != null ) { // Save the new eTag. OutputStream etagOut = null; + BufferedWriter etagWriter = null; try { etagOut = new FileOutputStream( eTagFile ); - BufferedWriter bw = new BufferedWriter( new OutputStreamWriter( etagOut, "UTF-8" ) ); - bw.append( remoteETag ); - bw.flush(); + etagWriter = new BufferedWriter( new OutputStreamWriter( etagOut, "UTF-8" ) ); + etagWriter.append( remoteETag ); + etagWriter.flush(); } catch ( IOException e ) { log.error( String.format( "Error writing eTag to \"%s\"", eTagFile.getName() ), e ); } finally { + try {if ( etagWriter != null ) etagWriter.close();} + catch ( IOException e ) {} + try {if ( etagOut != null ) etagOut.close();} catch ( IOException e ) {} }