Web protocol ready

This commit is contained in:
UnlegitDqrk
2026-02-10 19:34:28 +01:00
parent 1232330583
commit fcd9c57650
17 changed files with 921 additions and 131 deletions

View File

@@ -5,4 +5,20 @@ InfoName and URL library to make client-side connection set-ups easier
<hr> <hr>
Just set the protocol schemes to OAC-InfoName ones by calling: Just set the protocol schemes to OAC-InfoName ones by calling:
<code>InfoNames.registerOACInfoNameProtocols()</code>
```java
import dev.unlegitdqrk.unlegitlibrary.event.EventManager;
import org.openautonomousconnection.infonamelib.OacWebUrlInstaller;
import org.openautonomousconnection.infonamelib.ProtocolHandlerPackages;
import org.openautonomousconnection.protocol.side.client.ProtocolClient;
class Example {
private void init() {
EventManager eventManager = new EventManager();
ProtocolClient client = new MyImpl();
ProtocolHandlerPackages.installPackage("org.openautonomousconnection.infonamelib");
OacWebUrlInstaller.installOnce(eventManager, client);
}
}
```

11
pom.xml
View File

@@ -1,12 +1,12 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" <project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://maven.apache.org/POM/4.0.0"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<groupId>org.openautonomousconnection</groupId> <groupId>org.openautonomousconnection</groupId>
<artifactId>InfoNameLib</artifactId> <artifactId>InfoNameLib</artifactId>
<version>1.0.0-BETA.1.1</version> <version>1.0.0-BETA.1.2</version>
<organization> <organization>
<name>Open Autonomous Connection</name> <name>Open Autonomous Connection</name>
<url>https://open-autonomous-connection.org/</url> <url>https://open-autonomous-connection.org/</url>
@@ -94,6 +94,11 @@
<version>6.0.0</version> <version>6.0.0</version>
<scope>test</scope> <scope>test</scope>
</dependency> </dependency>
<dependency>
<groupId>org.openautonomousconnection</groupId>
<artifactId>Protocol</artifactId>
<version>1.0.0-BETA.7.7</version>
</dependency>
</dependencies> </dependencies>
<build> <build>

View File

@@ -1,24 +0,0 @@
/* Author: Maple
* Jan. 18 2026
* */
package org.openautonomousconnection.infonamelib;
import org.openautonomousconnection.infonamelib.protocols.ftp.FTPInfoNameURLStreamHandler;
import org.openautonomousconnection.infonamelib.protocols.web.WebInfoNameURLStreamHandler;
import java.net.URLStreamHandler;
import java.net.URLStreamHandlerFactory;
public class InfoNameURLStreamHandlerFactory implements URLStreamHandlerFactory {
@Override
public URLStreamHandler createURLStreamHandler(String protocol) {
return switch (protocol) {
case "web" -> new WebInfoNameURLStreamHandler();
case "ftp" -> new FTPInfoNameURLStreamHandler();
default -> null;
};
}
}

View File

@@ -1,16 +0,0 @@
/* Author: Maple
* Jan. 18 2026
* */
package org.openautonomousconnection.infonamelib;
import java.net.URL;
public class InfoNames {
/**
* Switches accepted Schemes in URLs to InfoName ones
*/
public static void registerOACInfoNameProtocols() {
URL.setURLStreamHandlerFactory(new InfoNameURLStreamHandlerFactory());
}
}

View File

