bug 1523104: remote: add WebSocketServer.upgrade for upgrading existing httpd.js requests; r=ochameau
authorAndreas Tolfsen <ato@sny.no>
Thu, 07 Mar 2019 22:17:36 +0000
changeset 524134 2dd67c30b7ae9994dadf32a0e2aa41f2268bd7f8
parent 524133 2c703d235c4c57b2e309510fc6b524ad046a0c4a
child 524135 53947dc827ddfbf1eaab8aaa753824c4cffc9ffe
push id2032
push userffxbld-merge
push dateMon, 13 May 2019 09:36:57 +0000
treeherdermozilla-release@455c1065dcbe [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersochameau
bugs1523104
milestone67.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
bug 1523104: remote: add WebSocketServer.upgrade for upgrading existing httpd.js requests; r=ochameau
remote/server/WebSocket.jsm
--- a/remote/server/WebSocket.jsm
+++ b/remote/server/WebSocket.jsm
@@ -125,29 +125,25 @@ const readHttpRequest = async function(i
 
 /** Write HTTP response (array of strings) to async output stream. */
 function writeHttpResponse(output, response) {
   const s = response.join("\r\n") + "\r\n\r\n";
   return writeString(output, s);
 }
 
 /**
- * Process the WebSocket handshake headers
- * and return the key to be sent in Sec-WebSocket-Accept response header.
+ * Process the WebSocket handshake headers and return the key to be sent in
+ * Sec-WebSocket-Accept response header.
  */
-function processRequest({ requestLine, headers }) {
-  const [method, path] = requestLine.split(" ");
+function processRequest({requestLine, headers}) {
+  const method = requestLine.split(" ")[0];
   if (method !== "GET") {
     throw new Error("The handshake request must use GET method");
   }
 
-  if (path !== "/") {
-    throw new Error("The handshake request has unknown path");
-  }
-
   const upgrade = headers.get("upgrade");
   if (!upgrade || upgrade !== "websocket") {
     throw new Error("The handshake request has incorrect Upgrade header");
   }
 
   const connection = headers.get("connection");
   if (!connection || !connection.split(",").map(t => t.trim()).includes("Upgrade")) {
     throw new Error("The handshake request has incorrect Connection header");
@@ -159,80 +155,97 @@ function processRequest({ requestLine, h
   }
 
   // Compute the accept key
   const key = headers.get("sec-websocket-key");
   if (!key) {
     throw new Error("The handshake request must have a Sec-WebSocket-Key header");
   }
 
-  const acceptKey = computeKey(key);
-  return {acceptKey};
+  return { acceptKey: computeKey(key) };
 }
 
 function computeKey(key) {
   const str = `${key}258EAFA5-E914-47DA-95CA-C5AB0DC85B11`;
   const data = Array.from(str, ch => ch.charCodeAt(0));
   const hash = new CryptoHash("sha1");
   hash.update(data, data.length);
   return hash.finish(true);
 }
 
 /**
- * Perform the server part of a WebSocket opening handshake on an incoming connection.
+ * Perform the server part of a WebSocket opening handshake
+ * on an incoming connection.
  */
-const serverHandshake = async function(input, output) {
-  // Read the request
-  const request = await readHttpRequest(input);
-
+async function serverHandshake(request, output) {
   try {
     // Check and extract info from the request
-    const { acceptKey } = processRequest(request);
+    const {acceptKey} = processRequest(request);
 
     // Send response headers
     await writeHttpResponse(output, [
       "HTTP/1.1 101 Switching Protocols",
       "Upgrade: websocket",
       "Connection: Upgrade",
       `Sec-WebSocket-Accept: ${acceptKey}`,
     ]);
   } catch (error) {
     // Send error response in case of error
     await writeHttpResponse(output, [ "HTTP/1.1 400 Bad Request" ]);
     throw error;
   }
-};
+}
+
+async function createWebSocket(transport, input, output) {
+  const transportProvider = {
+    setListener(upgradeListener) {
+      // onTransportAvailable callback shouldn't be called synchronously
+      Services.tm.dispatchToMainThread(() => {
+        upgradeListener.onTransportAvailable(transport, input, output);
+      });
+    },
+  };
+
+  return new Promise((resolve, reject) => {
+    const socket = WebSocket.createServerWebSocket(null, [], transportProvider, "");
+    socket.addEventListener("close", () => {
+      input.close();
+      output.close();
+    });
+
+    socket.onopen = () => resolve(socket);
+    socket.onerror = err => reject(err);
+  });
+}
 
 /**
  * Accept an incoming WebSocket server connection.
  * Takes an established nsISocketTransport in the parameters.
  * Performs the WebSocket handshake and waits for the WebSocket to open.
  * Returns Promise with a WebSocket ready to send and receive messages.
  */
-const accept = async function(transport, rx, tx) {
-  await serverHandshake(rx, tx);
+async function accept(transport, input, output) {
+  const request = await readHttpRequest(input);
+  await serverHandshake(request, output);
+  return createWebSocket(transport, input, output);
+}
 
-  const transportProvider = {
-    setListener(upgradeListener) {
-      // onTransportAvailable callback shouldn't be called synchronously
-      executeSoon(() => {
-        upgradeListener.onTransportAvailable(transport, rx, tx);
-      });
-    },
-  };
+/** Upgrade an existing HTTP request from httpd.js to WebSocket. */
+async function upgrade(request, response) {
+  // handle response manually, allowing us to send arbitrary data
+  response._powerSeized = true;
+
+  const {transport, input, output} = response._connection;
 
-  return new Promise((resolve, reject) => {
-    const so = WebSocket.createServerWebSocket(null, [], transportProvider, "");
-    so.addEventListener("close", () => {
-      rx.close();
-      tx.close();
-    });
+  const headers = new Map();
+  for (let [key, values] of Object.entries(request._headers._headers)) {
+    headers.set(key, values.join("\n"));
+  }
+  const convertedRequest = {
+    requestLine: `${request.method} ${request.path}`,
+    headers,
+  };
+  await serverHandshake(convertedRequest, output);
 
-    so.onopen = () => resolve(so);
-    so.onerror = err => reject(err);
-  });
-};
+  return createWebSocket(transport, input, output);
+}
 
-const executeSoon = function(func) {
-  Services.tm.dispatchToMainThread(func);
-};
-
-const WebSocketServer = {accept};
+const WebSocketServer = {accept, upgrade};