( info: ComputerdInfo, rpc: ReturnType<typeof createWorkspaceServer>, getStats?: () => Record<string, unknown>, )
| 165 | } |
| 166 | |
| 167 | function createHTTPServer( |
| 168 | info: ComputerdInfo, |
| 169 | rpc: ReturnType<typeof createWorkspaceServer>, |
| 170 | getStats?: () => Record<string, unknown>, |
| 171 | ): HTTPHandle { |
| 172 | // Holds the current outbound capnweb session opened via /connect. |
| 173 | // Re-POSTing /connect (e.g. after a DO hibernate + new incarnation) |
| 174 | // closes the previous socket so the old session doesn't leak for |
| 175 | // the life of the container. |
| 176 | const upstreamSlot: { ws: WebSocket | undefined } = { ws: undefined }; |
| 177 | const server = createServer((request, response) => { |
| 178 | const path = requestPath(request); |
| 179 | |
| 180 | // /api — capnweb HTTP-batch endpoint. Single POST per call; |
| 181 | // request body carries the serialized message, response body |
| 182 | // carries the reply. Useful for environments that can't open |
| 183 | // a WebSocket (curl, fetch from a Worker without ws upgrade). |
| 184 | if (path === "/api") { |
| 185 | if (request.method !== "POST") { |
| 186 | send(response, 405, "method not allowed\n", { |
| 187 | allow: "POST", |
| 188 | "content-type": "text/plain; charset=utf-8", |
| 189 | }); |
| 190 | return; |
| 191 | } |
| 192 | void serveHTTPBatch(request, response, rpc).catch((error) => { |
| 193 | console.error("/api batch failed:", error); |
| 194 | if (!response.headersSent) { |
| 195 | send(response, 500, "internal error\n", { |
| 196 | "content-type": "text/plain; charset=utf-8", |
| 197 | }); |
| 198 | } |
| 199 | }); |
| 200 | return; |
| 201 | } |
| 202 | |
| 203 | // /connect — POST { url } where url is the http(s) base of an |
| 204 | // egress endpoint the host wants us to dial back into. We open a |
| 205 | // capnweb WebSocket session against `${url}/ws` and serve our RPC |
| 206 | // over it, exactly like /ws but with the carrier inverted (we |
| 207 | // dial out instead of accepting an inbound upgrade). |
| 208 | if (path === "/connect") { |
| 209 | if (request.method !== "POST") { |
| 210 | send(response, 405, "method not allowed\n", { |
| 211 | allow: "POST", |
| 212 | "content-type": "text/plain; charset=utf-8", |
| 213 | }); |
| 214 | return; |
| 215 | } |
| 216 | void handleConnect(request, response, rpc, upstreamSlot); |
| 217 | return; |
| 218 | } |
| 219 | |
| 220 | if (request.method !== "GET" && request.method !== "HEAD") { |
| 221 | send(response, 405, "method not allowed\n", { |
| 222 | allow: "GET, HEAD", |
| 223 | "content-type": "text/plain; charset=utf-8", |
| 224 | }); |
no test coverage detected