Files
Flash5/flash/src/test/java/dev/relism/flash/websocket/WebSocketSessionTest.java
T
Zakaria El OrcheandClaude Sonnet 5 a037456634
CI / Build & Test (push) Failing after 6m7s
CI / Build & Test (pull_request) Failing after 4m56s
feat(core): WS client-mode masking, zero-copy header iteration, shared I/O relay buffer
- WebSocketSession supports client-mode outgoing frame masking (RFC 6455) via
  in-place XOR, reusing the unmask routine already used for inbound frames.
- HeaderMap gains an allocation-free forEach(HeaderConsumer) for callers that
  must handle an open-ended set of header names (e.g. proxying).
- HttpServer relays streaming/chunked response bodies through a shared
  per-connection ThreadLocal buffer instead of relying on InputStream#transferTo
  (which allocates internally) or a fresh byte[8192] per chunked write.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-09 20:24:56 +00:00

105 lines
3.9 KiB
Java

package dev.relism.flash.websocket;
import dev.relism.flash.http.HttpMethod;
import dev.relism.flash.models.HeaderMap;
import dev.relism.flash.models.Request;
import dev.relism.flash.models.RequestLine;
import dev.relism.fpr.core.ByteView;
import org.junit.jupiter.api.Test;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.nio.charset.StandardCharsets;
import static org.junit.jupiter.api.Assertions.*;
class WebSocketSessionTest {
private static ByteView viewOf(String s) {
byte[] bytes = s.getBytes(StandardCharsets.UTF_8);
return new ByteView() {
public int length() { return bytes.length; }
public byte byteAt(int idx) { return bytes[idx]; }
};
}
@Test
void request_returnsWhatWasPassedToConstructor() {
RequestLine line = new RequestLine(HttpMethod.GET, viewOf("/chat"), null, viewOf("HTTP/1.1"), new HeaderMap());
Request req = new Request(line, new byte[0]);
WebSocketSession session = new WebSocketSession(
new ByteArrayInputStream(new byte[0]), new ByteArrayOutputStream(), 64, req, false);
assertSame(req, session.request());
}
@Test
void request_defaultsToNullOnThreeArgConstructor() {
WebSocketSession session = new WebSocketSession(new ByteArrayInputStream(new byte[0]), new ByteArrayOutputStream(), 64);
assertNull(session.request());
}
@Test
void sendText_masksWhenActingAsClient() throws Exception {
ByteArrayOutputStream out = new ByteArrayOutputStream();
WebSocketSession session = new WebSocketSession(
new ByteArrayInputStream(new byte[0]), out, 64, null, true);
byte[] payload = "hi".getBytes();
session.sendText(payload, 0, payload.length);
byte[] bytes = out.toByteArray();
assertEquals((byte) 0x81, bytes[0]); // FIN + TEXT
assertEquals((byte) (0x80 | 2), bytes[1]); // masked bit + length 2
byte m0 = bytes[2], m1 = bytes[3], m2 = bytes[4], m3 = bytes[5];
assertEquals((byte) ('h' ^ m0), bytes[6]);
assertEquals((byte) ('i' ^ m1), bytes[7]);
// The caller's buffer is mutated in place by the mask (documented, zero-copy tradeoff).
assertEquals((byte) ('h' ^ m0), payload[0]);
}
@Test
void sendText_doesNotMaskWhenActingAsServer() throws Exception {
ByteArrayOutputStream out = new ByteArrayOutputStream();
WebSocketSession session = new WebSocketSession(new ByteArrayInputStream(new byte[0]), out, 64);
byte[] payload = "hi".getBytes();
session.sendText(payload, 0, payload.length);
byte[] bytes = out.toByteArray();
assertEquals((byte) 0x81, bytes[0]);
assertEquals((byte) 2, bytes[1]); // no masked bit
assertEquals('h', bytes[2]);
assertEquals('i', bytes[3]);
}
@Test
void close_setsClosedAndWritesFrame() throws Exception {
ByteArrayOutputStream out = new ByteArrayOutputStream();
WebSocketSession session = new WebSocketSession(new ByteArrayInputStream(new byte[0]), out, 64);
session.close(1000);
assertFalse(session.isOpen());
assertEquals(1000, session.closeCode());
byte[] bytes = out.toByteArray();
assertEquals((byte) 0x88, bytes[0]);
assertEquals((byte) 0x02, bytes[1]);
assertEquals((byte) 0x03, bytes[2]);
assertEquals((byte) 0xE8, bytes[3]);
}
@Test
void closeFromPeer_extractsCloseCode() {
WebSocketSession session = new WebSocketSession(new ByteArrayInputStream(new byte[0]), new ByteArrayOutputStream(), 64);
WebSocketFrame frame = new WebSocketFrame();
frame.reset(new byte[] {(byte) 0x03, (byte) 0xE8}, 0, 2, WebSocketFrame.OP_CLOSE, true);
session.closeFromPeer(frame);
assertFalse(session.isOpen());
assertEquals(1000, session.closeCode());
}
}