@@ -0,0 +1,173 @@
package org.openautonomousconnection.infonamelib;
import dev.unlegitdqrk.unlegitlibrary.event.EventListener;
import dev.unlegitdqrk.unlegitlibrary.event.EventPriority;
import dev.unlegitdqrk.unlegitlibrary.event.Listener;
import dev.unlegitdqrk.unlegitlibrary.network.system.client.events.packets.C_PacketReadEvent;
import org.openautonomousconnection.protocol.packets.v1_0_0.beta.INSResponsePacket;
import org.openautonomousconnection.protocol.packets.v1_0_0.beta.web.WebResponsePacket;
import org.openautonomousconnection.protocol.packets.v1_0_0.beta.web.stream.WebStreamChunkPacket;
import org.openautonomousconnection.protocol.packets.v1_0_0.beta.web.stream.WebStreamEndPacket;
import org.openautonomousconnection.protocol.packets.v1_0_0.beta.web.stream.WebStreamStartPacket;
import org.openautonomousconnection.protocol.side.client.ProtocolClient;
import org.openautonomousconnection.protocol.side.client.events.ConnectedToProtocolServerEvent;
import org.openautonomousconnection.protocol.versions.v1_0_0.beta.INSRecord;
import org.openautonomousconnection.protocol.versions.v1_0_0.beta.INSResponseStatus;
import java.util.List;
import java.util.Objects;
/**
* Receives incoming OAC web response packets and forwards them into the request broker.
*
* <p>Important:</p>
* <ul>
* <li>The shown protocol types do not contain any correlation id.</li>
* <li>Therefore, the broker must treat the connection as single-flight (one in-flight request).</li>
* </ul>
*/
public final class OacWebPacketListener extends EventListener {
private final OacWebRequestBroker broker;
private final ProtocolClient client;
/**
* Creates a new listener bound to the given broker.
*
* @param broker broker instance
*/
public OacWebPacketListener(OacWebRequestBroker broker, ProtocolClient client) {
this.broker = Objects.requireNonNull(broker, "broker");
this.client = Objects.requireNonNull(client, "client");
}
@Listener(priority = EventPriority.HIGHEST)
public void onConnected(ConnectedToProtocolServerEvent event) {
OacWebRequestBroker.get().notifyServerConnected();
}
@Listener(priority = EventPriority.HIGHEST)
public void onPacketRead(C_PacketReadEvent event) {
Object p = event.getPacket();
if (p instanceof INSResponsePacket resp) {
INSResponseStatus status = resp.getStatus();
List<INSRecord> records = resp.getRecords();
if (status != INSResponseStatus.OK) {
broker.invalidateCurrentInfoName();
throw new IllegalStateException("INS resolution failed: " + status);
}
if (records == null || records.isEmpty() || records.getFirst() == null || records.getFirst().value == null) {
broker.invalidateCurrentInfoName();
throw new IllegalStateException("INS resolution returned no usable records");
}
String host = records.getFirst().value.trim();
if (host.isEmpty()) {
broker.invalidateCurrentInfoName();
throw new IllegalStateException("INS record value is empty");
}
String hostname;
int port;
if (!host.contains(":")) {
hostname = host;
port = 1028;
} else {
String[] split = host.split(":", 2);
hostname = split[0].trim();
String p1 = split[1].trim();
if (hostname.isEmpty() || p1.isEmpty()) {
broker.invalidateCurrentInfoName();
throw new IllegalStateException("Invalid INS host:port value: " + host);
}
try {
port = Integer.parseInt(p1);
} catch (NumberFormatException e) {
broker.invalidateCurrentInfoName();
throw new IllegalStateException("Invalid port in INS record: " + host, e);
}
}
Thread t = new Thread(() -> connectServer(hostname, port), "oac-web-server-connect");
t.setDaemon(true);
t.start();
return;
}
if (p instanceof WebResponsePacket resp) {
onWebResponse(resp);
return;
}
if (p instanceof WebStreamStartPacket start) {
onStreamStart(start);
return;
}
if (p instanceof WebStreamChunkPacket chunk) {
onStreamChunk(chunk);
return;
}
if (p instanceof WebStreamEndPacket end) {
onStreamEnd(end);
}
}
private void connectServer(String hostname, int port) {
try {
// Ensure the server connection object exists
client.buildServerConnection(null, client.getProtocolBridge().getProtocolValues().ssl);
if (client.getClientServerConnection() != null && client.getClientServerConnection().isConnected()) {
client.getClientServerConnection().disconnect();
}
client.getClientServerConnection().connect(hostname, port);
} catch (Exception e) {
broker.invalidateCurrentInfoName();
e.printStackTrace();
}
}
/**
* Handles a non-streamed WebResponsePacket.
*
* @param resp response packet
*/
private void onWebResponse(WebResponsePacket resp) {
broker.onWebResponse(resp.getStatusCode(), resp.getContentType(), resp.getHeaders(), resp.getBody());
}
/**
* Handles the beginning of a streamed response.
*
* @param start stream start packet
*/
private void onStreamStart(WebStreamStartPacket start) {
broker.onStreamStart(start.getStatusCode(), start.getContentType(), start.getHeaders(), start.getTotalLength());
}
/**
* Handles a chunk of a streamed response.
*
* @param chunk chunk packet
*/
private void onStreamChunk(WebStreamChunkPacket chunk) {
broker.onStreamChunk(chunk.getSeq(), chunk.getData());
}
/**
* Handles stream end.
*
* @param end stream end packet
*/
private void onStreamEnd(WebStreamEndPacket end) {
broker.onStreamEnd(end.isOk());
}
}

