You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@jena.apache.org by an...@apache.org on 2026/04/28 13:52:37 UTC
(jena) 02/02: GH-3679: Return HttpHeaders in HTTPException and QueryExceptionHTTP
This is an automated email from the ASF dual-hosted git repository.
afs pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/jena.git
commit 4ad0de7ae3890c504f206177c5a1dbcadda811cc
Author: Andy Seaborne <an...@apache.org>
AuthorDate: Sun Apr 26 20:31:37 2026 +0100
GH-3679: Return HttpHeaders in HTTPException and QueryExceptionHTTP
---
.../org/apache/jena/atlas/web/HttpException.java | 49 ++++----
.../main/java/org/apache/jena/http/HttpLib.java | 8 +-
.../java/org/apache/jena/http/auth/AuthLib.java | 3 +-
.../sparql/engine/http/QueryExceptionHTTP.java | 132 ++++++++++++---------
.../sparql/exec/http/TestQueryExecCleanServer.java | 7 +-
.../jena/sparql/exec/http/TestQueryExecHTTP.java | 25 ++++
6 files changed, 137 insertions(+), 87 deletions(-)
diff --git a/jena-arq/src/main/java/org/apache/jena/atlas/web/HttpException.java b/jena-arq/src/main/java/org/apache/jena/atlas/web/HttpException.java
index 7128972cd7..d84da1c805 100644
--- a/jena-arq/src/main/java/org/apache/jena/atlas/web/HttpException.java
+++ b/jena-arq/src/main/java/org/apache/jena/atlas/web/HttpException.java
@@ -57,7 +57,7 @@ public class HttpException extends RuntimeException {
public static HttpException create(HttpResponse<?> response) {
return HttpException.builder()
.statusCode(response.statusCode())
- .httpHeaders(response.headers())
+ .httpResponseHeaders(response.headers())
.build();
}
@@ -69,6 +69,7 @@ public class HttpException extends RuntimeException {
return HttpException.builder()
.statusCode(other.getStatusCode())
.statusLine(other.getStatusLine())
+ .httpResponseHeaders(other.getHttpResponseHeaders())
.responseMessage(other.getResponse())
.cause(other.getCause())
.build();
@@ -107,58 +108,58 @@ public class HttpException extends RuntimeException {
return this;
}
- public Builder httpHeaders(HttpHeaders httpHeaders) {
+ public Builder httpResponseHeaders(HttpHeaders httpHeaders) {
this.httpHeaders = httpHeaders;
return this;
}
public HttpException build() {
- return new HttpException(statusCode, statusLine, responseMessage, cause);
+ return new HttpException(statusCode, statusLine, httpHeaders, responseMessage, cause);
}
}
// HTTP/2 does not have an information message.
+ private HttpException(int statusCode, String statusLine, HttpHeaders responseHttpHeaders, String responseBody, Throwable cause) {
+ super(exMessage(statusCode, statusLine), cause);
+ this.statusCode = statusCode;
+ this.statusLine = statusLine ;
+ this.responseHeaders = responseHttpHeaders;
+ this.responseBody = responseBody;
+ }
+
+ private static String exMessage(int statusCode, String statusLine) {
+ if ( statusLine == null )
+ statusLine = HttpSC.getMessage(statusCode);
+ return statusCode+" - "+HttpSC.getMessage(statusCode);
+ }
+
/** @deprecated Use {@link HttpException#create(int)} */
- @Deprecated
+ @Deprecated(forRemoval = true)
public HttpException(int statusCode) {
this(statusCode, null, null, null, null);
}
/** @deprecated Use {@link HttpException#builder()} */
- @Deprecated
+ @Deprecated(forRemoval = true)
public HttpException(int statusCode, String statusLine) {
this(statusCode, statusLine, null, null, null);
}
/** @deprecated Use {@link HttpException#create(HttpResponse)} or {@link HttpException#builder()} */
- @Deprecated
+ @Deprecated(forRemoval = true)
public HttpException(int statusCode, String statusLine, String responseMessage) {
this(statusCode, statusLine, null, responseMessage, null);
}
/** @deprecated Use {@link HttpException#create(HttpResponse)} or {@link HttpException#builder()} */
- @Deprecated
+ @Deprecated(forRemoval = true)
public HttpException(int statusCode, String statusLine, String responseMessage, Throwable cause) {
this(statusCode, statusLine, null, responseMessage, cause);
}
- private HttpException(int statusCode, String statusLine, HttpHeaders responseHttpHeaders, String responseBody, Throwable cause) {
- super(exMessage(statusCode, statusLine), cause);
- this.statusCode = statusCode;
- this.statusLine = statusLine ;
- this.responseHeaders = responseHttpHeaders;
- this.responseBody = responseBody;
- }
-
- private static String exMessage(int statusCode, String statusLine) {
- if ( statusLine == null )
- statusLine = HttpSC.getMessage(statusCode);
- return statusCode+" - "+HttpSC.getMessage(statusCode);
- }
-
/** @deprecated Use {@link HttpException#error(String)} */
@Deprecated
- private HttpException(String message) {
+ public HttpException(String message) {
super(message);
this.statusCode = -1;
this.statusLine = null ;
@@ -168,7 +169,7 @@ public class HttpException extends RuntimeException {
/** @deprecated Use {@link HttpException#error(String, Throwable)} */
@Deprecated
- private HttpException(String message, Throwable cause) {
+ public HttpException(String message, Throwable cause) {
super(message, cause);
this.statusCode = -1;
this.statusLine = null ;
@@ -214,7 +215,7 @@ public class HttpException extends RuntimeException {
/**
* The response headers.
*/
- public HttpHeaders getHttpResponseHeader() {
+ public HttpHeaders getHttpResponseHeaders() {
return responseHeaders;
}
diff --git a/jena-arq/src/main/java/org/apache/jena/http/HttpLib.java b/jena-arq/src/main/java/org/apache/jena/http/HttpLib.java
index a2ffceb621..422978e154 100644
--- a/jena-arq/src/main/java/org/apache/jena/http/HttpLib.java
+++ b/jena-arq/src/main/java/org/apache/jena/http/HttpLib.java
@@ -283,7 +283,7 @@ public class HttpLib {
static HttpException exception(HttpResponse<InputStream> response, int httpStatusCode) {
InputStream in = response.body();
if ( in == null )
- return HttpException.create(httpStatusCode);
+ return HttpException.create(response);
try {
String msg;
try {
@@ -293,7 +293,11 @@ public class HttpLib {
} catch (RuntimeIOException e) {
msg = null;
}
- return HttpException.builder().statusCode(httpStatusCode).responseMessage(msg).build();
+ return HttpException.builder()
+ .statusCode(httpStatusCode)
+ .responseMessage(msg)
+ .httpResponseHeaders(response.headers())
+ .build();
} finally { IO.close(in); }
}
diff --git a/jena-arq/src/main/java/org/apache/jena/http/auth/AuthLib.java b/jena-arq/src/main/java/org/apache/jena/http/auth/AuthLib.java
index 029a9870dd..14a2ac0b75 100644
--- a/jena-arq/src/main/java/org/apache/jena/http/auth/AuthLib.java
+++ b/jena-arq/src/main/java/org/apache/jena/http/auth/AuthLib.java
@@ -40,7 +40,6 @@ import org.apache.jena.atlas.web.HttpException;
import org.apache.jena.http.AsyncHttpRDF;
import org.apache.jena.http.HttpLib;
import org.apache.jena.riot.web.HttpNames;
-import org.apache.jena.web.HttpSC;
public class AuthLib {
/**
@@ -101,7 +100,7 @@ public class AuthLib {
passwordRecord = AuthEnv.get().getUsernamePassword(request.uri());
if ( passwordRecord == null )
// No entry.
- throw HttpException.create(HttpSC.UNAUTHORIZED_401);
+ throw HttpException.create(httpResponse401);
}
// Request target - no query string.
diff --git a/jena-arq/src/main/java/org/apache/jena/sparql/engine/http/QueryExceptionHTTP.java b/jena-arq/src/main/java/org/apache/jena/sparql/engine/http/QueryExceptionHTTP.java
index 0c14af343d..126857ddda 100644
--- a/jena-arq/src/main/java/org/apache/jena/sparql/engine/http/QueryExceptionHTTP.java
+++ b/jena-arq/src/main/java/org/apache/jena/sparql/engine/http/QueryExceptionHTTP.java
@@ -21,24 +21,25 @@
package org.apache.jena.sparql.engine.http;
+import java.net.http.HttpHeaders;
+
import org.apache.jena.atlas.web.HttpException;
import org.apache.jena.query.QueryException;
import org.apache.jena.web.HttpSC;
/**
- * Exception class for all operations in the SPARQL client library. Error codes are
- * as HTTP status codes.
+ * Exception class for all HTTP operations in the SPARQL client library.
+ * Error codes are as HTTP status codes.
*/
public class QueryExceptionHTTP extends QueryException
{
public static final int noStatusCode = -1234;
private int statusCode = noStatusCode;
- private final String responseMessage;
- private String statusLine;
- private String response;
+ private final String statusLine;
- // Codes for extra errors. We use HTTP error codes so
- // these are negative to avoid clashes
+ private final String responseBody;
+ private final HttpHeaders responseHeaders;
+ // Codes for extra errors. We use HTTP error codes so these are negative to avoid clashes
public static final int NoServer = -404;
public static QueryExceptionHTTP rewrap(HttpException httpEx) {
@@ -46,8 +47,8 @@ public class QueryExceptionHTTP extends QueryException
// ARQ machinery we use these days means the internal HTTP errors come back as HttpException
// Therefore we need to wrap appropriately
int responseCode = httpEx.getStatusCode();
- if (responseCode != -1) {
- // Was an actual HTTP error
+ if (responseCode > 0) {
+ // It was an actual HTTP error
String responseLine = httpEx.getStatusLine() != null ? httpEx.getStatusLine() : HttpSC.getMessage(responseCode);
return new QueryExceptionHTTP(responseCode, responseLine, httpEx);
} else if (httpEx.getMessage() != null) {
@@ -62,25 +63,14 @@ public class QueryExceptionHTTP extends QueryException
}
}
- /**
- * Constructor for QueryExceptionHTTP.
- * @param responseCode
- * @param responseMessage
- */
- public QueryExceptionHTTP(int responseCode, String responseMessage) {
- super(responseMessage);
- this.statusCode = responseCode;
- this.responseMessage = responseMessage;
- }
-
- /**
- * Constructor for QueryExceptionHTTP.
- * @param responseCode
- */
- public QueryExceptionHTTP(int responseCode) {
- super();
+ /** @deprecated Use {@ink #wrap(HttpException)} */
+ @Deprecated
+ public QueryExceptionHTTP(int responseCode, String messageBody, final HttpException ex) {
+ super(ex.getMessage(), ex.getCause());
this.statusCode = responseCode;
- this.responseMessage = null;
+ this.statusLine = ex.getStatusLine();
+ this.responseBody = ex.getResponse();
+ this.responseHeaders = ex.getHttpResponseHeaders();
}
/** The code for the reason for this exception
@@ -90,52 +80,33 @@ public class QueryExceptionHTTP extends QueryException
/** The message for the reason for this exception
* @return message
+ * @deprecate Use {@link #getResponseBody}
*/
- public String getResponseMessage() { return responseMessage; }
+ @Deprecated
+ public String getResponseMessage() { return responseBody; }
+
+ public HttpHeaders getResponseHeaders() { return responseHeaders; }
+
+ public String getResponseBody() { return responseBody; }
/** The response for this exception if available from HTTP
* @return response or {@code null} if no HTTP response was received
+ * @deprecate Use {@link #getResponseBody}
*/
- public String getResponse() { return response; }
+ @Deprecated
+ public String getResponse() { return getResponseBody(); }
/** The status line for the response for this exception if available from HTTP
* @return status line or {@code null} if no HTTP response was received
*/
public String getStatusLine() { return statusLine; }
- /**
- * Constructor for HttpException used for some unexpected execution error.
- * @param cause
- */
- public QueryExceptionHTTP(Throwable cause) {
- super(cause);
- this.statusCode = noStatusCode;
- this.responseMessage = null;
- }
-
- public QueryExceptionHTTP(String msg, Throwable cause) {
- super(msg, cause);
- this.statusCode = noStatusCode;
- this.responseMessage = msg;
- }
-
- public QueryExceptionHTTP(int responseCode, String message, Throwable cause) {
- this(message, cause);
- this.statusCode = responseCode;
- }
-
- public QueryExceptionHTTP(int responseCode, String message, final HttpException ex) {
- this(responseCode, message, ex.getCause());
- this.statusLine = ex.getStatusLine();
- this.response = ex.getResponse();
- }
-
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append("HttpException: ");
int code = getStatusCode();
- if ( code != QueryExceptionHTTP.noStatusCode ) {
+ if ( code > 0 ) {
sb.append(code);
if ( getResponseMessage() != null ) {
sb.append(" ");
@@ -146,4 +117,51 @@ public class QueryExceptionHTTP extends QueryException
}
return sb.toString();
}
+
+ // Older constructors, no longer used.
+
+ @Deprecated(forRemoval = true)
+ public QueryExceptionHTTP(int responseCode, String responseMessage) {
+ super(responseMessage);
+ this.statusCode = responseCode;
+ this.statusLine = responseMessage;
+ this.responseHeaders = null;
+ this.responseBody = null;
+ }
+
+ @Deprecated(forRemoval = true)
+ public QueryExceptionHTTP(int responseCode) {
+ super();
+ this.statusCode = responseCode;
+ this.statusLine = null;
+ this.responseHeaders = null;
+ this.responseBody = null;
+ }
+
+ @Deprecated(forRemoval = true)
+ public QueryExceptionHTTP(Throwable cause) {
+ super(cause);
+ this.statusCode = noStatusCode;
+ this.statusLine = null;
+ this.responseHeaders = null;
+ this.responseBody = null;
+ }
+
+ @Deprecated(forRemoval = true)
+ public QueryExceptionHTTP(String msg, Throwable cause) {
+ super(msg, cause);
+ this.statusCode = noStatusCode;
+ this.statusLine = null;
+ this.responseHeaders = null;
+ this.responseBody = null;
+ }
+
+ @Deprecated(forRemoval = true)
+ public QueryExceptionHTTP(int responseCode, String message, Throwable cause) {
+ super(message, cause);
+ this.statusCode = responseCode;
+ this.statusLine = null;
+ this.responseHeaders = null;
+ this.responseBody = null;
+ }
}
diff --git a/jena-integration-tests/src/test/java/org/apache/jena/sparql/exec/http/TestQueryExecCleanServer.java b/jena-integration-tests/src/test/java/org/apache/jena/sparql/exec/http/TestQueryExecCleanServer.java
index 29cd6a215f..8428080900 100644
--- a/jena-integration-tests/src/test/java/org/apache/jena/sparql/exec/http/TestQueryExecCleanServer.java
+++ b/jena-integration-tests/src/test/java/org/apache/jena/sparql/exec/http/TestQueryExecCleanServer.java
@@ -24,6 +24,7 @@ package org.apache.jena.sparql.exec.http;
import static org.apache.jena.atlas.lib.StrUtils.strjoinNL;
import static org.apache.jena.sparql.sse.SSE.parseQuad;
import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertThrows;
import java.util.concurrent.TimeUnit;
@@ -43,7 +44,7 @@ import org.apache.jena.sparql.engine.http.QueryExceptionHTTP;
/**
* Tests for {@link QueryExecHTTP} with no authentication.
- * See {@link TestQueryExecHTTP} for most of the tests.
+ * See {@link TestQueryExecHTTP} for most of the tests.
*/
public class TestQueryExecCleanServer {
// Unlike TestQueryExecutionHTTP these tests run a clean server each time.
@@ -104,10 +105,12 @@ public class TestQueryExecCleanServer {
// Short!
.timeout(10, TimeUnit.MILLISECONDS)
.build() ) {
- assertThrows(QueryExceptionHTTP.class, ()->{
+ QueryExceptionHTTP ex = assertThrows(QueryExceptionHTTP.class, ()->{
long x = Iter.count(qExec.select());
assertEquals(2, x);
});
+ // Client side timeout.
+ assertNull(ex.getResponseHeaders());
}
}
}
diff --git a/jena-integration-tests/src/test/java/org/apache/jena/sparql/exec/http/TestQueryExecHTTP.java b/jena-integration-tests/src/test/java/org/apache/jena/sparql/exec/http/TestQueryExecHTTP.java
index ae30518ee9..79c29ac8ba 100644
--- a/jena-integration-tests/src/test/java/org/apache/jena/sparql/exec/http/TestQueryExecHTTP.java
+++ b/jena-integration-tests/src/test/java/org/apache/jena/sparql/exec/http/TestQueryExecHTTP.java
@@ -24,11 +24,14 @@ package org.apache.jena.sparql.exec.http;
import static org.apache.jena.sparql.sse.SSE.parseQuad;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertTrue;
+import java.net.http.HttpHeaders;
import java.util.Iterator;
import org.junit.jupiter.api.AfterAll;
+import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
@@ -42,10 +45,13 @@ import org.apache.jena.graph.Triple;
import org.apache.jena.query.Query;
import org.apache.jena.query.QueryFactory;
import org.apache.jena.query.Syntax;
+import org.apache.jena.riot.web.HttpNames;
import org.apache.jena.sparql.core.DatasetGraph;
import org.apache.jena.sparql.core.DatasetGraphFactory;
import org.apache.jena.sparql.core.Quad;
+import org.apache.jena.sparql.engine.http.QueryExceptionHTTP;
import org.apache.jena.sparql.exec.RowSet;
+import org.apache.jena.web.HttpSC;
/**
* Tests for {@link QueryExecHTTP} with no authentication.
@@ -99,6 +105,25 @@ public class TestQueryExecHTTP {
}
}
+ @Test
+ public void query_select_bad_01() {
+ LogCtl.withLevel(Fuseki.actionLog, "ERROR", ()->{
+
+ // Make a bad request
+ try ( QueryExecHTTP qExec = QueryExecHTTP.newBuilder().endpoint(dsURL).queryString("SELECT * { JUNK }").build() ) {
+ QueryExceptionHTTP ex = Assertions.assertThrows(QueryExceptionHTTP.class, ()->qExec.select());
+ assertEquals(HttpSC.BAD_REQUEST_400, ex.getStatusCode());
+ assertNotNull(ex.getResponseHeaders());
+ HttpHeaders headers = ex.getResponseHeaders();
+ // Fuseki includes the parse error.
+ assertNotNull(headers);
+ var x = headers.firstValue(HttpNames.hContentType);
+ assertFalse(x.isEmpty());
+ assertNotNull(x.get());
+ }
+ });
+ }
+
@Test
public void query_select_post_form_1() {
try ( QueryExecHTTP qExec = QueryExecHTTP.newBuilder().sendMode(QuerySendMode.asPostForm)