Migrated to the Apache HttpComponents library

This commit is contained in:
Vhati 2018-01-06 10:30:30 -05:00
parent 8f55580ed2
commit 63f36b31cc
9 changed files with 111 additions and 76 deletions

View file

@ -59,6 +59,11 @@
<artifactId>jdom2</artifactId> <artifactId>jdom2</artifactId>
<version>2.0.6</version> <version>2.0.6</version>
</dependency> </dependency>
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
<version>4.5.4</version>
</dependency>
<dependency> <dependency>
<groupId>info.picocli</groupId> <groupId>info.picocli</groupId>
<artifactId>picocli</artifactId> <artifactId>picocli</artifactId>

View file

@ -32,6 +32,9 @@ To build, run "mvn clean package" in this folder.
This project depends on the following libraries. 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 - Jackson JSON Processor 2.x
http://jackson.codehaus.org/Home http://jackson.codehaus.org/Home
(For JavaDocs, look right.) (For JavaDocs, look right.)

View file

@ -23,6 +23,7 @@
"Disabled XML escaping when reencoding to ensure invalid chars cause an error", "Disabled XML escaping when reencoding to ensure invalid chars cause an error",
"Changed logging framework to SLF4J/Logback", "Changed logging framework to SLF4J/Logback",
"Changed command line parser to picocli", "Changed command line parser to picocli",
"Migrated to the Apache HttpComponents library",
"Made launcher scripts on OSX find java the recommended way" "Made launcher scripts on OSX find java the recommended way"
] ]
}, },

View file

@ -9,6 +9,7 @@ Changelog
- Added a Validate warning about FTL 1.5.13 for chars outside windows-1252 - Added a Validate warning about FTL 1.5.13 for chars outside windows-1252
- Changed logging framework to SLF4J/Logback - Changed logging framework to SLF4J/Logback
- Changed command line parser to picocli - Changed command line parser to picocli
- Migrated to the Apache HttpComponents library
- Made launcher scripts on OSX find java the recommended way - Made launcher scripts on OSX find java the recommended way
1.9: 1.9:

View file

@ -13,9 +13,9 @@ import net.vhati.modmanager.core.ComparableVersion;
*/ */
public class AutoUpdateInfo { public class AutoUpdateInfo {
private ComparableVersion latestVersion = null; private ComparableVersion latestVersion = null;
private Map<String,String> latestURLs = new TreeMap<String,String>(); private Map<String, String> latestURLs = new TreeMap<String, String>();
private String notice = null; private String notice = null;
private Map<ComparableVersion,List<String>> changelog = new TreeMap<ComparableVersion,List<String>>( Collections.reverseOrder() ); private Map<ComparableVersion, List<String>> changelog = new TreeMap<ComparableVersion, List<String>>( Collections.reverseOrder() );
public void setLatestVersion( ComparableVersion version ) { public void setLatestVersion( ComparableVersion version ) {
@ -26,7 +26,6 @@ public class AutoUpdateInfo {
return latestVersion; return latestVersion;
} }
public void setNotice( String s ) { public void setNotice( String s ) {
notice = s; notice = s;
} }
@ -35,7 +34,6 @@ public class AutoUpdateInfo {
return notice; return notice;
} }
public void putLatestURL( String os, String url ) { public void putLatestURL( String os, String url ) {
latestURLs.put( os, url ); latestURLs.put( os, url );
} }
@ -44,13 +42,11 @@ public class AutoUpdateInfo {
changelog.put( version, changeList ); changelog.put( version, changeList );
} }
public Map<String, String> getLatestURLs() {
public Map<String,String> getLatestURLs() {
return latestURLs; return latestURLs;
} }
public Map<ComparableVersion,List<String>> getChangelog() { public Map<ComparableVersion, List<String>> getChangelog() {
return changelog; return changelog;
} }
} }

View file