View File

@@ -0,0 +1,419 @@
package org.openautonomousconnection.infonamelib;
import dev.unlegitdqrk.unlegitlibrary.network.system.utils.TransportProtocol;
import org.openautonomousconnection.protocol.packets.v1_0_0.beta.INSQueryPacket;
import org.openautonomousconnection.protocol.packets.v1_0_0.beta.web.WebRequestPacket;
import org.openautonomousconnection.protocol.side.client.ProtocolClient;
import org.openautonomousconnection.protocol.versions.v1_0_0.beta.INSRecordType;
import org.openautonomousconnection.protocol.versions.v1_0_0.beta.WebRequestMethod;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.net.URL;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
/**
* Central broker that translates {@code web://} URLs into OAC protocol traffic.
*
* <p>Protocol limitation: no correlation id -> single-flight (one in-flight request at a time).</p>
*/
public final class OacWebRequestBroker {
private static final OacWebRequestBroker INSTANCE = new 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;
private volatile ResponseState responseState;
private OacWebRequestBroker() {
}
/**
* Returns the singleton broker.
*
* @return broker
*/
public static OacWebRequestBroker get() {
return INSTANCE;
}
private static String safeContentType(String ct) {
return (ct == null || ct.isBlank()) ? "application/octet-stream" : ct;
}
private static Map<String, String> safeHeaders(Map<String, String> 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.
*
* @param client protocol client
*/
public void attachClient(ProtocolClient client) {
this.client = Objects.requireNonNull(client, "client");
}
/**
* Fetches a URL via OAC protocol (used by {@link java.net.URLConnection}).
*
* @param url web:// URL
* @return response
*/
public OacWebResponse fetch(URL url) {
Objects.requireNonNull(url, "url");
ProtocolClient c = this.client;
if (c == null) {
throw new IllegalStateException("ProtocolClient not attached. Call OacWebUrlInstaller.installOnce(..., client) first.");
}
Response r = openAndAwait(c, url);
byte[] body = (r.body() == null) ? new byte[0] : r.body();
long len = body.length;
return new OacWebResponse(
r.statusCode(),
r.contentType(),
OacWebResponse.safeHeaders(r.headers()),
new ByteArrayInputStream(body),
len
);
}
/**
* Opens a resource and blocks until the current single-flight response completes.
*
* @param client protocol client
* @param url web:// URL
* @return response snapshot
*/
public Response openAndAwait(ProtocolClient client, URL url) {
Objects.requireNonNull(client, "client");
Objects.requireNonNull(url, "url");
open(client, url);
return awaitResponse(RESPONSE_TIMEOUT_SECONDS, TimeUnit.SECONDS);
}
/**
* Sends required packets for a {@code web://} URL.
*
* @param client protocol client
* @param url web:// URL
*/
public synchronized void open(ProtocolClient client, URL url) {
Objects.requireNonNull(client, "client");
Objects.requireNonNull(url, "url");
if (!"web".equalsIgnoreCase(url.getProtocol())) {
throw new IllegalArgumentException("Unsupported protocol: " + url.getProtocol());
}
String infoName = url.getHost();
if (infoName == null || infoName.isBlank()) {
throw new IllegalArgumentException("Missing InfoName in URL: " + url);
}
String path = normalizePath(url.getPath());
beginNewResponse();
if (!infoName.equals(currentInfoName)) {
resolveAndConnect(client, infoName);
currentInfoName = infoName;
} else {
awaitConnectionIfPending();
}
sendWebRequest(client, path);
}
/**
* Called once the ServerConnection is established (from listener).
*/
public void notifyServerConnected() {
CountDownLatch latch = connectionLatch;
if (latch != null) {
latch.countDown();
}
}
/**
* Forces re-resolution on next request.
*/
public synchronized void invalidateCurrentInfoName() {
currentInfoName = null;
}
/**
* Receives non-streamed WebResponsePacket.
*/
public void onWebResponse(int statusCode, String contentType, Map<String, String> headers, byte[] body) {
ResponseState st = responseState;
if (st == null) return;
synchronized (responseLock) {
if (st.completed) return;
st.statusCode = statusCode;
st.contentType = safeContentType(contentType);
st.headers = safeHeaders(headers);
byte[] b = (body == null) ? new byte[0] : body;
st.body.write(b, 0, b.length);
st.completed = true;
st.success = true;
st.done.countDown();
}
}
/**
* Receives stream start.
*/
public void onStreamStart(int statusCode, String contentType, Map<String, String> headers, long totalLength) {
ResponseState st = responseState;
if (st == null) return;
synchronized (responseLock) {
if (st.completed) return;
st.statusCode = statusCode;
st.contentType = safeContentType(contentType);
st.headers = safeHeaders(headers);
st.totalLength = Math.max(0, totalLength);
st.streamStarted = true;
}
}
// ============================
// Internal connect + request
// ============================
/**
* Receives a stream chunk (may arrive out-of-order).
*/
public void onStreamChunk(int seq, byte[] data) {
ResponseState st = responseState;
if (st == null) return;
if (data == null || data.length == 0) return;
synchronized (responseLock) {
if (st.completed) return;
if (!st.streamStarted) {
failLocked(st, "Stream chunk received before stream start");
return;
}
st.chunkBuffer.put(seq, Arrays.copyOf(data, data.length));
flushChunksLocked(st);
}
}
/**
* Receives stream end.
*/
public void onStreamEnd(boolean ok) {
ResponseState st = responseState;
if (st == null) return;
synchronized (responseLock) {
if (st.completed) return;
if (!st.streamStarted) {
st.streamStarted = true;
}
flushChunksLocked(st);
st.completed = true;
st.success = ok;
if (!ok) {
st.errorMessage = "Streaming failed";
}
st.done.countDown();
}
}
/**
* Waits for the current response.
*/
public Response awaitResponse(long timeout, TimeUnit unit) {
Objects.requireNonNull(unit, "unit");
ResponseState st = responseState;
if (st == null) {
throw new IllegalStateException("No in-flight request");
}
try {
boolean ok = st.done.await(timeout, unit);
if (!ok) {
throw new IllegalStateException("Timeout while waiting for Web response");
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
throw new IllegalStateException("Interrupted while waiting for Web response", e);
}
synchronized (responseLock) {
if (!st.success) {
throw new IllegalStateException(st.errorMessage == null ? "Request failed" : st.errorMessage);
}
return new Response(
st.statusCode,
st.contentType,
st.headers,
st.body.toByteArray()
);
}
}
// ============================
// Response helpers
// ============================
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(
"Invalid INS address format: " + infoName + " (expected name.tln or sub.name.tln)"
);
}
String tln = parts[parts.length - 1];
String name = parts[parts.length - 2];
String sub = (parts.length == 3) ? parts[0] : null;
connectionLatch = new CountDownLatch(1);
INSQueryPacket query = new INSQueryPacket(
tln,
name,
sub,
INSRecordType.A,
client.getClientINSConnection().getUniqueID()
);
try {
client.getClientINSConnection().sendPacket(query, TransportProtocol.TCP);
} catch (Exception e) {
throw new IllegalStateException("Failed to send INSQueryPacket for " + infoName, e);
}
awaitConnectionIfPending();
}
private void awaitConnectionIfPending() {
CountDownLatch latch = connectionLatch;
if (latch == null || client.getClientServerConnection() == null || client.getClientINSConnection() == null)
return;
try {
if (!latch.await(CONNECT_TIMEOUT_SECONDS, TimeUnit.SECONDS)) {
throw new IllegalStateException("Timeout while waiting for ServerConnection");
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
throw new IllegalStateException("Interrupted while waiting for ServerConnection", e);
}
}
private void sendWebRequest(ProtocolClient client, String path) {
Objects.requireNonNull(client, "client");
Objects.requireNonNull(path, "path");
if (client.getClientServerConnection() == null || !client.getClientServerConnection().isConnected()) {
awaitConnectionIfPending();
}
if (client.getClientServerConnection() == null || !client.getClientServerConnection().isConnected()) {
throw new IllegalStateException("ServerConnection is not connected after waiting");
}
WebRequestPacket packet = new WebRequestPacket(
path,
WebRequestMethod.GET,
Map.of(),
null
);
try {
client.getClientServerConnection().sendPacket(packet, TransportProtocol.TCP);
} catch (Exception e) {
throw new IllegalStateException("Failed to send WebRequestPacket for path " + path, e);
}
}
private void beginNewResponse() {
synchronized (responseLock) {
responseState = new ResponseState();
}
}
private void flushChunksLocked(ResponseState st) {
for (; ; ) {
byte[] next = st.chunkBuffer.remove(st.nextExpectedSeq);
if (next == null) break;
st.body.write(next, 0, next.length);
st.nextExpectedSeq++;
}
}
private void failLocked(ResponseState st, String message) {
st.completed = true;
st.success = false;
st.errorMessage = message;
st.done.countDown();
}
/**
* Immutable response snapshot.
*
* @param statusCode status code
* @param contentType content type
* @param headers headers
* @param body body bytes
*/
public record Response(int statusCode, String contentType, Map<String, String> headers, byte[] body) {
}
private static final class ResponseState {
private final CountDownLatch done = new CountDownLatch(1);
private final ByteArrayOutputStream body = new ByteArrayOutputStream(64 * 1024);
private final Map<Integer, byte[]> chunkBuffer = new HashMap<>();
private int statusCode = 0;
private String contentType = "application/octet-stream";
private Map<String, String> headers = Map.of();
private boolean streamStarted;
private long totalLength;
private int nextExpectedSeq = 0;
private boolean completed;
private boolean success;
private String errorMessage;
}
}

View File

@@ -0,0 +1,32 @@
package org.openautonomousconnection.infonamelib;
import java.io.InputStream;
import java.util.Collections;
import java.util.Map;
import java.util.Objects;
/**
* Represents a resolved web response for the JavaFX WebView.
*
* @param statusCode HTTP-like status code
* @param contentType response content-type (as sent by server)
* @param headers response headers
* @param bodyStream body stream (may be streaming)
* @param contentLength content length if known, else -1
*/
public record OacWebResponse(
int statusCode,
String contentType,
Map<String, String> headers,
InputStream bodyStream,
long contentLength
) {
public OacWebResponse {
Objects.requireNonNull(headers, "headers");
Objects.requireNonNull(bodyStream, "bodyStream");
}
public static Map<String, String> safeHeaders(Map<String, String> h) {
return (h == null) ? Collections.emptyMap() : Collections.unmodifiableMap(h);
}
}

View File

@@ -0,0 +1,126 @@
package org.openautonomousconnection.infonamelib;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.net.URLConnection;
import java.util.*;
/**
* URLConnection implementation that maps "web://" URLs to OAC WebRequestPacket/WebResponsePacket.
*
* <p>Important: This connection enforces global serialization of requests because the protocol has no request IDs.</p>
*/
public final class OacWebURLConnection extends URLConnection {
private static final int MAX_REDIRECTS = 8;
private final OacWebRequestBroker broker;
private boolean connected;
private OacWebResponse response;
public OacWebURLConnection(URL url, OacWebRequestBroker broker) {
super(url);
this.broker = Objects.requireNonNull(broker, "broker");
}
private static String headerValue(Map<String, String> headers, String nameLower) {
if (headers == null || headers.isEmpty()) return null;
for (Map.Entry<String, String> e : headers.entrySet()) {
if (e.getKey() == null) continue;
if (e.getKey().trim().toLowerCase(Locale.ROOT).equals(nameLower)) {
return e.getValue();
}
}
return null;
}
@Override
public void connect() throws IOException {
if (connected) return;
URL cur = this.url;
OacWebResponse resp = null;
for (int i = 0; i <= MAX_REDIRECTS; i++) {
resp = broker.fetch(cur);
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;
}
try {
cur = new URL(cur, loc);
continue;
} catch (Exception ex) {
break;
}
}
// Non-redirect or redirect that cannot be followed -> stop here.
break;
}
this.response = resp;
this.connected = true;
}
@Override
public InputStream getInputStream() throws IOException {
connect();
return response.bodyStream();
}
@Override
public String getContentType() {
try {
connect();
} catch (IOException e) {
return "application/octet-stream";
}
String ct = response.contentType();
return (ct == null || ct.isBlank()) ? "application/octet-stream" : ct;
}
@Override
public int getContentLength() {
try {
connect();
} catch (IOException e) {
return -1;
}
long len = response.contentLength();
return (len <= 0 || len > Integer.MAX_VALUE) ? -1 : (int) len;
}
@Override
public long getContentLengthLong() {
try {
connect();
} catch (IOException e) {
return -1L;
}
return response.contentLength();
}
@Override
public Map<String, List<String>> getHeaderFields() {
try {
connect();
} catch (IOException e) {
return Map.of();
}
Map<String, List<String>> out = new LinkedHashMap<>();
for (Map.Entry<String, String> e : response.headers().entrySet()) {
String k = e.getKey();
String v = e.getValue();
if (k == null) continue;
out.put(k, v == null ? List.of() : List.of(v));
}
return out;
}
}

