From e5eb6667b354b9ad4fd9ebd1ba800a8405fc3cf0 Mon Sep 17 00:00:00 2001 From: John Connelly Date: Wed, 22 Jul 2026 16:26:55 -0700 Subject: [PATCH] Implemented proxy warning headers. - Proxy can return x-nosql-warning; the SDK logs each distinct message once per handle at WARNING, including error responses. - Added coverage for duplicate suppression and distinct warnings. --- .../java/oracle/nosql/driver/http/Client.java | 14 ++ .../nosql/driver/util/HttpConstants.java | 6 + .../nosql/driver/http/ClientWarningTest.java | 121 ++++++++++++++++++ 3 files changed, 141 insertions(+) create mode 100644 driver/src/test/java/oracle/nosql/driver/http/ClientWarningTest.java diff --git a/driver/src/main/java/oracle/nosql/driver/http/Client.java b/driver/src/main/java/oracle/nosql/driver/http/Client.java index 711bd7d6..52a9385d 100644 --- a/driver/src/main/java/oracle/nosql/driver/http/Client.java +++ b/driver/src/main/java/oracle/nosql/driver/http/Client.java @@ -31,6 +31,7 @@ import static oracle.nosql.driver.util.HttpConstants.REQUEST_ID_HEADER; import static oracle.nosql.driver.util.HttpConstants.SERVER_SERIAL_VERSION; import static oracle.nosql.driver.util.HttpConstants.SERVER_VERSION; +import static oracle.nosql.driver.util.HttpConstants.SERVER_WARNING; import static oracle.nosql.driver.util.HttpConstants.USER_AGENT; import static oracle.nosql.driver.util.HttpConstants.X_RATELIMIT_DELAY; import static oracle.nosql.driver.util.LogUtil.isLoggable; @@ -1270,6 +1271,8 @@ final Result processResponse(HttpResponseStatus status, short serialVersionUsed, short queryVersionUsed) { + processProxyWarning(headers); + if (!HttpResponseStatus.OK.equals(status)) { processNotOKResponse(status, content); @@ -1299,6 +1302,17 @@ final Result processResponse(HttpResponseStatus status, return res; } + /* Log a proxy-supplied warning once for each distinct message. */ + void processProxyWarning(HttpHeaders headers) { + if (headers == null) { + return; + } + String warning = headers.get(SERVER_WARNING); + if (warning != null && !warning.isEmpty()) { + oneTimeMessage(warning); + } + } + /** * Process an OK response * diff --git a/driver/src/main/java/oracle/nosql/driver/util/HttpConstants.java b/driver/src/main/java/oracle/nosql/driver/util/HttpConstants.java index 0a85b0f6..37ecf47c 100644 --- a/driver/src/main/java/oracle/nosql/driver/util/HttpConstants.java +++ b/driver/src/main/java/oracle/nosql/driver/util/HttpConstants.java @@ -63,6 +63,12 @@ public class HttpConstants { */ public static final String SERVER_VERSION = "x-nosql-version"; + /** + * A message supplied by the proxy for the driver to log as a warning. + * The driver logs each distinct message only once per handle. + */ + public static final String SERVER_WARNING = "x-nosql-warning"; + /** * Headers possibly set by the load balancer service to indicate original * IP address diff --git a/driver/src/test/java/oracle/nosql/driver/http/ClientWarningTest.java b/driver/src/test/java/oracle/nosql/driver/http/ClientWarningTest.java new file mode 100644 index 00000000..a08ca490 --- /dev/null +++ b/driver/src/test/java/oracle/nosql/driver/http/ClientWarningTest.java @@ -0,0 +1,121 @@ +/*- + * Copyright (c) 2011, 2026 Oracle and/or its affiliates. All rights reserved. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * https://oss.oracle.com/licenses/upl/ + */ + +package oracle.nosql.driver.http; + +import static oracle.nosql.driver.util.HttpConstants.SERVER_WARNING; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertThrows; + +import java.net.URL; +import java.util.ArrayList; +import java.util.List; +import java.util.logging.Handler; +import java.util.logging.Level; +import java.util.logging.LogRecord; +import java.util.logging.Logger; + +import org.junit.Test; + +import io.netty.buffer.Unpooled; +import io.netty.handler.codec.http.DefaultHttpHeaders; +import io.netty.handler.codec.http.HttpResponseStatus; +import io.netty.handler.ssl.SslContext; +import oracle.nosql.driver.AuthorizationProvider; +import oracle.nosql.driver.NoSQLException; +import oracle.nosql.driver.NoSQLHandleConfig; +import oracle.nosql.driver.httpclient.HttpClient; +import oracle.nosql.driver.ops.Request; + +public class ClientWarningTest { + + @Test + public void proxyWarningIsLoggedOncePerMessage() { + Logger logger = Logger.getLogger(getClass().getName()); + logger.setUseParentHandlers(false); + logger.setLevel(Level.ALL); + CapturingHandler handler = new CapturingHandler(); + logger.addHandler(handler); + + Client client = new TestClient(logger, config()); + try { + DefaultHttpHeaders headers = new DefaultHttpHeaders(); + headers.add(SERVER_WARNING, "Upgrade the SDK"); + processResponse(client, headers); + processResponse(client, headers); + + headers.set(SERVER_WARNING, "A different proxy warning"); + processResponse(client, headers); + + assertEquals(2, handler.messages.size()); + assertEquals("Upgrade the SDK", handler.messages.get(0)); + assertEquals("A different proxy warning", handler.messages.get(1)); + } finally { + client.shutdown(); + logger.removeHandler(handler); + } + } + + private void processResponse(Client client, DefaultHttpHeaders headers) { + assertThrows(NoSQLException.class, + () -> client.processResponse( + HttpResponseStatus.INTERNAL_SERVER_ERROR, + headers, Unpooled.EMPTY_BUFFER, null, + (short) 0, (short) 0)); + } + + private NoSQLHandleConfig config() { + NoSQLHandleConfig config = + new NoSQLHandleConfig("http://localhost:8080"); + config.setAuthorizationProvider(new AuthorizationProvider() { + @Override + public String getAuthorizationString(Request request) { + return "test"; + } + + @Override + public void close() { + } + }); + return config; + } + + private static class TestClient extends Client { + TestClient(Logger logger, NoSQLHandleConfig config) { + super(logger, config); + } + + @Override + public HttpClient createHttpClient(URL url, + NoSQLHandleConfig config, + SslContext sslCtx, + Logger logger) { + return new HttpClient(url.getHost(), url.getPort(), 1, + -1, 0, 0, 0, null, 0, + "ClientWarningTest", logger); + } + } + + private static class CapturingHandler extends Handler { + private final List messages = new ArrayList<>(); + + @Override + public void publish(LogRecord record) { + if (record.getLevel() == Level.WARNING) { + messages.add(record.getMessage()); + } + } + + @Override + public void flush() { + } + + @Override + public void close() { + } + } +}