@ -12,11 +12,9 @@ import java.util.regex.Pattern;
* It is composed of three parts: * It is composed of three parts:
* - A series of period-separated positive ints. * - A series of period-separated positive ints.
* *
* - The numbers may be immediately followed by a short * - The numbers may be immediately followed by a short suffix string.
* suffix string.
* *
* - Finally, a string comment, separated from the rest * - Finally, a string comment, separated from the rest by a space.
* by a space.
* *
* The (numbers + suffix) or comment may appear alone. * The (numbers + suffix) or comment may appear alone.
* *
@ -148,16 +146,14 @@ public class ComparableVersion implements Comparable<ComparableVersion> {
Matcher m = suffixPtn.matcher( s ); Matcher m = suffixPtn.matcher( s );
if ( m.matches() ) { if ( m.matches() ) {
suffix = s;
// Matched groups 1 and 2... or 3. // Matched groups 1 and 2... or 3.
if ( m.group(1) != null ) { if ( m.group( 1 ) != null ) {
suffixDivider = m.group( 1 ); suffixDivider = m.group( 1 );
} }
if ( m.group(2) != null ) { if ( m.group( 2 ) != null ) {
suffixNum = Integer.parseInt( m.group(2) ); suffixNum = Integer.parseInt( m.group( 2 ) );
} else { } else {
suffixNum = -1; suffixNum = -1;
} }

View file

@ -3,7 +3,6 @@ package net.vhati.modmanager.json;
import java.io.File; import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator; import java.util.Iterator;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
@ -28,10 +27,11 @@ public class JacksonAutoUpdateReader {
public static AutoUpdateInfo parse( File jsonFile ) { public static AutoUpdateInfo parse( File jsonFile ) {
AutoUpdateInfo aui = new AutoUpdateInfo();
Exception exception = null; Exception exception = null;
try { try {
AutoUpdateInfo aui = new AutoUpdateInfo();
ObjectMapper mapper = new ObjectMapper(); ObjectMapper mapper = new ObjectMapper();
mapper.configure( JsonParser.Feature.ALLOW_SINGLE_QUOTES, true ); mapper.configure( JsonParser.Feature.ALLOW_SINGLE_QUOTES, true );
mapper.setVisibility( PropertyAccessor.FIELD, Visibility.ANY ); mapper.setVisibility( PropertyAccessor.FIELD, Visibility.ANY );
@ -43,9 +43,9 @@ public class JacksonAutoUpdateReader {
JsonNode latestNode = historyNode.get( "latest" ); JsonNode latestNode = historyNode.get( "latest" );
aui.setLatestVersion( new ComparableVersion( latestNode.get( "version" ).textValue() ) ); aui.setLatestVersion( new ComparableVersion( latestNode.get( "version" ).textValue() ) );
Iterator<Map.Entry<String,JsonNode>> fieldIt = latestNode.get( "urls" ).fields(); Iterator<Map.Entry<String, JsonNode>> fieldIt = latestNode.get( "urls" ).fields();
while ( fieldIt.hasNext() ) { while ( fieldIt.hasNext() ) {
Map.Entry<String,JsonNode> entry = fieldIt.next(); Map.Entry<String, JsonNode> entry = fieldIt.next();
aui.putLatestURL( entry.getKey(), entry.getValue().textValue() ); aui.putLatestURL( entry.getKey(), entry.getValue().textValue() );
} }
@ -67,6 +67,8 @@ public class JacksonAutoUpdateReader {
} }
aui.putChanges( new ComparableVersion( releaseVersion ), changeList ); aui.putChanges( new ComparableVersion( releaseVersion ), changeList );
} }
return aui;
} }
catch ( JsonProcessingException e ) { catch ( JsonProcessingException e ) {
exception = e; exception = e;
@ -76,9 +78,8 @@ public class JacksonAutoUpdateReader {
} }
if ( exception != null ) { if ( exception != null ) {
log.error( "Failed to parse info about available updates", exception ); log.error( "Failed to parse info about available updates", exception );
return null;
} }
return aui; return null;
} }
} }

View file

@ -23,10 +23,11 @@ public class JacksonCatalogReader {
public static ModDB parse( File jsonFile ) { public static ModDB parse( File jsonFile ) {
ModDB modDB = new ModDB();
Exception exception = null; Exception exception = null;
try { try {
ModDB modDB = new ModDB();
ObjectMapper mapper = new ObjectMapper(); ObjectMapper mapper = new ObjectMapper();
mapper.configure( JsonParser.Feature.ALLOW_SINGLE_QUOTES, true ); mapper.configure( JsonParser.Feature.ALLOW_SINGLE_QUOTES, true );
mapper.setVisibility( PropertyAccessor.FIELD, Visibility.ANY ); mapper.setVisibility( PropertyAccessor.FIELD, Visibility.ANY );
@ -53,6 +54,8 @@ public class JacksonCatalogReader {
modDB.addMod( modInfo ); modDB.addMod( modInfo );
} }
} }
return modDB;
} }
catch ( JsonProcessingException e ) { catch ( JsonProcessingException e ) {
exception = e; exception = e;
@ -65,6 +68,6 @@ public class JacksonCatalogReader {
return null; return null;
} }
return modDB; return null;
} }
} }

View file