View File

@@ -0,0 +1,52 @@
package org.openautonomousconnection.infonamelib;
import dev.unlegitdqrk.unlegitlibrary.event.EventManager;
import org.openautonomousconnection.protocol.side.client.ProtocolClient;
import java.util.Objects;
import java.util.concurrent.atomic.AtomicBoolean;
/**
* Installs the "web://" protocol handler using the standard Java mechanism:
* {@code java.protocol.handler.pkgs}.
*
* <p>This avoids {@link java.net.URL#setURLStreamHandlerFactory} which can only be set once per JVM.</p>
*/
public final class OacWebUrlInstaller {
private static final AtomicBoolean INSTALLED = new AtomicBoolean(false);
private OacWebUrlInstaller() {
}
/**
* Installs:
* <ul>
* <li>protocol handler package prefix</li>
* <li>packet listener forwarding WebResponse/WebStream + INSResponse into the broker</li>
* </ul>
*
* <p>Must be called before any {@code web://} URL is resolved/loaded.</p>
*
* @param eventManager global event manager
* @param client protocol client (required for connecting ServerConnection after INS resolution)
*/
public static void installOnce(EventManager eventManager, ProtocolClient client) {
Objects.requireNonNull(eventManager, "eventManager");
Objects.requireNonNull(client, "client");
if (!INSTALLED.compareAndSet(false, true)) {
return;
}
// Make client available for broker.fetch(...)
OacWebRequestBroker.get().attachClient(client);
// Register packet listener (INSResponse + WebResponse + WebStream*)
eventManager.registerListener(new OacWebPacketListener(OacWebRequestBroker.get(), client));
// Register protocol handler package prefix:
// JVM will load: "org.openautonomousconnection.webclient.recode.url.web.Handler"
ProtocolHandlerPackages.installPackage("org.openautonomousconnection.webclient.recode.url");
}
}

