From b8f1d4cb36c9101ce6fae50022a972d70794553f Mon Sep 17 00:00:00 2001 From: UnlegitDqrk Date: Tue, 10 Feb 2026 23:15:52 +0100 Subject: [PATCH] Headers and method doesnt work right now --- pom.xml | 2 +- .../infonamelib/OacWebRequestBroker.java | 123 ++++++++++++------ .../infonamelib/OacWebURLConnection.java | 96 +++++++++++++- .../infonamelib/test/URLTests.java | 28 ---- 4 files changed, 174 insertions(+), 75 deletions(-) delete mode 100644 src/test/java/org/openautonomousconnection/infonamelib/test/URLTests.java diff --git a/pom.xml b/pom.xml index d517320..e6153ab 100644 --- a/pom.xml +++ b/pom.xml @@ -6,7 +6,7 @@ org.openautonomousconnection InfoNameLib - 1.0.0-BETA.1.2 + 1.0.0-BETA.1.3 Open Autonomous Connection https://open-autonomous-connection.org/ diff --git a/src/main/java/org/openautonomousconnection/infonamelib/OacWebRequestBroker.java b/src/main/java/org/openautonomousconnection/infonamelib/OacWebRequestBroker.java index 8a93315..6e167e4 100644 --- a/src/main/java/org/openautonomousconnection/infonamelib/OacWebRequestBroker.java +++ b/src/main/java/org/openautonomousconnection/infonamelib/OacWebRequestBroker.java @@ -28,7 +28,9 @@ public final class OacWebRequestBroker { private static final long CONNECT_TIMEOUT_SECONDS = 10; private static final long RESPONSE_TIMEOUT_SECONDS = 25; + private final Object responseLock = new Object(); + private volatile ProtocolClient client; private volatile CountDownLatch connectionLatch; private volatile String currentInfoName; @@ -46,22 +48,6 @@ public final class OacWebRequestBroker { return INSTANCE; } - private static String safeContentType(String ct) { - return (ct == null || ct.isBlank()) ? "application/octet-stream" : ct; - } - - private static Map safeHeaders(Map headers) { - return (headers == null || headers.isEmpty()) ? Map.of() : Map.copyOf(headers); - } - - private static String normalizePath(String path) { - if (path == null || path.isBlank() || "/".equals(path)) { - return "index.html"; - } - String p = path.startsWith("/") ? path.substring(1) : path; - return p.isBlank() ? "index.html" : p; - } - /** * Attaches the client used to send INS/Web packets. * @@ -72,29 +58,44 @@ public final class OacWebRequestBroker { } /** - * Fetches a URL via OAC protocol (used by {@link java.net.URLConnection}). + * Legacy overload (GET with no body). * - * @param url web:// URL + * @param url web:// URL + * @param headers request headers * @return response */ - public OacWebResponse fetch(URL url) { + public OacWebResponse fetch(URL url, Map headers) { + return fetch(url, WebRequestMethod.GET, headers, null); + } + + /** + * Fetches a URL via OAC protocol (used by {@link java.net.URLConnection}). + * + * @param url web:// URL + * @param method request method + * @param headers request headers + * @param body request body (may be null) + * @return response + */ + public OacWebResponse fetch(URL url, WebRequestMethod method, Map headers, byte[] body) { Objects.requireNonNull(url, "url"); + Objects.requireNonNull(method, "method"); ProtocolClient c = this.client; if (c == null) { throw new IllegalStateException("ProtocolClient not attached. Call OacWebUrlInstaller.installOnce(..., client) first."); } - Response r = openAndAwait(c, url); + Response r = openAndAwait(c, url, method, headers, body); - byte[] body = (r.body() == null) ? new byte[0] : r.body(); - long len = body.length; + byte[] respBody = (r.body() == null) ? new byte[0] : r.body(); + long len = respBody.length; return new OacWebResponse( r.statusCode(), r.contentType(), OacWebResponse.safeHeaders(r.headers()), - new ByteArrayInputStream(body), + new ByteArrayInputStream(respBody), len ); } @@ -104,13 +105,17 @@ public final class OacWebRequestBroker { * * @param client protocol client * @param url web:// URL + * @param method request method + * @param headers request headers + * @param body request body * @return response snapshot */ - public Response openAndAwait(ProtocolClient client, URL url) { + public Response openAndAwait(ProtocolClient client, URL url, WebRequestMethod method, Map headers, byte[] body) { Objects.requireNonNull(client, "client"); Objects.requireNonNull(url, "url"); + Objects.requireNonNull(method, "method"); - open(client, url); + open(client, url, method, headers, body); return awaitResponse(RESPONSE_TIMEOUT_SECONDS, TimeUnit.SECONDS); } @@ -119,10 +124,14 @@ public final class OacWebRequestBroker { * * @param client protocol client * @param url web:// URL + * @param method request method + * @param headers request headers + * @param body request body */ - public synchronized void open(ProtocolClient client, URL url) { + public synchronized void open(ProtocolClient client, URL url, WebRequestMethod method, Map headers, byte[] body) { Objects.requireNonNull(client, "client"); Objects.requireNonNull(url, "url"); + Objects.requireNonNull(method, "method"); if (!"web".equalsIgnoreCase(url.getProtocol())) { throw new IllegalArgumentException("Unsupported protocol: " + url.getProtocol()); @@ -144,7 +153,7 @@ public final class OacWebRequestBroker { awaitConnectionIfPending(); } - sendWebRequest(client, path); + sendWebRequest(client, path, method, headers, body); } /** @@ -166,6 +175,11 @@ public final class OacWebRequestBroker { /** * Receives non-streamed WebResponsePacket. + * + * @param statusCode status code + * @param contentType content type + * @param headers headers + * @param body body bytes */ public void onWebResponse(int statusCode, String contentType, Map headers, byte[] body) { ResponseState st = responseState; @@ -188,7 +202,12 @@ public final class OacWebRequestBroker { } /** - * Receives stream start. + * Receives a stream start. + * + * @param statusCode status code + * @param contentType content type + * @param headers headers + * @param totalLength total length (may be 0/unknown) */ public void onStreamStart(int statusCode, String contentType, Map headers, long totalLength) { ResponseState st = responseState; @@ -206,12 +225,11 @@ public final class OacWebRequestBroker { } } - // ============================ - // Internal connect + request - // ============================ - /** * Receives a stream chunk (may arrive out-of-order). + * + * @param seq chunk sequence number + * @param data chunk bytes */ public void onStreamChunk(int seq, byte[] data) { ResponseState st = responseState; @@ -233,6 +251,8 @@ public final class OacWebRequestBroker { /** * Receives stream end. + * + * @param ok whether stream completed successfully */ public void onStreamEnd(boolean ok) { ResponseState st = responseState; @@ -258,6 +278,10 @@ public final class OacWebRequestBroker { /** * Waits for the current response. + * + * @param timeout timeout + * @param unit unit + * @return response snapshot */ public Response awaitResponse(long timeout, TimeUnit unit) { Objects.requireNonNull(unit, "unit"); @@ -291,11 +315,28 @@ public final class OacWebRequestBroker { } // ============================ - // Response helpers + // Internal helpers // ============================ + private static String safeContentType(String ct) { + return (ct == null || ct.isBlank()) ? "application/octet-stream" : ct; + } + + private static Map safeHeaders(Map headers) { + return (headers == null || headers.isEmpty()) ? Map.of() : Map.copyOf(headers); + } + + private static String normalizePath(String path) { + if (path == null || path.isBlank() || "/".equals(path)) { + return "index.html"; + } + String p = path.startsWith("/") ? path.substring(1) : path; + return p.isBlank() ? "index.html" : p; + } + private void resolveAndConnect(ProtocolClient client, String infoName) { if (client.getClientINSConnection() == null || !client.getClientINSConnection().isConnected()) return; + String[] parts = infoName.split("\\."); if (parts.length < 2 || parts.length > 3) { throw new IllegalArgumentException( @@ -328,8 +369,9 @@ public final class OacWebRequestBroker { private void awaitConnectionIfPending() { CountDownLatch latch = connectionLatch; - if (latch == null || client.getClientServerConnection() == null || client.getClientINSConnection() == null) + if (latch == null || client == null || client.getClientServerConnection() == null || client.getClientINSConnection() == null) { return; + } try { if (!latch.await(CONNECT_TIMEOUT_SECONDS, TimeUnit.SECONDS)) { @@ -341,9 +383,10 @@ public final class OacWebRequestBroker { } } - private void sendWebRequest(ProtocolClient client, String path) { + private void sendWebRequest(ProtocolClient client, String path, WebRequestMethod method, Map headers, byte[] body) { Objects.requireNonNull(client, "client"); Objects.requireNonNull(path, "path"); + Objects.requireNonNull(method, "method"); if (client.getClientServerConnection() == null || !client.getClientServerConnection().isConnected()) { awaitConnectionIfPending(); @@ -355,9 +398,9 @@ public final class OacWebRequestBroker { WebRequestPacket packet = new WebRequestPacket( path, - WebRequestMethod.GET, - Map.of(), - null + method, + (headers == null ? Map.of() : headers), + body ); try { @@ -374,7 +417,7 @@ public final class OacWebRequestBroker { } private void flushChunksLocked(ResponseState st) { - for (; ; ) { + for (;;) { byte[] next = st.chunkBuffer.remove(st.nextExpectedSeq); if (next == null) break; @@ -405,9 +448,11 @@ public final class OacWebRequestBroker { private final CountDownLatch done = new CountDownLatch(1); private final ByteArrayOutputStream body = new ByteArrayOutputStream(64 * 1024); private final Map chunkBuffer = new HashMap<>(); + private int statusCode = 0; private String contentType = "application/octet-stream"; private Map headers = Map.of(); + private boolean streamStarted; private long totalLength; private int nextExpectedSeq = 0; diff --git a/src/main/java/org/openautonomousconnection/infonamelib/OacWebURLConnection.java b/src/main/java/org/openautonomousconnection/infonamelib/OacWebURLConnection.java index a909d87..11cdddc 100644 --- a/src/main/java/org/openautonomousconnection/infonamelib/OacWebURLConnection.java +++ b/src/main/java/org/openautonomousconnection/infonamelib/OacWebURLConnection.java @@ -1,7 +1,11 @@ package org.openautonomousconnection.infonamelib; +import org.openautonomousconnection.protocol.versions.v1_0_0.beta.WebRequestMethod; + +import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; +import java.io.OutputStream; import java.net.URL; import java.net.URLConnection; import java.util.*; @@ -9,7 +13,15 @@ import java.util.*; /** * URLConnection implementation that maps "web://" URLs to OAC WebRequestPacket/WebResponsePacket. * - *

