You are viewing a plain text version of this content. The canonical link for it is here.
Posted to notifications@asterixdb.apache.org by AsterixDB Code Review <do...@asterix-gerrit.ics.uci.edu> on 2026/09/30 08:25:26 UTC
[M] Change in asterixdb[master]: [NO ISSUE][API] Use request scheme in result handle URLs
From Murtadha Hubail <mh...@apache.org>:
Murtadha Hubail has uploaded this change for review. ( https://asterix-gerrit.ics.uci.edu/c/asterixdb/+/21948?usp=email )
Change subject: [NO ISSUE][API] Use request scheme in result handle URLs
......................................................................
[NO ISSUE][API] Use request scheme in result handle URLs
Host-qualified result and status handles were always built with
"http://", while the host and port are taken from the request. A request
received over TLS therefore got handles pointing plaintext at the TLS
port, which cannot be fetched as returned.
Ext-ref: MB-74299
Generated-by: Claude Opus 5.5 (1M context) <no...@anthropic.com>
Change-Id: If97b78f5899546195ec13ff8358433cc7df56270
---
M asterixdb/asterix-app/src/main/java/org/apache/asterix/api/http/server/NCQueryServiceServlet.java
M asterixdb/asterix-app/src/main/java/org/apache/asterix/api/http/server/QueryServiceRequestParameters.java
M asterixdb/asterix-app/src/main/java/org/apache/asterix/api/http/server/QueryServiceServlet.java
M asterixdb/asterix-app/src/main/java/org/apache/asterix/api/http/server/QueryStatusApiServlet.java
A asterixdb/asterix-app/src/test/java/org/apache/asterix/api/http/server/ResultHandleSchemeTest.java
5 files changed, 155 insertions(+), 6 deletions(-)
git pull ssh://asterix-gerrit.ics.uci.edu:29418/asterixdb refs/changes/48/21948/1
diff --git a/asterixdb/asterix-app/src/main/java/org/apache/asterix/api/http/server/NCQueryServiceServlet.java b/asterixdb/asterix-app/src/main/java/org/apache/asterix/api/http/server/NCQueryServiceServlet.java
index e43da3f..ddb67bf 100644
--- a/asterixdb/asterix-app/src/main/java/org/apache/asterix/api/http/server/NCQueryServiceServlet.java
+++ b/asterixdb/asterix-app/src/main/java/org/apache/asterix/api/http/server/NCQueryServiceServlet.java
@@ -102,7 +102,7 @@
if (!param.isIncludeHost()) {
handleUrl = getHandleUrl(param.getPath(), delivery);
} else {
- handleUrl = getHandleUrl(param.getHost(), param.getPath(), delivery);
+ handleUrl = getHandleUrl(param.getScheme(), param.getHost(), param.getPath(), delivery);
}
try {
long timeout = param.getTimeout();
diff --git a/asterixdb/asterix-app/src/main/java/org/apache/asterix/api/http/server/QueryServiceRequestParameters.java b/asterixdb/asterix-app/src/main/java/org/apache/asterix/api/http/server/QueryServiceRequestParameters.java
index 57fc8d7..9d6585a 100644
--- a/asterixdb/asterix-app/src/main/java/org/apache/asterix/api/http/server/QueryServiceRequestParameters.java
+++ b/asterixdb/asterix-app/src/main/java/org/apache/asterix/api/http/server/QueryServiceRequestParameters.java
@@ -55,6 +55,7 @@
import com.google.common.collect.ImmutableMap;
import io.netty.handler.codec.http.HttpMethod;
+import io.netty.handler.codec.http.HttpScheme;
public class QueryServiceRequestParameters {
@@ -128,6 +129,7 @@
ImmutableMap.of("present", Boolean.TRUE, "absent", Boolean.FALSE);
private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();
+ private HttpScheme scheme = HttpScheme.HTTP;
private String host;
private String path;
private String statement;
@@ -163,6 +165,14 @@
private long resultTtlInMillis = -1L; // -1 means use system default
private boolean includeHost = false;
+ public HttpScheme getScheme() {
+ return scheme;
+ }
+
+ public void setScheme(HttpScheme scheme) {
+ this.scheme = scheme;
+ }
+
public String getHost() {
return host;
}
@@ -495,6 +505,7 @@
public void setParameters(QueryServiceServlet servlet, IServletRequest request, Map<String, String> optionalParams)
throws IOException {
+ setScheme(request.getScheme());
setHost(servlet.host(request));
setPath(servlet.servletPath(request));
setOptionalParams(optionalParams);
diff --git a/asterixdb/asterix-app/src/main/java/org/apache/asterix/api/http/server/QueryServiceServlet.java b/asterixdb/asterix-app/src/main/java/org/apache/asterix/api/http/server/QueryServiceServlet.java
index ccddc13..ba2a026 100644
--- a/asterixdb/asterix-app/src/main/java/org/apache/asterix/api/http/server/QueryServiceServlet.java
+++ b/asterixdb/asterix-app/src/main/java/org/apache/asterix/api/http/server/QueryServiceServlet.java
@@ -90,6 +90,7 @@
import org.apache.logging.log4j.Logger;
import io.netty.handler.codec.http.HttpResponseStatus;
+import io.netty.handler.codec.http.HttpScheme;
public class QueryServiceServlet extends AbstractQueryApiServlet {
protected static final Logger LOGGER = LogManager.getLogger();
@@ -200,8 +201,8 @@
return path + handlePath(delivery);
}
- protected String getHandleUrl(String host, String path, ResultDelivery delivery) {
- return "http://" + host + path + handlePath(delivery);
+ protected String getHandleUrl(HttpScheme scheme, String host, String path, ResultDelivery delivery) {
+ return scheme + "://" + host + path + handlePath(delivery);
}
private void handleRequest(IServletRequest request, IServletResponse response, boolean forceReadOnly)
@@ -444,8 +445,9 @@
private void setSessionConfig(SessionOutput sessionOutput, QueryServiceRequestParameters param,
ResultDelivery delivery) {
- String handleUrl = param.isIncludeHost() ? getHandleUrl(param.getHost(), param.getPath(), delivery)
- : getHandleUrl(param.getPath(), delivery);
+ String handleUrl =
+ param.isIncludeHost() ? getHandleUrl(param.getScheme(), param.getHost(), param.getPath(), delivery)
+ : getHandleUrl(param.getPath(), delivery);
sessionOutput.setHandleAppender(ResultUtil.createResultHandleAppender(handleUrl));
SessionConfig sessionConfig = sessionOutput.config();
SessionConfig.ClientType clientType = param.getClientType();
diff --git a/asterixdb/asterix-app/src/main/java/org/apache/asterix/api/http/server/QueryStatusApiServlet.java b/asterixdb/asterix-app/src/main/java/org/apache/asterix/api/http/server/QueryStatusApiServlet.java
index e5b9df3..277b997 100644
--- a/asterixdb/asterix-app/src/main/java/org/apache/asterix/api/http/server/QueryStatusApiServlet.java
+++ b/asterixdb/asterix-app/src/main/java/org/apache/asterix/api/http/server/QueryStatusApiServlet.java
@@ -122,7 +122,7 @@
if (uriMode) {
resHandle = servletPath + strHandle;
} else {
- resHandle = "http://" + host(request) + servletPath + strHandle;
+ resHandle = request.getScheme() + "://" + host(request) + servletPath + strHandle;
}
printer.addResultPrinter(new ResultHandlePrinter(resHandle));
if (uriMode) {
diff --git a/asterixdb/asterix-app/src/test/java/org/apache/asterix/api/http/server/ResultHandleSchemeTest.java b/asterixdb/asterix-app/src/test/java/org/apache/asterix/api/http/server/ResultHandleSchemeTest.java
new file mode 100644
index 0000000..92e0b46
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/java/org/apache/asterix/api/http/server/ResultHandleSchemeTest.java
@@ -0,0 +1,136 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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 org.apache.asterix.api.http.server;
+
+import java.nio.charset.StandardCharsets;
+import java.util.List;
+import java.util.concurrent.TimeUnit;
+
+import org.apache.asterix.api.common.AsterixHyracksIntegrationUtil;
+import org.apache.commons.io.IOUtils;
+import org.apache.http.client.entity.UrlEncodedFormEntity;
+import org.apache.http.client.methods.CloseableHttpResponse;
+import org.apache.http.client.methods.HttpGet;
+import org.apache.http.client.methods.HttpPost;
+import org.apache.http.client.methods.HttpRequestBase;
+import org.apache.http.impl.client.CloseableHttpClient;
+import org.apache.http.impl.client.HttpClients;
+import org.apache.http.message.BasicNameValuePair;
+import org.apache.hyracks.http.server.utils.HttpUtil;
+import org.junit.AfterClass;
+import org.junit.Assert;
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.ObjectMapper;
+
+/**
+ * A host-qualified result handle must carry the scheme the request arrived on, not only its host and port: a handle
+ * naming the TLS port under {@code http://} cannot be fetched as returned. The request's scheme is resolved the same
+ * way for a TLS listener and for a request forwarded by a proxy that terminated TLS, so the forwarded form stands in
+ * for a TLS cluster here.
+ */
+public class ResultHandleSchemeTest {
+
+ private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();
+ private static final String CC_ENDPOINT = "localhost:19002";
+ private static final String NC_ENDPOINT = "localhost:19004";
+ private static final long STATUS_TIMEOUT_MILLIS = TimeUnit.SECONDS.toMillis(60);
+
+ private static final AsterixHyracksIntegrationUtil INTEGRATION_UTIL = new AsterixHyracksIntegrationUtil();
+
+ @BeforeClass
+ public static void setUp() throws Exception {
+ INTEGRATION_UTIL.init(true, AsterixHyracksIntegrationUtil.DEFAULT_CONF_FILE);
+ }
+
+ @AfterClass
+ public static void tearDown() throws Exception {
+ INTEGRATION_UTIL.deinit(true);
+ }
+
+ @Test
+ public void deferredHandleFromTheCcKeepsTheRequestScheme() throws Exception {
+ assertHandle(submit(CC_ENDPOINT, "deferred", "https"), "https", CC_ENDPOINT, "/query/service/result/");
+ }
+
+ @Test
+ public void deferredHandleFromAnNcKeepsTheRequestScheme() throws Exception {
+ assertHandle(submit(NC_ENDPOINT, "deferred", "https"), "https", NC_ENDPOINT, "/query/service/result/");
+ }
+
+ @Test
+ public void resultHandleFromAsyncStatusKeepsTheRequestScheme() throws Exception {
+ String statusHandle = submit(CC_ENDPOINT, "async", "https");
+ assertHandle(statusHandle, "https", CC_ENDPOINT, "/query/service/status/");
+ assertHandle(awaitResultHandle(statusHandle, "https"), "https", CC_ENDPOINT, "/query/service/result/");
+ }
+
+ @Test
+ public void plainRequestStillGetsAnHttpHandle() throws Exception {
+ assertHandle(submit(CC_ENDPOINT, "deferred", null), "http", CC_ENDPOINT, "/query/service/result/");
+ }
+
+ private static void assertHandle(String handle, String scheme, String endpoint, String path) {
+ Assert.assertTrue(handle, handle.startsWith(scheme + "://" + endpoint + path));
+ }
+
+ private static String submit(String endpoint, String mode, String forwardedProto) throws Exception {
+ HttpPost request = new HttpPost("http://" + endpoint + "/query/service");
+ request.setEntity(new UrlEncodedFormEntity(
+ List.of(new BasicNameValuePair("statement", "select 1;"), new BasicNameValuePair("mode", mode)),
+ StandardCharsets.UTF_8));
+ return handleOf(execute(request, forwardedProto));
+ }
+
+ private static String awaitResultHandle(String statusHandle, String forwardedProto) throws Exception {
+ // the server only listens on plain HTTP; the forwarded header is what makes the request a TLS one
+ String statusUrl = statusHandle.replaceFirst("^https://", "http://");
+ long deadline = System.currentTimeMillis() + STATUS_TIMEOUT_MILLIS;
+ while (true) {
+ JsonNode status = execute(new HttpGet(statusUrl), forwardedProto);
+ if ("success".equals(status.path("status").asText())) {
+ return handleOf(status);
+ }
+ if (System.currentTimeMillis() > deadline) {
+ Assert.fail("request did not succeed in time: " + status);
+ }
+ TimeUnit.MILLISECONDS.sleep(100);
+ }
+ }
+
+ private static String handleOf(JsonNode response) {
+ JsonNode handle = response.get("handle");
+ Assert.assertNotNull(response.toString(), handle);
+ return handle.asText();
+ }
+
+ private static JsonNode execute(HttpRequestBase request, String forwardedProto) throws Exception {
+ if (forwardedProto != null) {
+ request.setHeader(HttpUtil.X_FORWARDED_PROTO.toString(), forwardedProto);
+ }
+ try (CloseableHttpClient httpClient = HttpClients.createDefault();
+ CloseableHttpResponse response = httpClient.execute(request)) {
+ String body = IOUtils.toString(response.getEntity().getContent(), StandardCharsets.UTF_8);
+ Assert.assertEquals(body, 200, response.getStatusLine().getStatusCode());
+ return OBJECT_MAPPER.readTree(body);
+ }
+ }
+}
--
To view, visit https://asterix-gerrit.ics.uci.edu/c/asterixdb/+/21948?usp=email
To unsubscribe, or for help writing mail filters, visit https://asterix-gerrit.ics.uci.edu/settings?usp=email
Gerrit-MessageType: newchange
Gerrit-Project: asterixdb
Gerrit-Branch: master
Gerrit-Change-Id: If97b78f5899546195ec13ff8358433cc7df56270
Gerrit-Change-Number: 21948
Gerrit-PatchSet: 1
Gerrit-Owner: Murtadha Hubail <mh...@apache.org>