View File

@@ -0,0 +1,28 @@
package org.openautonomousconnection.infonamelib;
import java.net.URL;
import java.net.URLConnection;
import java.net.URLStreamHandler;
import java.util.Objects;
/**
* URLStreamHandler for the custom OAC scheme "web://".
*/
public final class OacWebUrlStreamHandler extends URLStreamHandler {
private final OacWebRequestBroker broker;
/**
* Creates a handler.
*
* @param broker request broker
*/
public OacWebUrlStreamHandler(OacWebRequestBroker broker) {
this.broker = Objects.requireNonNull(broker, "broker");
}
@Override
protected URLConnection openConnection(URL u) {
return new OacWebURLConnection(u, broker);
}
}

View File

@@ -0,0 +1,42 @@
package org.openautonomousconnection.infonamelib;
import java.util.LinkedHashSet;
import java.util.Objects;
import java.util.Set;
/**
* Utility to safely append packages to "java.protocol.handler.pkgs".
*/
public final class ProtocolHandlerPackages {
private static final String KEY = "java.protocol.handler.pkgs";
private ProtocolHandlerPackages() {
}
/**
* Appends a package prefix to the protocol handler search path.
*
* @param pkg package prefix (e.g. "com.example.protocols")
*/
public static void installPackage(String pkg) {
Objects.requireNonNull(pkg, "pkg");
String p = pkg.trim();
if (p.isEmpty()) return;
String existing = System.getProperty(KEY, "");
Set<String> parts = new LinkedHashSet<>();
if (!existing.isBlank()) {
for (String s : existing.split("\\|")) {
String t = s.trim();
if (!t.isEmpty()) parts.add(t);
}
}
parts.add(p);
String merged = String.join("|", parts);
System.setProperty(KEY, merged);
}
}