@ -10,10 +10,6 @@ import java.io.InputStreamReader;
import java.io.IOException; import java.io.IOException;
import java.io.OutputStream; import java.io.OutputStream;
import java.io.OutputStreamWriter; 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.Date;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
@ -21,6 +17,14 @@ import java.util.Map;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; 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 { 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 * @return true if successfully downloaded, false otherwise
*/ */
public static boolean refetchURL( String url, File localFile, File eTagFile ) { public static boolean refetchURL( String url, File localFile, File eTagFile ) {
String localETag = null; 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() ) { if ( eTagFile.exists() ) {
// Load the old eTag. // Load the old eTag.
InputStream etagIn = null; InputStream etagIn = null;
BufferedReader etagReader = null;
try { try {
etagIn = new FileInputStream( eTagFile ); etagIn = new FileInputStream( eTagFile );
BufferedReader br = new BufferedReader( new InputStreamReader( etagIn, "UTF-8" ) ); etagReader = new BufferedReader( new InputStreamReader( etagIn, "UTF-8" ) );
String line = br.readLine(); String line = etagReader.readLine();
if ( line.length() > 0 ) if ( line.length() > 0 ) {
localETag = line; localETag = line;
} }
}
catch ( IOException e ) { catch ( IOException e ) {
// Not serious enough to be a real error. // 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 { finally {
try {if ( etagReader != null ) etagReader.close();}
catch ( IOException e ) {}
try {if ( etagIn != null ) etagIn.close();} try {if ( etagIn != null ) etagIn.close();}
catch ( IOException e ) {} catch ( IOException e ) {}
} }
} }
String remoteETag = null; HttpGet request = null;
InputStream urlIn = null;
OutputStream localOut = 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 { try {
URLConnection conn = new URL( url ).openConnection(); request = new HttpGet( url );
if ( conn instanceof HttpURLConnection == false ) { HttpResponse response = httpClient.execute( request );
log.error( String.format( "Non-Http(s) URL given for fetching: %s", url ) );
return false; int status = response.getStatusLine().getStatusCode();
if ( status >= 200 && status < 300 ) {
HttpEntity entity = response.getEntity();
if ( entity != null ) {
localOut = new FileOutputStream( localFile );
entity.writeTo( localOut );
} }
HttpURLConnection httpConn = (HttpURLConnection)conn;
httpConn.setReadTimeout( 10000 ); if ( response.containsHeader( "ETag" ) ) {
if ( localETag != null ) remoteETag = response.getLastHeader( "ETag" ).getValue();
httpConn.setRequestProperty( "If-None-Match", localETag ); }
httpConn.connect(); }
else if ( status == 304 ) { // Not modified.
int responseCode = httpConn.getResponseCode(); log.debug( String.format( "No need to download \"%s\", the server's copy has not been modified", localFile.getName() ) );
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() ) );
// Update the local file's timestamp as if it had downloaded. // Update the local file's timestamp as if it had downloaded.
localFile.setLastModified( new Date().getTime() ); localFile.setLastModified( new Date().getTime() );
return false; return false;
} }
else if ( responseCode == HttpURLConnection.HTTP_OK ) {
Map<String, List<String>> headerMap = httpConn.getHeaderFields();
List<String> 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 { else {
log.error( String.format( "Download request failed for \"%s\": HTTP Code %d (%s)", httpConn.getURL(), responseCode, httpConn.getResponseMessage() ) ); throw new ClientProtocolException( "Unexpected response status: "+ status );
}
}
catch ( ClientProtocolException e ) {
log.error( "GET request failed for url: "+ request.getURI().toString(), e );
return false; return false;
} }
}
catch ( IOException e ) { 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 { finally {
try {if ( urlIn != null ) urlIn.close();} try {if ( localOut != null ) localOut.close();}
catch ( IOException e ) {} catch ( IOException e ) {}
try {if ( localOut != null ) localOut.close();} try {httpClient.close();}
catch ( IOException e ) {} catch ( IOException e ) {}
} }
if ( remoteETag != null ) { if ( remoteETag != null ) {
// Save the new eTag. // Save the new eTag.
OutputStream etagOut = null; OutputStream etagOut = null;
BufferedWriter etagWriter = null;
try { try {
etagOut = new FileOutputStream( eTagFile ); etagOut = new FileOutputStream( eTagFile );
BufferedWriter bw = new BufferedWriter( new OutputStreamWriter( etagOut, "UTF-8" ) ); etagWriter = new BufferedWriter( new OutputStreamWriter( etagOut, "UTF-8" ) );
bw.append( remoteETag ); etagWriter.append( remoteETag );
bw.flush(); etagWriter.flush();
} }
catch ( IOException e ) { catch ( IOException e ) {
log.error( String.format( "Error writing eTag to \"%s\"", eTagFile.getName() ), e ); log.error( String.format( "Error writing eTag to \"%s\"", eTagFile.getName() ), e );
} }
finally { finally {
try {if ( etagWriter != null ) etagWriter.close();}
catch ( IOException e ) {}
try {if ( etagOut != null ) etagOut.close();} try {if ( etagOut != null ) etagOut.close();}
catch ( IOException e ) {} catch ( IOException e ) {}
} }