Compile and run the Java.
Open both browsers to http://localhost:8080
Open consoles in both browsers.
In one, enter the call to listen: listen()
In the other, enter the call to send a message: send("hello from other browser")
| import java.io.*; | |
| import java.net.ServerSocket; | |
| import java.net.Socket; | |
| import java.net.URLDecoder; | |
| import java.nio.charset.StandardCharsets; | |
| import java.util.concurrent.ArrayBlockingQueue; | |
| import java.util.concurrent.BlockingQueue; | |
| public class BrowserRelay { | |
| // Shared queue between the /send handler and the /listen SSE stream | |
| static final BlockingQueue<String> messages = new ArrayBlockingQueue<>(100); | |
| public static void main(String[] args) throws IOException { | |
| System.setOut(new PrintStream(System.out, true)); | |
| try (ServerSocket server = new ServerSocket(8080)) { | |
| System.out.println("Relay running on http://localhost:8080"); | |
| while (true) { | |
| Socket client = server.accept(); | |
| new Thread(() -> handleClient(client)).start(); | |
| } | |
| } | |
| } | |
| static void handleClient(Socket client) { | |
| try (client; | |
| InputStream in = client.getInputStream(); | |
| OutputStream out = client.getOutputStream()) { | |
| String requestLine = readLine(in); | |
| if (requestLine == null || requestLine.isEmpty()) return; | |
| System.out.println(requestLine); | |
| // Drain headers, grab Origin for CORS | |
| String origin = "http://localhost:8080"; | |
| String headerLine; | |
| while (!(headerLine = readLine(in)).isEmpty()) { | |
| if (headerLine.toLowerCase().startsWith("origin:")) | |
| origin = headerLine.split(":", 2)[1].trim(); | |
| } | |
| String url = requestLine.split(" ")[1]; | |
| if (url.startsWith("/send")) { | |
| // POST from Firefox: extract msg param and queue it | |
| String query = url.contains("?") ? url.split("\\?", 2)[1] : ""; | |
| String msg = parseParam(query, "msg"); | |
| messages.offer(msg.isEmpty() ? "(empty)" : msg); | |
| System.out.println("Queued: " + msg); | |
| String body = "OK"; | |
| writeResponse(out, "200 OK", "text/plain", origin, | |
| body.getBytes(StandardCharsets.UTF_8), false); | |
| } else if (url.startsWith("/listen")) { | |
| // SSE stream held open for Safari | |
| String headers = "HTTP/1.1 200 OK\r\n" | |
| + "Content-Type: text/event-stream\r\n" | |
| + "Cache-Control: no-cache\r\n" | |
| + "Access-Control-Allow-Origin: " + origin + "\r\n" | |
| + "Connection: keep-alive\r\n" | |
| + "\r\n"; | |
| out.write(headers.getBytes(StandardCharsets.US_ASCII)); | |
| out.flush(); | |
| // Block here, pushing messages as they arrive | |
| while (true) { | |
| String msg = messages.take(); // blocks until something is queued | |
| String event = "data: " + msg + "\n\n"; // SSE format | |
| out.write(event.getBytes(StandardCharsets.UTF_8)); | |
| out.flush(); | |
| System.out.println("Sent to listener: " + msg); | |
| } | |
| } else { | |
| // Serve a minimal HTML page for both browsers | |
| String body = """ | |
| <!DOCTYPE html><html><body> | |
| <h3>Browser Relay</h3> | |
| <script> | |
| // Open SSE listener (run this in Safari) | |
| function listen() { | |
| const es = new EventSource('http://localhost:8080/listen'); | |
| es.onmessage = e => console.log('Received:', e.data); | |
| console.log('Listening for messages...'); | |
| } | |
| // Send a message (run this in Firefox) | |
| function send(msg) { | |
| fetch('http://localhost:8080/send?msg=' + encodeURIComponent(msg)) | |
| .then(() => console.log('Sent: ' + msg)); | |
| } | |
| </script> | |
| <p>Open the browser console and call <code>listen()</code> or <code>send('hello')</code></p> | |
| </body></html> | |
| """; | |
| writeResponse(out, "200 OK", "text/html", origin, | |
| body.getBytes(StandardCharsets.UTF_8), false); | |
| } | |
| } catch (IOException | InterruptedException e) { | |
| System.out.println("Connection closed: " + e.getMessage()); | |
| } | |
| } | |
| static void writeResponse(OutputStream out, String status, String contentType, | |
| String origin, byte[] body, boolean close) throws IOException { | |
| String headers = "HTTP/1.1 " + status + "\r\n" | |
| + "Content-Type: " + contentType + "\r\n" | |
| + "Content-Length: " + body.length + "\r\n" | |
| + "Access-Control-Allow-Origin: " + origin + "\r\n" | |
| + (close ? "Connection: close\r\n" : "") + "\r\n"; | |
| out.write(headers.getBytes(StandardCharsets.US_ASCII)); | |
| out.write(body); | |
| out.flush(); | |
| } | |
| static String parseParam(String query, String key) throws UnsupportedEncodingException { | |
| for (String pair : query.split("[&;]")) { | |
| int eq = pair.indexOf('='); | |
| if (eq == -1) continue; | |
| String k = URLDecoder.decode(pair.substring(0, eq), StandardCharsets.UTF_8); | |
| if (k.equals(key)) | |
| return URLDecoder.decode(pair.substring(eq + 1), StandardCharsets.UTF_8); | |
| } | |
| return ""; | |
| } | |
| static String readLine(InputStream in) throws IOException { | |
| ByteArrayOutputStream buf = new ByteArrayOutputStream(); | |
| int b; | |
| while ((b = in.read()) != -1) { | |
| if (b == '\n') break; | |
| if (b == '\r') continue; | |
| buf.write(b); | |
| } | |
| return buf.toString(StandardCharsets.UTF_8); | |
| } | |
| } |
Compile and run the Java.
Open both browsers to http://localhost:8080
Open consoles in both browsers.
In one, enter the call to listen: listen()
In the other, enter the call to send a message: send("hello from other browser")