View File

@@ -1,26 +0,0 @@
/* Author: Maple
* Jan. 18 2026
* */
package org.openautonomousconnection.infonamelib.protocols.ftp;
import java.io.IOException;
import java.net.URL;
import java.net.URLConnection;
public class FTPInfoNameURLConnection extends URLConnection {
/**
* Constructs a URL connection to the specified URL. A connection to
* the object referenced by the URL is not created.
*
* @param url the specified URL.
*/
protected FTPInfoNameURLConnection(URL url) {
super(url);
}
@Override
public void connect() throws IOException {
}
}

View File

@@ -1,17 +0,0 @@
/* Author: Maple
* Jan. 18 2026
* */
package org.openautonomousconnection.infonamelib.protocols.ftp;
import java.io.IOException;
import java.net.URL;
import java.net.URLConnection;
import java.net.URLStreamHandler;
public class FTPInfoNameURLStreamHandler extends URLStreamHandler {
@Override
protected URLConnection openConnection(URL url) throws IOException {
return new FTPInfoNameURLConnection(url);
}
}

View File

@@ -1,26 +0,0 @@
/* Author: Maple
* Jan. 18 2026
* */
package org.openautonomousconnection.infonamelib.protocols.web;
import java.io.IOException;
import java.net.URL;
import java.net.URLConnection;
public class WebInfoNameURLConnection extends URLConnection {
/**
* Constructs a URL connection to the specified URL. A connection to
* the object referenced by the URL is not created.
*
* @param url the specified URL.
*/
protected WebInfoNameURLConnection(URL url) {
super(url);
}
@Override
public void connect() throws IOException {
}
}

