Web protocol ready
This commit is contained in:
18
README.md
18
README.md
@@ -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
11
pom.xml
@@ -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>
|
||||||
|
|||||||
@@ -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;
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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());
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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 {
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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 {
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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());
|
||||||
|
}
|
||||||
|
}
|
||||||
1
src/main/resources/license/protocol/oapl
Normal file
1
src/main/resources/license/protocol/oapl
Normal file
@@ -0,0 +1 @@
|
|||||||
|
Please read the license here: https://open-autonomous-connection.org/license.html
|
||||||
Reference in New Issue
Block a user