2013-04-03 15:53:57 +02:00
|
|
|
package com.commafeed.backend;
|
|
|
|
|
|
2013-04-11 12:49:54 +02:00
|
|
|
import java.io.IOException;
|
2023-12-18 10:46:30 +01:00
|
|
|
import java.net.URI;
|
|
|
|
|
import java.net.http.HttpClient;
|
|
|
|
|
import java.net.http.HttpClient.Redirect;
|
|
|
|
|
import java.net.http.HttpClient.Version;
|
|
|
|
|
import java.net.http.HttpRequest;
|
|
|
|
|
import java.net.http.HttpResponse;
|
|
|
|
|
import java.time.Duration;
|
|
|
|
|
import java.util.Optional;
|
2013-04-19 13:24:46 +02:00
|
|
|
|
2014-10-28 16:36:09 +01:00
|
|
|
import org.apache.commons.lang3.StringUtils;
|
2023-12-18 10:46:30 +01:00
|
|
|
import org.eclipse.jetty.http.HttpStatus;
|
2013-04-03 15:53:57 +02:00
|
|
|
|
2014-08-19 12:49:31 +02:00
|
|
|
import com.commafeed.CommaFeedConfiguration;
|
2023-12-18 10:46:30 +01:00
|
|
|
import com.google.common.net.HttpHeaders;
|
2014-08-19 12:49:31 +02:00
|
|
|
|
2023-12-17 14:11:15 +01:00
|
|
|
import jakarta.inject.Inject;
|
|
|
|
|
import jakarta.inject.Singleton;
|
2022-01-02 20:54:28 +01:00
|
|
|
import lombok.Getter;
|
|
|
|
|
import lombok.RequiredArgsConstructor;
|
|
|
|
|
import nl.altindag.ssl.SSLFactory;
|
|
|
|
|
|
2013-07-26 16:00:02 +02:00
|
|
|
/**
|
2013-08-13 12:15:30 +02:00
|
|
|
* Smart HTTP getter: handles gzip, ssl, last modified and etag headers
|
2022-01-02 20:54:28 +01:00
|
|
|
*
|
2013-07-26 16:00:02 +02:00
|
|
|
*/
|
2014-08-17 14:16:30 +02:00
|
|
|
@Singleton
|
2013-04-03 15:53:57 +02:00
|
|
|
public class HttpGetter {
|
|
|
|
|
|
2023-12-18 10:46:30 +01:00
|
|
|
private final HttpClient client;
|
|
|
|
|
private final String userAgent;
|
2014-08-19 12:49:31 +02:00
|
|
|
|
|
|
|
|
@Inject
|
|
|
|
|
public HttpGetter(CommaFeedConfiguration config) {
|
2023-12-18 10:46:30 +01:00
|
|
|
this.client = newClient();
|
|
|
|
|
this.userAgent = Optional.ofNullable(config.getApplicationSettings().getUserAgent())
|
|
|
|
|
.orElseGet(() -> String.format("CommaFeed/%s (https://github.com/Athou/commafeed)", config.getVersion()));
|
2014-08-19 12:49:31 +02:00
|
|
|
}
|
|
|
|
|
|
2023-12-18 10:46:30 +01:00
|
|
|
public HttpResult getBinary(String url, int timeout) throws IOException, NotModifiedException, InterruptedException {
|
2013-06-29 12:05:22 +02:00
|
|
|
return getBinary(url, null, null, timeout);
|
2013-04-03 15:53:57 +02:00
|
|
|
}
|
|
|
|
|
|
2013-04-17 12:49:03 +02:00
|
|
|
/**
|
2022-01-02 20:54:28 +01:00
|
|
|
*
|
2013-04-17 12:49:03 +02:00
|
|
|
* @param url
|
|
|
|
|
* the url to retrive
|
|
|
|
|
* @param lastModified
|
|
|
|
|
* header we got last time we queried that url, or null
|
|
|
|
|
* @param eTag
|
|
|
|
|
* header we got last time we queried that url, or null
|
|
|
|
|
* @throws NotModifiedException
|
|
|
|
|
* if the url hasn't changed since we asked for it last time
|
|
|
|
|
*/
|
2023-12-18 10:46:30 +01:00
|
|
|
public HttpResult getBinary(String url, String lastModified, String eTag, int timeout)
|
|
|
|
|
throws IOException, NotModifiedException, InterruptedException {
|
2013-04-19 09:37:07 +02:00
|
|
|
long start = System.currentTimeMillis();
|
2013-04-03 15:53:57 +02:00
|
|
|
|
2023-12-18 10:46:30 +01:00
|
|
|
HttpRequest.Builder builder = HttpRequest.newBuilder()
|
|
|
|
|
.uri(URI.create(url))
|
|
|
|
|
.timeout(Duration.ofMillis(timeout))
|
|
|
|
|
.header(HttpHeaders.ACCEPT_LANGUAGE, "en")
|
|
|
|
|
.header(HttpHeaders.PRAGMA, "No-cache")
|
|
|
|
|
.header(HttpHeaders.CACHE_CONTROL, "no-cache")
|
|
|
|
|
.header(HttpHeaders.USER_AGENT, userAgent);
|
|
|
|
|
if (lastModified != null) {
|
|
|
|
|
builder.header(HttpHeaders.IF_MODIFIED_SINCE, lastModified);
|
|
|
|
|
}
|
|
|
|
|
if (eTag != null) {
|
|
|
|
|
builder.header(HttpHeaders.IF_NONE_MATCH, eTag);
|
|
|
|
|
}
|
|
|
|
|
HttpRequest request = builder.GET().build();
|
|
|
|
|
|
|
|
|
|
HttpResponse<byte[]> response = client.send(request, HttpResponse.BodyHandlers.ofByteArray());
|
|
|
|
|
int code = response.statusCode();
|
|
|
|
|
if (code == HttpStatus.NOT_MODIFIED_304) {
|
|
|
|
|
throw new NotModifiedException("'304 - not modified' http code received");
|
|
|
|
|
} else if (code >= 300) {
|
|
|
|
|
throw new HttpResponseException(code, "Server returned HTTP error code " + code);
|
2013-04-03 15:53:57 +02:00
|
|
|
}
|
2013-04-17 12:49:03 +02:00
|
|
|
|
2023-12-18 10:46:30 +01:00
|
|
|
String lastModifiedHeader = response.headers().firstValue(HttpHeaders.LAST_MODIFIED).map(StringUtils::trimToNull).orElse(null);
|
|
|
|
|
if (lastModifiedHeader != null && lastModifiedHeader.equals(lastModified)) {
|
|
|
|
|
throw new NotModifiedException("lastModifiedHeader is the same");
|
|
|
|
|
}
|
2013-11-05 15:10:09 +01:00
|
|
|
|
2023-12-18 10:46:30 +01:00
|
|
|
String eTagHeader = response.headers().firstValue(HttpHeaders.ETAG).map(StringUtils::trimToNull).orElse(null);
|
|
|
|
|
if (eTagHeader != null && eTagHeader.equals(eTag)) {
|
|
|
|
|
throw new NotModifiedException("eTagHeader is the same");
|
|
|
|
|
}
|
2013-11-05 15:10:09 +01:00
|
|
|
|
2023-12-18 10:46:30 +01:00
|
|
|
byte[] content = response.body();
|
|
|
|
|
String contentType = response.headers().firstValue(HttpHeaders.CONTENT_TYPE).orElse(null);
|
|
|
|
|
String urlAfterRedirect = response.request().uri().toString();
|
2013-11-05 15:10:09 +01:00
|
|
|
|
2023-12-18 10:46:30 +01:00
|
|
|
long duration = System.currentTimeMillis() - start;
|
|
|
|
|
return new HttpResult(content, contentType, lastModifiedHeader, eTagHeader, duration, urlAfterRedirect);
|
|
|
|
|
}
|
2013-11-05 15:10:09 +01:00
|
|
|
|
2023-12-18 10:46:30 +01:00
|
|
|
public static HttpClient newClient() {
|
|
|
|
|
SSLFactory sslFactory = SSLFactory.builder().withUnsafeTrustMaterial().withUnsafeHostnameVerifier().build();
|
|
|
|
|
return HttpClient.newBuilder()
|
|
|
|
|
.version(Version.HTTP_1_1)
|
|
|
|
|
.followRedirects(Redirect.ALWAYS)
|
|
|
|
|
.sslContext(sslFactory.getSslContext())
|
|
|
|
|
.sslParameters(sslFactory.getSslParameters())
|
|
|
|
|
.build();
|
2013-04-19 13:24:46 +02:00
|
|
|
}
|
|
|
|
|
|
2023-01-27 08:08:50 +01:00
|
|
|
@Getter
|
2013-04-17 12:49:03 +02:00
|
|
|
public static class NotModifiedException extends Exception {
|
|
|
|
|
private static final long serialVersionUID = 1L;
|
2013-07-03 08:09:42 +02:00
|
|
|
|
2023-01-27 08:08:50 +01:00
|
|
|
/**
|
|
|
|
|
* if the value of this header changed, this is its new value
|
|
|
|
|
*/
|
2023-05-01 09:25:44 +02:00
|
|
|
private final String newLastModifiedHeader;
|
2023-01-27 08:08:50 +01:00
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* if the value of this header changed, this is its new value
|
|
|
|
|
*/
|
2023-05-01 09:25:44 +02:00
|
|
|
private final String newEtagHeader;
|
2023-01-27 08:08:50 +01:00
|
|
|
|
2013-07-03 08:09:42 +02:00
|
|
|
public NotModifiedException(String message) {
|
2023-01-27 08:08:50 +01:00
|
|
|
this(message, null, null);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public NotModifiedException(String message, String newLastModifiedHeader, String newEtagHeader) {
|
2013-07-03 07:56:52 +02:00
|
|
|
super(message);
|
2023-01-27 08:08:50 +01:00
|
|
|
this.newLastModifiedHeader = newLastModifiedHeader;
|
|
|
|
|
this.newEtagHeader = newEtagHeader;
|
2013-07-03 07:56:52 +02:00
|
|
|
}
|
2023-12-18 10:46:30 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Getter
|
|
|
|
|
public static class HttpResponseException extends IOException {
|
|
|
|
|
private static final long serialVersionUID = 1L;
|
|
|
|
|
|
|
|
|
|
private final int code;
|
|
|
|
|
|
|
|
|
|
public HttpResponseException(int code, String message) {
|
|
|
|
|
super(message);
|
|
|
|
|
this.code = code;
|
|
|
|
|
}
|
2013-04-17 12:49:03 +02:00
|
|
|
|
2013-04-03 15:53:57 +02:00
|
|
|
}
|
2013-04-19 13:24:46 +02:00
|
|
|
|
2022-01-02 20:54:28 +01:00
|
|
|
@Getter
|
|
|
|
|
@RequiredArgsConstructor
|
|
|
|
|
public static class HttpResult {
|
|
|
|
|
private final byte[] content;
|
|
|
|
|
private final String contentType;
|
|
|
|
|
private final String lastModifiedSince;
|
|
|
|
|
private final String eTag;
|
|
|
|
|
private final long duration;
|
|
|
|
|
private final String urlAfterRedirect;
|
2013-04-19 13:24:46 +02:00
|
|
|
}
|
2018-02-06 15:17:37 +01:00
|
|
|
|
2013-04-03 15:53:57 +02:00
|
|
|
}
|