Introduce 1.0.1-BETA packet model with 1.0.0-BETA backwards compatibility
This commit is contained in:
@@ -12,14 +12,19 @@ import java.util.List;
|
||||
*/
|
||||
public enum ProtocolVersion implements Serializable {
|
||||
/**
|
||||
* Support for old OAC-Project => <a href="https://repo.open-autonomous-connection.org/Open-Autonomous-Connection/">*_old</a>
|
||||
* For classic OAC-Project => <a href="https://repo.open-autonomous-connection.org/Open-Autonomous-Connection/">"classic"-branch</a>
|
||||
*/
|
||||
@Deprecated(forRemoval = false, since = "1.0.1-BETA.0.1")
|
||||
PV_1_0_0_CLASSIC("1.0.0", ProtocolType.CLASSIC, ProtocolSide.WEB_INS, List.of(Protocol.HTTP)),
|
||||
|
||||
/**
|
||||
* First Beta Version of OAC-Protocol
|
||||
* Version {@link ProtocolVersion#PV_1_0_0_BETA} has many bugs and does not work as expected (occurred by incompleted packets).
|
||||
* Use {@link ProtocolVersion#PV_1_0_1_BETA} or newer.
|
||||
*/
|
||||
PV_1_0_0_BETA("1.0.0", ProtocolType.BETA, ProtocolSide.ALL, List.of(Protocol.OAC), PV_1_0_0_CLASSIC),
|
||||
@Deprecated(forRemoval = false, since = "1.0.1-BETA.0.1")
|
||||
PV_1_0_0_BETA("1.0.0", ProtocolType.BETA, ProtocolSide.ALL, List.of(Protocol.OAC)),
|
||||
|
||||
PV_1_0_1_BETA("1.0.1", ProtocolType.BETA, ProtocolSide.ALL, List.of(Protocol.OAC), PV_1_0_0_BETA),
|
||||
;
|
||||
|
||||
/**
|
||||
|
||||
@@ -149,9 +149,7 @@ public final class INSRecordTools {
|
||||
return record.ttl >= 0;
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Validation helpers
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Validates a collection of records by applying {@link #isValidRecord(INSRecord)}
|
||||
|
||||
@@ -0,0 +1,133 @@
|
||||
package org.openautonomousconnection.protocol.versions.v1_0_0.beta;
|
||||
|
||||
import dev.unlegitdqrk.unlegitlibrary.file.FileUtils;
|
||||
import dev.unlegitdqrk.unlegitlibrary.string.RandomString;
|
||||
import org.openautonomousconnection.protocol.annotations.ProtocolInfo;
|
||||
import org.openautonomousconnection.protocol.packets.v1_0_0.beta.web.WebRequestPacket;
|
||||
import org.openautonomousconnection.protocol.packets.v1_0_0.beta.web.WebResponsePacket;
|
||||
import org.openautonomousconnection.protocol.side.server.CustomConnectedClient;
|
||||
import org.openautonomousconnection.protocol.side.server.ProtocolCustomServer;
|
||||
import org.openautonomousconnection.protocol.side.web.managers.AuthManager;
|
||||
import org.openautonomousconnection.protocol.side.web.managers.RuleManager;
|
||||
import org.openautonomousconnection.protocol.versions.ProtocolVersion;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.Random;
|
||||
|
||||
/**
|
||||
* Represents the web server for the protocol.
|
||||
*/
|
||||
@Deprecated(forRemoval = false, since = "1.0.1-BETA.0.1")
|
||||
@ProtocolInfo(protocolSide = ProtocolVersion.ProtocolSide.WEB)
|
||||
public abstract class ProtocolWebServer_1_0_0_B extends ProtocolCustomServer {
|
||||
/**
|
||||
* Folder for web content.
|
||||
*/
|
||||
private final File contentFolder;
|
||||
/**
|
||||
* Folder for error pages.
|
||||
*/
|
||||
private final File errorsFolder;
|
||||
/**
|
||||
* A unique secret for session management.
|
||||
*/
|
||||
private final String uniqueSessionString;
|
||||
/**
|
||||
* The expiration time of a Session in minutes
|
||||
*/
|
||||
private final int sessionExpire;
|
||||
/**
|
||||
* The max upload size in MB
|
||||
*/
|
||||
private final int maxUploadSize;
|
||||
|
||||
/**
|
||||
* Initializes the web server with the given configuration, authentication, and rules files.
|
||||
*
|
||||
* @param authFile The authentication file.
|
||||
* @param rulesFile The rules file.
|
||||
* @param sessionExpire The expiration time of a Session in minutes
|
||||
* @param uploadSize The max upload size in MB
|
||||
* @throws Exception If an error occurs during initialization.
|
||||
*/
|
||||
public ProtocolWebServer_1_0_0_B(File authFile, File rulesFile, int sessionExpire, int uploadSize) throws Exception {
|
||||
super("server", "server");
|
||||
|
||||
this.sessionExpire = sessionExpire;
|
||||
this.maxUploadSize = uploadSize;
|
||||
|
||||
// Set up content and error folders
|
||||
contentFolder = new File("content");
|
||||
errorsFolder = new File("errors");
|
||||
|
||||
// Create folders if they don't exist
|
||||
if (!contentFolder.exists()) contentFolder.mkdir();
|
||||
if (!errorsFolder.exists()) errorsFolder.mkdir();
|
||||
|
||||
// Create auth and rules files with default content if they don't exist
|
||||
if (!authFile.exists()) {
|
||||
authFile.createNewFile();
|
||||
FileUtils.writeFile(authFile, """
|
||||
admin:5e884898da28047151d0e56f8dc6292773603d0d6aabbddab8f91d8e5f99f6c7
|
||||
user:e99a18c428cb38d5f260853678922e03abd8335f
|
||||
""");
|
||||
}
|
||||
|
||||
// Create default rules file if it doesn't exist
|
||||
if (!rulesFile.exists()) {
|
||||
rulesFile.createNewFile();
|
||||
FileUtils.writeFile(rulesFile, """
|
||||
{
|
||||
"allow": [
|
||||
"index.html",
|
||||
"css/*",
|
||||
"private/info.php"
|
||||
],
|
||||
"deny": [
|
||||
"private/*"
|
||||
],
|
||||
"auth": [
|
||||
"private/*",
|
||||
"admin/*"
|
||||
]
|
||||
}
|
||||
""");
|
||||
}
|
||||
|
||||
// Load authentication and rules
|
||||
uniqueSessionString = AuthManager.sha256(new RandomString(new Random(System.currentTimeMillis()).nextInt(10, 20)).nextString());
|
||||
|
||||
AuthManager.loadAuthFile(authFile);
|
||||
RuleManager.loadRules(rulesFile);
|
||||
}
|
||||
|
||||
public final File getContentFolder() {
|
||||
return contentFolder;
|
||||
}
|
||||
|
||||
public final File getErrorsFolder() {
|
||||
return errorsFolder;
|
||||
}
|
||||
|
||||
public final String getUniqueSessionString() {
|
||||
return uniqueSessionString;
|
||||
}
|
||||
|
||||
public final int getSessionExpire() {
|
||||
return sessionExpire;
|
||||
}
|
||||
|
||||
public final int getMaxUploadSize() {
|
||||
return maxUploadSize;
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when the server receives a WebRequestPacket from the client.
|
||||
*
|
||||
* @param client The connected web client (pipeline + web socket).
|
||||
* @param request The full decoded request packet.
|
||||
* @return The response packet that should be sent back to the client.
|
||||
*/
|
||||
@Deprecated(forRemoval = false, since = "1.0.1-BETA.0.1")
|
||||
public abstract WebResponsePacket onWebRequest(CustomConnectedClient client, WebRequestPacket request);
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
package org.openautonomousconnection.protocol.versions.v1_0_0.beta;
|
||||
|
||||
public enum WebRequestMethod {
|
||||
GET, POST
|
||||
GET, POST, EXECUTE, SCRIPT
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@ import java.io.Serializable;
|
||||
/**
|
||||
* Enum representing the protocol versions for the Classic protocol.
|
||||
*/
|
||||
@Deprecated(forRemoval = false, since = "1.0.0-BETA.3")
|
||||
@Deprecated(forRemoval = true, since = "1.0.1-BETA.0.1")
|
||||
public enum Classic_ProtocolVersion implements Serializable {
|
||||
PV_1_0_0("1.0.0");
|
||||
public final String version;
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
package org.openautonomousconnection.protocol.versions.v1_0_1.beta;
|
||||
|
||||
/**
|
||||
* Cache behavior for navigation/resource requests.
|
||||
*/
|
||||
public enum WebCacheMode {
|
||||
DEFAULT,
|
||||
BYPASS,
|
||||
ONLY_CACHE
|
||||
}
|
||||
@@ -0,0 +1,250 @@
|
||||
package org.openautonomousconnection.protocol.versions.v1_0_1.beta;
|
||||
|
||||
import java.security.SecureRandom;
|
||||
import java.util.Objects;
|
||||
import java.util.concurrent.atomic.AtomicLong;
|
||||
|
||||
/**
|
||||
* Factory for creating {@link WebPacketHeader} instances with stable tab/page identifiers and
|
||||
* monotonic request identifiers.
|
||||
*
|
||||
* <p>This class is intended to be used by the WebClient/WebServer integration layer to ensure
|
||||
* every packet carries a valid header and consistent correlation metadata.</p>
|
||||
*
|
||||
* <p>Thread-safety:
|
||||
* <ul>
|
||||
* <li>Request id generation is thread-safe.</li>
|
||||
* <li>Tab/page allocation methods are thread-safe.</li>
|
||||
* <li>{@link WebTabContext} is thread-safe for reads; writes are synchronized.</li>
|
||||
* </ul>
|
||||
* </p>
|
||||
*/
|
||||
public final class WebHeaderFactory {
|
||||
|
||||
private static final long MIN_ID = 1L;
|
||||
|
||||
private final AtomicLong requestIdSeq;
|
||||
private final AtomicLong tabIdSeq;
|
||||
private final AtomicLong pageIdSeq;
|
||||
|
||||
/**
|
||||
* Creates a factory with randomized id bases to reduce collisions across process restarts.
|
||||
*/
|
||||
public WebHeaderFactory() {
|
||||
SecureRandom rnd = new SecureRandom();
|
||||
this.requestIdSeq = new AtomicLong(Math.max(MIN_ID, rnd.nextLong() & Long.MAX_VALUE));
|
||||
this.tabIdSeq = new AtomicLong(Math.max(MIN_ID, rnd.nextLong() & Long.MAX_VALUE));
|
||||
this.pageIdSeq = new AtomicLong(Math.max(MIN_ID, rnd.nextLong() & Long.MAX_VALUE));
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new tab context with a fresh tab id and an initial page id.
|
||||
*
|
||||
* @return new tab context
|
||||
*/
|
||||
public WebTabContext createTab() {
|
||||
long tabId = nextTabId();
|
||||
long pageId = nextPageId();
|
||||
return new WebTabContext(tabId, pageId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts a new navigation by allocating a new page id for the given tab context.
|
||||
*
|
||||
* <p>Call this when navigation is initiated (typed URL, link click, reload, back/forward).</p>
|
||||
*
|
||||
* @param tab tab context
|
||||
* @return the new page id
|
||||
*/
|
||||
public long beginNavigation(WebTabContext tab) {
|
||||
Objects.requireNonNull(tab, "tab");
|
||||
long newPageId = nextPageId();
|
||||
tab.setPageId(newPageId);
|
||||
return newPageId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a header for a navigation request.
|
||||
*
|
||||
* @param tab tab context
|
||||
* @param noStore if true, sets {@link WebPacketFlags#NO_STORE}
|
||||
* @return header
|
||||
*/
|
||||
public WebPacketHeader navigation(WebTabContext tab, boolean noStore) {
|
||||
Objects.requireNonNull(tab, "tab");
|
||||
int flags = WebPacketFlags.NAVIGATION;
|
||||
if (noStore) flags |= WebPacketFlags.NO_STORE;
|
||||
return header(nextRequestId(), tab.getTabId(), tab.getPageId(), 0L, flags);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a header for a resource request.
|
||||
*
|
||||
* @param tab tab context
|
||||
* @param frameId frame id (0 = main)
|
||||
* @param noStore if true, sets {@link WebPacketFlags#NO_STORE}
|
||||
* @return header
|
||||
*/
|
||||
public WebPacketHeader resource(WebTabContext tab, long frameId, boolean noStore) {
|
||||
Objects.requireNonNull(tab, "tab");
|
||||
int flags = WebPacketFlags.RESOURCE;
|
||||
if (noStore) flags |= WebPacketFlags.NO_STORE;
|
||||
return header(nextRequestId(), tab.getTabId(), tab.getPageId(), frameId, flags);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a header for a resource response correlated to a request id.
|
||||
*
|
||||
* @param requestId request id to correlate
|
||||
* @param tabId tab id
|
||||
* @param pageId page id
|
||||
* @param frameId frame id
|
||||
* @return header
|
||||
*/
|
||||
public WebPacketHeader resourceResponse(long requestId, long tabId, long pageId, long frameId) {
|
||||
return header(requestId, tabId, pageId, frameId, WebPacketFlags.RESOURCE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a header for a streamed payload associated with a request id.
|
||||
*
|
||||
* @param requestId correlated request id
|
||||
* @param tabId tab id
|
||||
* @param pageId page id
|
||||
* @param frameId frame id
|
||||
* @return header
|
||||
*/
|
||||
public WebPacketHeader stream(long requestId, long tabId, long pageId, long frameId) {
|
||||
return header(requestId, tabId, pageId, frameId, WebPacketFlags.STREAM);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an event header (server->client or client->server).
|
||||
*
|
||||
* @param tabId tab id
|
||||
* @param pageId page id
|
||||
* @param frameId frame id
|
||||
* @return header
|
||||
*/
|
||||
public WebPacketHeader event(long tabId, long pageId, long frameId) {
|
||||
return header(nextRequestId(), tabId, pageId, frameId, WebPacketFlags.EVENT);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an event header correlated to an existing request id.
|
||||
*
|
||||
* @param requestId request id
|
||||
* @param tabId tab id
|
||||
* @param pageId page id
|
||||
* @param frameId frame id
|
||||
* @return header
|
||||
*/
|
||||
public WebPacketHeader correlatedEvent(long requestId, long tabId, long pageId, long frameId) {
|
||||
return header(requestId, tabId, pageId, frameId, WebPacketFlags.EVENT);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a header for a document snapshot event.
|
||||
*
|
||||
* @param tab tab context
|
||||
* @param frameId frame id
|
||||
* @return header
|
||||
*/
|
||||
public WebPacketHeader documentSnapshotEvent(WebTabContext tab, long frameId) {
|
||||
Objects.requireNonNull(tab, "tab");
|
||||
return header(nextRequestId(), tab.getTabId(), tab.getPageId(), frameId, WebPacketFlags.EVENT);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a header for a document apply request.
|
||||
*
|
||||
* @param tab tab context
|
||||
* @param frameId frame id
|
||||
* @return header
|
||||
*/
|
||||
public WebPacketHeader documentApply(WebTabContext tab, long frameId) {
|
||||
Objects.requireNonNull(tab, "tab");
|
||||
return header(nextRequestId(), tab.getTabId(), tab.getPageId(), frameId, WebPacketFlags.RESOURCE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether an incoming packet is stale for a given tab context.
|
||||
*
|
||||
* <p>Stale means: the packet refers to an older pageId than the current tab pageId.</p>
|
||||
*
|
||||
* @param tab tab context
|
||||
* @param incoming incoming header
|
||||
* @return true if stale, false otherwise
|
||||
*/
|
||||
public boolean isStale(WebTabContext tab, WebPacketHeader incoming) {
|
||||
Objects.requireNonNull(tab, "tab");
|
||||
Objects.requireNonNull(incoming, "incoming");
|
||||
return incoming.getTabId() == tab.getTabId() && incoming.getPageId() != tab.getPageId();
|
||||
}
|
||||
|
||||
private WebPacketHeader header(long requestId, long tabId, long pageId, long frameId, int flags) {
|
||||
long ts = System.currentTimeMillis();
|
||||
return new WebPacketHeader(requestId, tabId, pageId, frameId, flags, ts);
|
||||
}
|
||||
|
||||
private long nextRequestId() {
|
||||
long v = requestIdSeq.incrementAndGet();
|
||||
return (v < MIN_ID) ? MIN_ID : v;
|
||||
}
|
||||
|
||||
private long nextTabId() {
|
||||
long v = tabIdSeq.incrementAndGet();
|
||||
return (v < MIN_ID) ? MIN_ID : v;
|
||||
}
|
||||
|
||||
private long nextPageId() {
|
||||
long v = pageIdSeq.incrementAndGet();
|
||||
return (v < MIN_ID) ? MIN_ID : v;
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents per-tab state used for header creation and stale detection.
|
||||
*/
|
||||
public static final class WebTabContext {
|
||||
|
||||
private final long tabId;
|
||||
private volatile long pageId;
|
||||
|
||||
/**
|
||||
* Creates a tab context.
|
||||
*
|
||||
* @param tabId tab id
|
||||
* @param pageId initial page id
|
||||
*/
|
||||
public WebTabContext(long tabId, long pageId) {
|
||||
if (tabId < MIN_ID) throw new IllegalArgumentException("tabId must be >= 1");
|
||||
if (pageId < MIN_ID) throw new IllegalArgumentException("pageId must be >= 1");
|
||||
this.tabId = tabId;
|
||||
this.pageId = pageId;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return stable tab id
|
||||
*/
|
||||
public long getTabId() {
|
||||
return tabId;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return current page id
|
||||
*/
|
||||
public long getPageId() {
|
||||
return pageId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the current page id.
|
||||
*
|
||||
* @param pageId new page id
|
||||
*/
|
||||
public synchronized void setPageId(long pageId) {
|
||||
if (pageId < MIN_ID) throw new IllegalArgumentException("pageId must be >= 1");
|
||||
this.pageId = pageId;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
package org.openautonomousconnection.protocol.versions.v1_0_1.beta;
|
||||
|
||||
/**
|
||||
* Identifies the initiator of a resource request.
|
||||
*/
|
||||
public enum WebInitiatorType {
|
||||
DOCUMENT,
|
||||
SCRIPT,
|
||||
STYLE,
|
||||
IMAGE,
|
||||
MEDIA,
|
||||
FONT,
|
||||
XHR,
|
||||
FETCH,
|
||||
OTHER
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
package org.openautonomousconnection.protocol.versions.v1_0_1.beta;
|
||||
|
||||
/**
|
||||
* Bitset flags used in WebPacketHeader.
|
||||
*/
|
||||
public final class WebPacketFlags {
|
||||
|
||||
/**
|
||||
* Packet describes a top-level navigation action.
|
||||
*/
|
||||
public static final int NAVIGATION = 1 << 0;
|
||||
/**
|
||||
* Packet describes a resource request/response.
|
||||
*/
|
||||
public static final int RESOURCE = 1 << 1;
|
||||
/**
|
||||
* Packet is part of a streamed body transfer.
|
||||
*/
|
||||
public static final int STREAM = 1 << 2;
|
||||
/**
|
||||
* Packet is devtools-related.
|
||||
*/
|
||||
public static final int DEVTOOLS = 1 << 3;
|
||||
/**
|
||||
* Packet is an event (server->client / client->server).
|
||||
*/
|
||||
public static final int EVENT = 1 << 4;
|
||||
/**
|
||||
* Disable caching / do not store.
|
||||
*/
|
||||
public static final int NO_STORE = 1 << 5;
|
||||
|
||||
private WebPacketFlags() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if a flag is present.
|
||||
*
|
||||
* @param flags bitset
|
||||
* @param flag flag constant
|
||||
* @return true if present
|
||||
*/
|
||||
public static boolean has(int flags, int flag) {
|
||||
return (flags & flag) == flag;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
package org.openautonomousconnection.protocol.versions.v1_0_1.beta;
|
||||
|
||||
import lombok.Getter;
|
||||
|
||||
import java.io.DataInputStream;
|
||||
import java.io.DataOutputStream;
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* Shared header for all Web v1.0.1-BETA packets.
|
||||
*
|
||||
* <p>This header MUST be written/read first in every web packet.</p>
|
||||
*/
|
||||
@Getter
|
||||
public final class WebPacketHeader {
|
||||
|
||||
private long requestId;
|
||||
private long tabId;
|
||||
private long pageId;
|
||||
private long frameId;
|
||||
private int flags;
|
||||
private long timestampMs;
|
||||
|
||||
/**
|
||||
* Empty constructor for decoding.
|
||||
*/
|
||||
public WebPacketHeader() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new web header.
|
||||
*
|
||||
* @param requestId correlation id (request/response/stream/events)
|
||||
* @param tabId stable tab id
|
||||
* @param pageId navigation instance id
|
||||
* @param frameId frame id (0 = main frame)
|
||||
* @param flags bitset flags
|
||||
* @param timestampMs unix epoch millis (0 allowed)
|
||||
*/
|
||||
public WebPacketHeader(long requestId, long tabId, long pageId, long frameId, int flags, long timestampMs) {
|
||||
this.requestId = requestId;
|
||||
this.tabId = tabId;
|
||||
this.pageId = pageId;
|
||||
this.frameId = frameId;
|
||||
this.flags = flags;
|
||||
this.timestampMs = timestampMs;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads a header from the stream.
|
||||
*
|
||||
* @param in stream
|
||||
* @return decoded header
|
||||
* @throws IOException on IO error
|
||||
*/
|
||||
public static WebPacketHeader read(DataInputStream in) throws IOException {
|
||||
WebPacketHeader h = new WebPacketHeader();
|
||||
h.requestId = in.readLong();
|
||||
h.tabId = in.readLong();
|
||||
h.pageId = in.readLong();
|
||||
h.frameId = in.readLong();
|
||||
h.flags = in.readInt();
|
||||
h.timestampMs = in.readLong();
|
||||
return h;
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes the header to the stream.
|
||||
*
|
||||
* @param out stream
|
||||
* @throws IOException on IO error
|
||||
*/
|
||||
public void write(DataOutputStream out) throws IOException {
|
||||
out.writeLong(requestId);
|
||||
out.writeLong(tabId);
|
||||
out.writeLong(pageId);
|
||||
out.writeLong(frameId);
|
||||
out.writeInt(flags);
|
||||
out.writeLong(timestampMs);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
package org.openautonomousconnection.protocol.versions.v1_0_1.beta;
|
||||
|
||||
|
||||
/**
|
||||
* Describes how a navigation was triggered.
|
||||
*/
|
||||
public enum WebTransitionType {
|
||||
LINK,
|
||||
TYPED,
|
||||
RELOAD,
|
||||
BACK_FORWARD
|
||||
}
|
||||
@@ -0,0 +1,281 @@
|
||||
package org.openautonomousconnection.protocol.versions.v1_0_1.beta.compat;
|
||||
|
||||
import org.openautonomousconnection.protocol.packets.v1_0_0.beta.web.WebRequestPacket;
|
||||
import org.openautonomousconnection.protocol.packets.v1_0_0.beta.web.WebResponsePacket;
|
||||
import org.openautonomousconnection.protocol.packets.v1_0_0.beta.web.stream.WebStreamChunkPacket_v1_0_0_B;
|
||||
import org.openautonomousconnection.protocol.packets.v1_0_0.beta.web.stream.WebStreamEndPacket_v1_0_0_B;
|
||||
import org.openautonomousconnection.protocol.packets.v1_0_0.beta.web.stream.WebStreamStartPacket_v1_0_0_B;
|
||||
import org.openautonomousconnection.protocol.packets.v1_0_1.beta.web.impl.resource.WebResourceRequestPacket;
|
||||
import org.openautonomousconnection.protocol.packets.v1_0_1.beta.web.impl.resource.WebResourceResponsePacket;
|
||||
import org.openautonomousconnection.protocol.packets.v1_0_1.beta.web.impl.stream.WebStreamChunkPacket_v1_0_1_B;
|
||||
import org.openautonomousconnection.protocol.packets.v1_0_1.beta.web.impl.stream.WebStreamEndPacket_v1_0_1_B;
|
||||
import org.openautonomousconnection.protocol.packets.v1_0_1.beta.web.impl.stream.WebStreamStartPacket_v1_0_1_B;
|
||||
import org.openautonomousconnection.protocol.versions.v1_0_0.beta.WebRequestMethod;
|
||||
import org.openautonomousconnection.protocol.versions.v1_0_1.beta.WebCacheMode;
|
||||
import org.openautonomousconnection.protocol.versions.v1_0_1.beta.WebInitiatorType;
|
||||
import org.openautonomousconnection.protocol.versions.v1_0_1.beta.WebPacketFlags;
|
||||
import org.openautonomousconnection.protocol.versions.v1_0_1.beta.WebPacketHeader;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.concurrent.atomic.AtomicLong;
|
||||
|
||||
/**
|
||||
* Compatibility mapper between Web protocol v1.0.0
|
||||
* and Web protocol v1.0.1 (beta).
|
||||
*
|
||||
* <p>This class provides pure mapping logic and contains no networking code.</p>
|
||||
*
|
||||
* <p>Usage:
|
||||
* <ul>
|
||||
* <li>Server-side: map 1.0.0 {@link WebRequestPacket} to {@link WebResourceRequestPacket}</li>
|
||||
* <li>Server-side: map {@link WebResourceResponsePacket} back to {@link WebResponsePacket}</li>
|
||||
* <li>Client-side: map 1.0.0 responses/streams to v1.0.1 packets so v1.0.1 hooks can be reused</li>
|
||||
* </ul>
|
||||
* </p>
|
||||
*/
|
||||
public final class WebCompatMapper {
|
||||
|
||||
/**
|
||||
* Synthetic request id generator for 1.0.0 requests.
|
||||
*/
|
||||
private static final AtomicLong COMPAT_REQUEST_ID = new AtomicLong(1L);
|
||||
|
||||
private WebCompatMapper() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a monotonic synthetic request id.
|
||||
*
|
||||
* @return next request id
|
||||
*/
|
||||
public static long nextCompatRequestId() {
|
||||
return COMPAT_REQUEST_ID.getAndIncrement();
|
||||
}
|
||||
|
||||
/**
|
||||
* Maps a v1.0.0 {@link WebRequestPacket} to a v1.0.1 {@link WebResourceRequestPacket}.
|
||||
*
|
||||
* <p>Because v1.0.0 has no tab/page/frame/request correlation model,
|
||||
* synthetic identifiers must be provided.</p>
|
||||
*
|
||||
* @param requestId synthetic request id
|
||||
* @param tabId synthetic tab id
|
||||
* @param pageId synthetic page id
|
||||
* @param requestPacket 1.0.0 request
|
||||
* @return mapped v1.0.1 resource request
|
||||
*/
|
||||
public static WebResourceRequestPacket toResourceRequest(
|
||||
long requestId,
|
||||
long tabId,
|
||||
long pageId,
|
||||
WebRequestPacket requestPacket
|
||||
) {
|
||||
Objects.requireNonNull(requestPacket, "requestPacket");
|
||||
|
||||
String path = requestPacket.getPath();
|
||||
String method = mapMethod(requestPacket.getMethod());
|
||||
Map<String, String> headers = requestPacket.getHeaders() != null ? requestPacket.getHeaders() : Collections.emptyMap();
|
||||
byte[] body = requestPacket.getBody() != null ? requestPacket.getBody() : new byte[0];
|
||||
|
||||
WebPacketHeader header = new WebPacketHeader(
|
||||
requestId,
|
||||
tabId,
|
||||
pageId,
|
||||
0L,
|
||||
WebPacketFlags.RESOURCE,
|
||||
System.currentTimeMillis()
|
||||
);
|
||||
|
||||
return new WebResourceRequestPacket(
|
||||
header,
|
||||
path,
|
||||
method,
|
||||
headers,
|
||||
body,
|
||||
null,
|
||||
WebInitiatorType.OTHER,
|
||||
WebCacheMode.DEFAULT
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Maps a v1.0.1 {@link WebResourceResponsePacket} back to a 1.0.0 {@link WebResponsePacket}.
|
||||
*
|
||||
* <p>Correlation information is lost because v1.0.0 does not support it.</p>
|
||||
*
|
||||
* @param response v1.0.1 resource response
|
||||
* @return 1.0.0 response packet
|
||||
*/
|
||||
public static WebResponsePacket to100BResponse(WebResourceResponsePacket response) {
|
||||
Objects.requireNonNull(response, "response");
|
||||
|
||||
return new WebResponsePacket(
|
||||
response.getStatusCode(),
|
||||
response.getContentType(),
|
||||
response.getHeaders(),
|
||||
response.getBody()
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Maps v1.0.1 resource response to 1.0.0 response with overridden body.
|
||||
*
|
||||
* <p>Useful when buffering streamed responses for 1.0.0 clients.</p>
|
||||
*
|
||||
* @param response original v1.0.1 response
|
||||
* @param body buffered full body
|
||||
* @return 1.0.0 response
|
||||
*/
|
||||
public static WebResponsePacket to100BResponse(WebResourceResponsePacket response, byte[] body) {
|
||||
Objects.requireNonNull(response, "response");
|
||||
|
||||
return new WebResponsePacket(
|
||||
response.getStatusCode(),
|
||||
response.getContentType(),
|
||||
response.getHeaders(),
|
||||
body != null ? body : new byte[0]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Maps a v1.0.0 {@link WebResponsePacket} to a v1.0.1 {@link WebResourceResponsePacket}
|
||||
* using the provided synthetic correlation header.
|
||||
*
|
||||
* @param correlationHeader synthetic correlation header (requestId/tabId/pageId/frameId)
|
||||
* @param responsePacket 1.0.0 response packet
|
||||
* @return v1.0.1 resource response packet
|
||||
*/
|
||||
public static WebResourceResponsePacket toV101ResourceResponse(WebPacketHeader correlationHeader, WebResponsePacket responsePacket) {
|
||||
Objects.requireNonNull(correlationHeader, "correlationHeader");
|
||||
Objects.requireNonNull(responsePacket, "responsePacket");
|
||||
|
||||
WebPacketHeader header = mirrorCorrelation(correlationHeader, WebPacketFlags.RESOURCE);
|
||||
|
||||
return new WebResourceResponsePacket(
|
||||
header,
|
||||
responsePacket.getStatusCode(),
|
||||
responsePacket.getContentType(),
|
||||
responsePacket.getHeaders(),
|
||||
responsePacket.getBody(),
|
||||
null
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Maps a v1.0.0 {@link WebStreamStartPacket_v1_0_0_B} to a v1.0.1 {@link WebStreamStartPacket_v1_0_1_B}
|
||||
* using the provided synthetic correlation header.
|
||||
*
|
||||
* @param correlationHeader synthetic correlation header
|
||||
* @param streamPacket 1.0.0 stream start
|
||||
* @return v1.0.1 stream start
|
||||
*/
|
||||
public static WebStreamStartPacket_v1_0_1_B toV101StreamStart(WebPacketHeader correlationHeader, WebStreamStartPacket_v1_0_0_B streamPacket) {
|
||||
Objects.requireNonNull(correlationHeader, "correlationHeader");
|
||||
Objects.requireNonNull(streamPacket, "streamPacket");
|
||||
|
||||
WebPacketHeader header = mirrorCorrelation(correlationHeader, WebPacketFlags.RESOURCE);
|
||||
|
||||
return new WebStreamStartPacket_v1_0_1_B(
|
||||
header,
|
||||
streamPacket.getStatusCode(),
|
||||
streamPacket.getContentType(),
|
||||
streamPacket.getHeaders(),
|
||||
streamPacket.getTotalLength()
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Maps a v1.0.0 {@link WebStreamChunkPacket_v1_0_0_B} to a v1.0.1 {@link WebStreamChunkPacket_v1_0_1_B}
|
||||
* using the provided synthetic correlation header.
|
||||
*
|
||||
* @param correlationHeader synthetic correlation header
|
||||
* @param streamPacket 1.0.0 stream chunk
|
||||
* @return v1.0.1 stream chunk
|
||||
*/
|
||||
public static WebStreamChunkPacket_v1_0_1_B toV101StreamChunk(WebPacketHeader correlationHeader, WebStreamChunkPacket_v1_0_0_B streamPacket) {
|
||||
Objects.requireNonNull(correlationHeader, "correlationHeader");
|
||||
Objects.requireNonNull(streamPacket, "streamPacket");
|
||||
|
||||
WebPacketHeader header = mirrorCorrelation(correlationHeader, WebPacketFlags.RESOURCE);
|
||||
|
||||
return new WebStreamChunkPacket_v1_0_1_B(
|
||||
header,
|
||||
streamPacket.getSeq(),
|
||||
streamPacket.getData()
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Maps a v1.0.0 {@link WebStreamEndPacket_v1_0_0_B} to a v1.0.1 {@link WebStreamEndPacket_v1_0_1_B}
|
||||
* using the provided synthetic correlation header.
|
||||
*
|
||||
* @param correlationHeader synthetic correlation header
|
||||
* @param streamPacket v1.0.0 stream end packet
|
||||
* @return v1.0.1 stream end packet
|
||||
*/
|
||||
public static WebStreamEndPacket_v1_0_1_B toV101StreamEnd(
|
||||
WebPacketHeader correlationHeader,
|
||||
WebStreamEndPacket_v1_0_0_B streamPacket
|
||||
) {
|
||||
Objects.requireNonNull(correlationHeader, "correlationHeader");
|
||||
Objects.requireNonNull(streamPacket, "streamPacket");
|
||||
|
||||
WebPacketHeader header = mirrorCorrelation(correlationHeader, WebPacketFlags.RESOURCE);
|
||||
|
||||
// v1.0.0 has no error string -> null
|
||||
return new WebStreamEndPacket_v1_0_1_B(
|
||||
header,
|
||||
streamPacket.isOk(),
|
||||
null
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a header that mirrors correlation fields from the provided header and updates timestamp/flags.
|
||||
*
|
||||
* @param correlationHeader correlation header to mirror
|
||||
* @param extraFlags flags to OR
|
||||
* @return mirrored header
|
||||
*/
|
||||
public static WebPacketHeader mirrorCorrelation(WebPacketHeader correlationHeader, int extraFlags) {
|
||||
Objects.requireNonNull(correlationHeader, "correlationHeader");
|
||||
return new WebPacketHeader(
|
||||
correlationHeader.getRequestId(),
|
||||
correlationHeader.getTabId(),
|
||||
correlationHeader.getPageId(),
|
||||
correlationHeader.getFrameId(),
|
||||
correlationHeader.getFlags() | extraFlags,
|
||||
System.currentTimeMillis()
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Maps 1.0.0 {@link WebRequestMethod}.
|
||||
*
|
||||
* @param method 1.0.0 method
|
||||
* @return method string
|
||||
*/
|
||||
public static String mapMethod(WebRequestMethod method) {
|
||||
if (method == null) return "GET";
|
||||
return switch (method) {
|
||||
case GET -> "GET";
|
||||
case POST, EXECUTE, SCRIPT -> "POST";
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Maps 1.0.1 to 1.0.0 {@link WebRequestMethod}.
|
||||
*
|
||||
* @param method 1.0.0 method
|
||||
* @return method string
|
||||
*/
|
||||
public static WebRequestMethod map100BMethod(String method) {
|
||||
if (method == null) return WebRequestMethod.GET;
|
||||
return switch (method.toUpperCase()) {
|
||||
case "POST" -> WebRequestMethod.POST;
|
||||
default -> WebRequestMethod.GET;
|
||||
};
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user