HttpClient.java
/*
* Copyright 2024 Emmanuel Bourg
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.jsign;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.nio.file.Files;
import java.security.MessageDigest;
import org.apache.commons.io.IOUtils;
import org.bouncycastle.util.encoders.Hex;
/**
* Simple HTTP client with resource caching.
*
* @since 7.0
*/
class HttpClient {
/** The directory where the cached files are stored */
private final File cacheDir;
/** The expiration time for the cached files */
private final long expirationTime;
public HttpClient(File cacheDir, long expirationTime) {
this.cacheDir = cacheDir;
this.expirationTime = expirationTime;
}
public InputStream getInputStream(URL url) throws IOException {
File cacheFile = new File(cacheDir, getRequestHash(url) + ".cache");
if (cacheFile.exists() && (System.currentTimeMillis() - cacheFile.lastModified()) < expirationTime) {
return new FileInputStream(cacheFile);
} else {
HttpURLConnection conn = connect(url);
if (conn.getResponseCode() >= 400) {
throw new IOException("Unable to read " + url + " : " + conn.getResponseCode() + " - " + conn.getResponseMessage());
}
InputStream in = conn.getInputStream();
byte[] response = IOUtils.toByteArray(in);
in.close();
conn.disconnect();
// put the response in the cache
cacheFile.getParentFile().mkdirs();
Files.write(cacheFile.toPath(), response);
return new ByteArrayInputStream(response);
}
}
/**
* Connect to the specified URL and follow the redirections (including http -> https).
*/
private HttpURLConnection connect(URL url) throws IOException {
int redirections = 0;
while (redirections++ < 10) {
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
String userAgent = System.getProperty("http.agent");
conn.setRequestProperty("User-Agent", "Jsign (https://ebourg.github.io/jsign/)" + (userAgent != null ? " " + userAgent : ""));
conn.setConnectTimeout(15000);
conn.setReadTimeout(15000);
conn.setInstanceFollowRedirects(false);
switch (conn.getResponseCode()) {
case HttpURLConnection.HTTP_MOVED_PERM:
case HttpURLConnection.HTTP_MOVED_TEMP:
url = new URL(url, conn.getHeaderField("Location"));
continue;
}
return conn;
}
throw new IOException("Too many redirections for " + url);
}
String getRequestHash(URL url) {
MessageDigest digest = DigestAlgorithm.SHA1.getMessageDigest();
digest.update(url.toString().getBytes());
return Hex.toHexString(digest.digest());
}
}