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);
- }
-
- }
-}