View File

@@ -1,17 +0,0 @@
/* Author: Maple
* Jan. 18 2026
* */
package org.openautonomousconnection.infonamelib.protocols.web;
import java.io.IOException;
import java.net.URL;
import java.net.URLConnection;
import java.net.URLStreamHandler;
public class WebInfoNameURLStreamHandler extends URLStreamHandler {
@Override
protected URLConnection openConnection(URL url) throws IOException {
return new WebInfoNameURLConnection(url);
}
}

View File

@@ -0,0 +1,22 @@
package org.openautonomousconnection.infonamelib.web;
import org.openautonomousconnection.infonamelib.OacWebRequestBroker;
import org.openautonomousconnection.infonamelib.OacWebURLConnection;
import java.io.IOException;
import java.net.URL;
import java.net.URLConnection;
import java.net.URLStreamHandler;
/**
* URLStreamHandler for the "web" protocol.
*
* <p>Loaded via the "java.protocol.handler.pkgs" mechanism.</p>
*/
public final class Handler extends URLStreamHandler {
@Override
protected URLConnection openConnection(URL u) throws IOException {
return new OacWebURLConnection(u, OacWebRequestBroker.get());
}
}

View File

@@ -0,0 +1 @@
Please read the license here: https://open-autonomous-connection.org/license.html