Important: This connection enforces global serialization of requests because the protocol has no request IDs.

+ *

This implementation supports:

+ *
    + *
  • GET requests via {@link #getInputStream()}
  • + *
  • POST requests via {@link #getOutputStream()} (e.g. HTML form submits)
  • + *
  • Request headers via {@link #setRequestProperty(String, String)}
  • + *
  • Redirect following (301/302/303/307/308) with session propagation
  • + *
+ * + *

Important: the underlying protocol has no request IDs, so the broker enforces single-flight.

*/ public final class OacWebURLConnection extends URLConnection { @@ -20,13 +32,22 @@ public final class OacWebURLConnection extends URLConnection { private boolean connected; private OacWebResponse response; + private final Map requestHeaders = new LinkedHashMap<>(); + private final ByteArrayOutputStream requestBody = new ByteArrayOutputStream(1024); + + /** + * Creates a new OAC URLConnection. + * + * @param url the web:// URL + * @param broker request broker + */ public OacWebURLConnection(URL url, OacWebRequestBroker broker) { super(url); this.broker = Objects.requireNonNull(broker, "broker"); } private static String headerValue(Map headers, String nameLower) { - if (headers == null || headers.isEmpty()) return null; + if (headers == null || headers.isEmpty() || nameLower == null) return null; for (Map.Entry e : headers.entrySet()) { if (e.getKey() == null) continue; if (e.getKey().trim().toLowerCase(Locale.ROOT).equals(nameLower)) { @@ -36,31 +57,92 @@ public final class OacWebURLConnection extends URLConnection { return null; } + @Override + public void setRequestProperty(String key, String value) { + if (key == null) return; + if (value == null) requestHeaders.remove(key); + else requestHeaders.put(key, value); + } + + @Override + public String getRequestProperty(String key) { + if (key == null) return null; + for (Map.Entry e : requestHeaders.entrySet()) { + if (e.getKey() != null && e.getKey().equalsIgnoreCase(key)) return e.getValue(); + } + return null; + } + + @Override + public Map> getRequestProperties() { + Map> out = new LinkedHashMap<>(); + for (Map.Entry e : requestHeaders.entrySet()) { + out.put(e.getKey(), e.getValue() == null ? List.of() : List.of(e.getValue())); + } + return Collections.unmodifiableMap(out); + } + + @Override + public OutputStream getOutputStream() { + setDoOutput(true); + return requestBody; + } + @Override public void connect() throws IOException { if (connected) return; URL cur = this.url; + + // Determine initial method/body. + byte[] body = (getDoOutput() && requestBody.size() > 0) ? requestBody.toByteArray() : null; + WebRequestMethod method = (body != null) ? WebRequestMethod.POST : WebRequestMethod.GET; + + // Ensure content-type exists for form posts if caller didn't set it. + if (method == WebRequestMethod.POST && headerValue(requestHeaders, "content-type") == null) { + requestHeaders.put("content-type", "application/x-www-form-urlencoded; charset=utf-8"); + } + + // Redirect loop. OacWebResponse resp = null; + Map carryHeaders = new LinkedHashMap<>(requestHeaders); for (int i = 0; i <= MAX_REDIRECTS; i++) { - resp = broker.fetch(cur); + resp = broker.fetch(cur, method, carryHeaders, body); int code = resp.statusCode(); if (code == 301 || code == 302 || code == 303 || code == 307 || code == 308) { String loc = headerValue(resp.headers(), "location"); - if (loc == null || loc.isBlank()) { - break; + if (loc == null || loc.isBlank()) break; + + // Propagate session header from redirect response to the next request. + String session = headerValue(resp.headers(), "session"); + if (session != null && !session.isBlank()) { + carryHeaders.put("session", session); } + + // Resolve redirect URL. try { cur = new URL(cur, loc); - continue; } catch (Exception ex) { break; } + + // Redirect method handling: + // - 303: switch to GET + // - 301/302: commonly switch to GET for POST (browser-like) + // - 307/308: keep method and body + if (code == 303) { + method = WebRequestMethod.GET; + body = null; + } else if ((code == 301 || code == 302) && method == WebRequestMethod.POST) { + method = WebRequestMethod.GET; + body = null; + } + + continue; } - // Non-redirect or redirect that cannot be followed -> stop here. break; } diff --git a/src/test/java/org/openautonomousconnection/infonamelib/test/URLTests.java b/src/test/java/org/openautonomousconnection/infonamelib/test/URLTests.java deleted file mode 100644 index ce69eca..0000000 --- a/src/test/java/org/openautonomousconnection/infonamelib/test/URLTests.java +++ /dev/null @@ -1,28 +0,0 @@ -package org.openautonomousconnection.infonamelib.test; - -import org.junit.jupiter.api.Test; -import org.openautonomousconnection.infonamelib.InfoNames; - -import java.net.MalformedURLException; -import java.net.URI; -import java.net.URL; - -public class URLTests { - @Test - public void createWebUrl() { - System.out.println("Registering OAC protocols"); - - InfoNames.registerOACInfoNameProtocols(); - - System.out.println("Creating url to: web://localhost:8080"); - - URI uri = URI.create("web://localhost:8080"); - - try { - URL url = uri.toURL(); - } catch (MalformedURLException e) { - throw new RuntimeException(e); - } - - } -}