Skip to content

Commit 1c6fa20

Browse files
committed
GH-5220 Follow 301/302/307/308 redirects for state-changing requests; preserve method; enable Expect: 100-continue
1 parent 211c7d1 commit 1c6fa20

2 files changed

Lines changed: 86 additions & 28 deletions

File tree

core/http/client/src/main/java/org/eclipse/rdf4j/http/client/SPARQLProtocolSession.java

Lines changed: 46 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@
4949
import org.apache.http.client.methods.HttpGet;
5050
import org.apache.http.client.methods.HttpPost;
5151
import org.apache.http.client.methods.HttpUriRequest;
52+
import org.apache.http.client.methods.RequestBuilder;
5253
import org.apache.http.client.protocol.HttpClientContext;
5354
import org.apache.http.client.utils.URIBuilder;
5455
import org.apache.http.entity.ContentType;
@@ -1058,44 +1059,61 @@ protected void executeNoContent(HttpUriRequest method) throws IOException, RDF4J
10581059
}
10591060

10601061
protected HttpResponse execute(HttpUriRequest method) throws IOException, RDF4JException {
1062+
return executeWithRedirects(method, 5);
1063+
}
1064+
1065+
private HttpResponse executeWithRedirects(HttpUriRequest method, int redirectsLeft)
1066+
throws IOException, RDF4JException {
10611067
boolean consume = true;
10621068
if (params != null) {
10631069
method.setParams(params);
10641070
}
10651071
HttpResponse response = httpClient.execute(method, httpContext);
1066-
10671072
try {
10681073
int httpCode = response.getStatusLine().getStatusCode();
10691074
if (httpCode >= 200 && httpCode < 300 || httpCode == HttpURLConnection.HTTP_NOT_FOUND) {
10701075
consume = false;
10711076
return response; // everything OK, control flow can continue
1072-
} else {
1073-
switch (httpCode) {
1074-
case HttpURLConnection.HTTP_UNAUTHORIZED: // 401
1075-
throw new UnauthorizedException();
1076-
case HttpURLConnection.HTTP_UNAVAILABLE: // 503
1077-
throw new QueryInterruptedException();
1078-
default:
1079-
ErrorInfo errInfo = getErrorInfo(response);
1080-
// Throw appropriate exception
1081-
if (errInfo.getErrorType() == ErrorType.MALFORMED_DATA) {
1082-
throw new RDFParseException(errInfo.getErrorMessage());
1083-
} else if (errInfo.getErrorType() == ErrorType.UNSUPPORTED_FILE_FORMAT) {
1084-
throw new UnsupportedRDFormatException(errInfo.getErrorMessage());
1085-
} else if (errInfo.getErrorType() == ErrorType.MALFORMED_QUERY) {
1086-
throw new MalformedQueryException(errInfo.getErrorMessage());
1087-
} else if (errInfo.getErrorType() == ErrorType.UNSUPPORTED_QUERY_LANGUAGE) {
1088-
throw new UnsupportedQueryLanguageException(errInfo.getErrorMessage());
1089-
} else if (contentTypeIs(response, "application/shacl-validation-report")) {
1090-
RDFFormat format = getContentTypeSerialisation(response);
1091-
throw new RepositoryException(new RemoteShaclValidationException(
1092-
new StringReader(errInfo.toString()), "", format));
1093-
1094-
} else if (!errInfo.toString().isEmpty()) {
1095-
throw new RepositoryException(errInfo.toString());
1096-
} else {
1097-
throw new RepositoryException(response.getStatusLine().getReasonPhrase());
1098-
}
1077+
}
1078+
1079+
// Follow redirects for any method (preserving method and entity)
1080+
if (redirectsLeft > 0 && (httpCode == HttpURLConnection.HTTP_MOVED_PERM
1081+
|| httpCode == HttpURLConnection.HTTP_MOVED_TEMP || httpCode == 307 || httpCode == 308)) {
1082+
Header location = response.getFirstHeader("Location");
1083+
if (location != null) {
1084+
// consume and follow
1085+
EntityUtils.consumeQuietly(response.getEntity());
1086+
java.net.URI uri = java.net.URI.create(location.getValue());
1087+
HttpUriRequest redirect = RequestBuilder.copy(method).setUri(uri).build();
1088+
return executeWithRedirects(redirect, redirectsLeft - 1);
1089+
}
1090+
}
1091+
1092+
switch (httpCode) {
1093+
case HttpURLConnection.HTTP_UNAUTHORIZED: // 401
1094+
throw new UnauthorizedException();
1095+
case HttpURLConnection.HTTP_UNAVAILABLE: // 503
1096+
throw new QueryInterruptedException();
1097+
default:
1098+
ErrorInfo errInfo = getErrorInfo(response);
1099+
// Throw appropriate exception
1100+
if (errInfo.getErrorType() == ErrorType.MALFORMED_DATA) {
1101+
throw new RDFParseException(errInfo.getErrorMessage());
1102+
} else if (errInfo.getErrorType() == ErrorType.UNSUPPORTED_FILE_FORMAT) {
1103+
throw new UnsupportedRDFormatException(errInfo.getErrorMessage());
1104+
} else if (errInfo.getErrorType() == ErrorType.MALFORMED_QUERY) {
1105+
throw new MalformedQueryException(errInfo.getErrorMessage());
1106+
} else if (errInfo.getErrorType() == ErrorType.UNSUPPORTED_QUERY_LANGUAGE) {
1107+
throw new UnsupportedQueryLanguageException(errInfo.getErrorMessage());
1108+
} else if (contentTypeIs(response, "application/shacl-validation-report")) {
1109+
RDFFormat format = getContentTypeSerialisation(response);
1110+
throw new RepositoryException(new RemoteShaclValidationException(
1111+
new StringReader(errInfo.toString()), "", format));
1112+
1113+
} else if (!errInfo.toString().isEmpty()) {
1114+
throw new RepositoryException(errInfo.toString());
1115+
} else {
1116+
throw new RepositoryException(response.getStatusLine().getReasonPhrase());
10991117
}
11001118
}
11011119
} finally {

core/http/client/src/main/java/org/eclipse/rdf4j/http/client/SharedHttpClientSessionManager.java

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212

1313
import java.io.IOException;
1414
import java.net.HttpURLConnection;
15+
import java.net.URI;
1516
import java.util.Map;
1617
import java.util.Objects;
1718
import java.util.concurrent.ConcurrentHashMap;
@@ -24,15 +25,20 @@
2425
import java.util.concurrent.atomic.AtomicLong;
2526

2627
import org.apache.http.HttpConnection;
28+
import org.apache.http.HttpRequest;
2729
import org.apache.http.HttpResponse;
30+
import org.apache.http.ProtocolException;
2831
import org.apache.http.client.HttpClient;
2932
import org.apache.http.client.HttpRequestRetryHandler;
3033
import org.apache.http.client.ServiceUnavailableRetryStrategy;
3134
import org.apache.http.client.config.CookieSpecs;
3235
import org.apache.http.client.config.RequestConfig;
36+
import org.apache.http.client.methods.HttpUriRequest;
37+
import org.apache.http.client.methods.RequestBuilder;
3338
import org.apache.http.client.protocol.HttpClientContext;
3439
import org.apache.http.client.utils.HttpClientUtils;
3540
import org.apache.http.impl.client.CloseableHttpClient;
41+
import org.apache.http.impl.client.DefaultRedirectStrategy;
3642
import org.apache.http.impl.client.HttpClientBuilder;
3743
import org.apache.http.protocol.HttpContext;
3844
import org.eclipse.rdf4j.http.client.util.HttpClientBuilders;
@@ -579,6 +585,7 @@ private CloseableHttpClient createHttpClient() {
579585
.setMaxConnPerRoute(MAX_CONN_PER_ROUTE)
580586
.setMaxConnTotal(MAX_CONN_TOTAL)
581587
.useSystemProperties()
588+
.setRedirectStrategy(new SameMethodRedirectStrategy())
582589
.setDefaultRequestConfig(requestConfig)
583590
.build();
584591
}
@@ -593,10 +600,43 @@ public RequestConfig getDefaultRequestConfig() {
593600
.setConnectTimeout(currentConnectionTimeout)
594601
.setConnectionRequestTimeout(currentConnectionRequestTimeout)
595602
.setSocketTimeout(currentSocketTimeout)
603+
.setRedirectsEnabled(true)
604+
.setRelativeRedirectsAllowed(true)
605+
.setExpectContinueEnabled(true)
596606
.setCookieSpec(CookieSpecs.STANDARD)
597607
.build();
598608
}
599609

610+
/**
611+
* Redirect strategy that follows 301/302/307/308 for any HTTP method and preserves the original method and entity.
612+
*/
613+
private static class SameMethodRedirectStrategy extends DefaultRedirectStrategy {
614+
private static final String[] REDIRECT_METHODS = new String[] { "GET", "HEAD", "POST", "PUT", "DELETE",
615+
"PATCH" };
616+
617+
@Override
618+
protected boolean isRedirectable(String method) {
619+
for (String m : REDIRECT_METHODS) {
620+
if (m.equalsIgnoreCase(method)) {
621+
return true;
622+
}
623+
}
624+
return false;
625+
}
626+
627+
@Override
628+
public HttpUriRequest getRedirect(HttpRequest request,
629+
HttpResponse response, HttpContext context)
630+
throws ProtocolException {
631+
URI uri = getLocationURI(request, response, context);
632+
// Preserve original method and entity
633+
RequestBuilder rb = RequestBuilder
634+
.copy(request);
635+
rb.setUri(uri);
636+
return rb.build();
637+
}
638+
}
639+
600640
/**
601641
* Switches the current timeout settings to use the SPARQL-specific timeouts. This method should be called when
602642
* making SPARQL SERVICE calls to apply shorter timeout values.

0 commit comments

Comments
 (0)