Skip to content

Commit cd64953

Browse files
fix(std): working hacked response
1 parent 3e6f0d9 commit cd64953

File tree

5 files changed

+150
-86
lines changed

5 files changed

+150
-86
lines changed

packages/jco-std/src/wasi/0.2.3/http/types/response.ts

Lines changed: 37 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,13 @@
11
import { OutgoingBody, ResponseOutparam, Fields, OutgoingResponse, FieldValue } from 'wasi:http/[email protected]';
22

3+
const ENCODER = new TextEncoder();
4+
35
/** Write a Web `Response` into a `wasi:[email protected]#outgoing-response` */
46
export async function writeWebResponse(resp: Response, outgoingWasiResp: ResponseOutparam) {
5-
const encoder = new TextEncoder();
7+
// Build headers
68
const fields: [string, FieldValue][] = [];
79
for (const [k,v] of [...resp.headers.entries()]) {
8-
fields.push([k.toString(), encoder.encode(v)]);
10+
fields.push([k.toString(), ENCODER.encode(v)]);
911
}
1012
const headers = Fields.fromList(fields);
1113
const outgoingResponse = new OutgoingResponse(headers);
@@ -22,36 +24,55 @@ export async function writeWebResponse(resp: Response, outgoingWasiResp: Respons
2224
if (resp.body === null) {
2325
throw new Error("unexpectedly missing resp.body");
2426
}
25-
for await (const chunk of resp.body) {
27+
const pollable = outputStream.subscribe();
28+
29+
// Create a reader for the body we'll be writing out
30+
const reader = await resp.body.getReader();
31+
32+
while (true) {
33+
const { value: chunk, done } = await reader.read();
34+
if (done) { break; }
35+
2636
if (chunk.length === 0) {
2737
continue;
2838
}
29-
let written = 0;
39+
40+
let written = 0n;
3041
while (written < chunk.length) {
31-
//let pollable = outputStream.subscribe();
42+
// Wait until output stream is ready
43+
if (!pollable.ready()) {
44+
pollable.block();
45+
}
3246

47+
// Find out how much we are allowed to write
3348
const bytesAllowedRaw = outputStream.checkWrite();
34-
if (!Number.isSafeInteger(bytesAllowedRaw)) {
35-
throw new Error("unexpectedly unsafe integer bytes allowed");
36-
}
37-
const bytesAllowed = Number(bytesAllowedRaw);
3849

39-
outputStream.write(
40-
new Uint8Array(chunk.buffer, written, bytesAllowed)
41-
);
42-
if (written + bytesAllowed > Number.MAX_VALUE) {
43-
throw new Error("integer overflow for written bytes");
50+
// If we can't write as much as we want, we must
51+
const remaining = BigInt(chunk.length) - written;
52+
let pendingAmt;
53+
if (remaining <= bytesAllowedRaw) {
54+
pendingAmt = chunk.length;
55+
} else if (remaining > bytesAllowedRaw) {
56+
pendingAmt = bytesAllowedRaw;
4457
}
45-
written += bytesAllowed;
58+
59+
// Write a view of the chunk in
60+
const view = new Uint8Array(chunk, Number(written), pendingAmt);
61+
outputStream.write(view);
62+
63+
written += BigInt(pendingAmt);
4664
}
4765
}
66+
67+
// Clean up pollable & stream
68+
pollable[Symbol.dispose]();
4869
outputStream[Symbol.dispose]();
4970
}
5071

5172
// Set the outgoing response body w/ no trailers
5273
OutgoingBody.finish(outgoingBody, undefined);
5374

54-
// Set the outparam
75+
// Set the response outparam
5576
ResponseOutparam.set(outgoingWasiResp, {
5677
tag: 'ok',
5778
val: outgoingResponse,

packages/jco-std/src/wasi/0.2.6/http/types/request.ts

Lines changed: 72 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,14 @@
44
* @see: https://github.com/WebAssembly/wasi-http
55
*/
66

7-
import { IncomingBody, IncomingRequest } from "wasi:http/[email protected]";
8-
import { Pollable } from "wasi:io/[email protected]";
9-
import { InputStream } from "wasi:io/[email protected]";
7+
import { IncomingRequest } from "wasi:http/[email protected]";
8+
// import { IncomingBody, IncomingRequest } from "wasi:http/[email protected]";
9+
// import { Pollable } from "wasi:io/[email protected]";
10+
// import { InputStream } from "wasi:io/[email protected]";
1011

11-
import { ensureGlobalReadableStream, ensureGlobalRequest } from "../../../globals.js";
12+
// import { ensureGlobalReadableStream, ensureGlobalRequest } from "../../../globals.js";
1213
import { wasiHTTPMethodToString } from "../../../0.2.x/http/index.js";
13-
import { DEFAULT_INCOMING_BODY_READ_MAX_BYTES } from "../../../constants.js";
14+
// import { DEFAULT_INCOMING_BODY_READ_MAX_BYTES } from "../../../constants.js";
1415

1516
/**
1617
* Create a web-platform `Request` from a `wasi:http/incoming-handler` `incoming-request`.
@@ -31,61 +32,84 @@ export async function readWASIRequest(wasiIncomingRequest: IncomingRequest): Pro
3132
}
3233
const method = wasiHTTPMethodToString(wasiIncomingRequest.method());
3334
const pathWithQuery = wasiIncomingRequest.pathWithQuery();
34-
const scheme = wasiIncomingRequest.scheme();
35+
36+
const schemeRaw = wasiIncomingRequest.scheme();
37+
let scheme;
38+
switch (schemeRaw.tag) {
39+
case 'HTTP':
40+
scheme = 'http'
41+
break;
42+
case 'HTTPS':
43+
scheme = 'https'
44+
break;
45+
default:
46+
throw new Error(`unexpected scheme [${schemeRaw.tag}]`);
47+
}
48+
3549
const authority = wasiIncomingRequest.authority();
3650
const decoder = new TextDecoder('utf-8');
3751
const headers = Object.fromEntries(
3852
wasiIncomingRequest.headers().entries().map(([k,valueBytes]) => {
3953
return [k, decoder.decode(valueBytes)];
4054
})
4155
);
42-
const Request = ensureGlobalRequest();
43-
const ReadableStream = ensureGlobalReadableStream();
56+
// const Request = ensureGlobalRequest();
57+
// const ReadableStream = ensureGlobalReadableStream();
4458

45-
let incomingBody: IncomingBody;
46-
let incomingBodyStream: InputStream;
47-
let incomingBodyPollable: Pollable;
48-
const body = new ReadableStream({
49-
async pull(controller) {
50-
if (!incomingBody) {
51-
incomingBody = wasiIncomingRequest.consume();
52-
incomingBodyStream = incomingBody.stream();
53-
incomingBodyPollable = incomingBodyStream.subscribe();
54-
}
59+
// let incomingBody: IncomingBody;
60+
// let incomingBodyStream: InputStream;
61+
// let incomingBodyPollable: Pollable;
5562

56-
// Read all information coming from the request
57-
while (true) {
58-
// Wait until the pollable is ready
59-
if (!incomingBodyPollable.ready()) {
60-
incomingBodyPollable.block();
61-
}
63+
// const body = new ReadableStream({
64+
// async start(controller) {
65+
// if (!incomingBody) {
66+
// incomingBody = wasiIncomingRequest.consume();
67+
// incomingBodyStream = incomingBody.stream();
68+
// incomingBodyPollable = incomingBodyStream.subscribe();
69+
// }
70+
// },
6271

63-
try {
64-
const bytes = incomingBodyStream.read(
65-
DEFAULT_INCOMING_BODY_READ_MAX_BYTES
66-
);
67-
if (bytes.length === 0) {
68-
break;
69-
} else {
70-
controller.enqueue(bytes);
71-
}
72-
} catch (err) {
73-
console.error('error while reading bytes', err);
74-
controller.close();
75-
break;
76-
}
77-
}
72+
// async pull(controller) {
73+
// // Read all information coming from the request
74+
// while (true) {
75+
// // Wait until the pollable is ready
76+
// if (!incomingBodyPollable.ready()) {
77+
// incomingBodyPollable.block();
78+
// }
7879

79-
incomingBodyPollable[Symbol.dispose]();
80-
incomingBodyStream[Symbol.dispose]();
81-
incomingBody[Symbol.dispose]();
82-
controller.close();
83-
},
84-
});
85-
86-
return new Request(`${scheme}://${authority}/${pathWithQuery}`, {
80+
// try {
81+
// console.error("BEFORE READ!");
82+
// const bytes = incomingBodyStream.read(
83+
// DEFAULT_INCOMING_BODY_READ_MAX_BYTES
84+
// );
85+
// if (bytes.length === 0) {
86+
// break;
87+
// } else {
88+
// controller.enqueue(bytes);
89+
// }
90+
// } catch (err) {
91+
// // If the channel is closed, we can break out
92+
// if (err.payload.tag === 'closed') { break; }
93+
// throw err;
94+
// }
95+
// }
96+
97+
// incomingBodyPollable[Symbol.dispose]();
98+
// incomingBodyStream[Symbol.dispose]();
99+
// incomingBody[Symbol.dispose]();
100+
// wasiIncomingRequest[Symbol.dispose]();
101+
// controller.close();
102+
// },
103+
// });
104+
105+
// TODO: unfortunately, Request`s don't seem to work properly with StarlingMonkey.
106+
// we may have to do some sort of polyfill.
107+
const url = `${scheme}://${authority}${pathWithQuery}`;
108+
const req = new Request(url, {
87109
method,
88110
headers,
89-
body,
111+
// body, <--- using any kind of body will break
90112
});
113+
114+
return req;
91115
}

packages/jco-std/src/wasi/0.2.6/http/types/response.ts

Lines changed: 38 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,13 @@
11
import { OutgoingBody, ResponseOutparam, Fields, OutgoingResponse, FieldValue } from 'wasi:http/[email protected]';
22

3+
const ENCODER = new TextEncoder();
4+
35
/** Write a Web `Response` into a `wasi:[email protected]#outgoing-response` */
46
export async function writeWebResponse(resp: Response, outgoingWasiResp: ResponseOutparam) {
5-
const encoder = new TextEncoder();
7+
// Build headers
68
const fields: [string, FieldValue][] = [];
79
for (const [k,v] of [...resp.headers.entries()]) {
8-
fields.push([k.toString(), encoder.encode(v)]);
10+
fields.push([k.toString(), ENCODER.encode(v)]);
911
}
1012
const headers = Fields.fromList(fields);
1113
const outgoingResponse = new OutgoingResponse(headers);
@@ -22,35 +24,55 @@ export async function writeWebResponse(resp: Response, outgoingWasiResp: Respons
2224
if (resp.body === null) {
2325
throw new Error("unexpectedly missing resp.body");
2426
}
25-
for await (const chunk of resp.body) {
27+
const pollable = outputStream.subscribe();
28+
29+
// Create a reader for the body we'll be writing out
30+
const reader = await resp.body.getReader();
31+
32+
while (true) {
33+
const { value: chunk, done } = await reader.read();
34+
if (done) { break; }
35+
2636
if (chunk.length === 0) {
2737
continue;
2838
}
29-
let written = 0;
39+
40+
let written = 0n;
3041
while (written < chunk.length) {
31-
//let pollable = outputStream.subscribe();
32-
const bytesAllowedRaw = outputStream.checkWrite();
33-
if (!Number.isSafeInteger(bytesAllowedRaw)) {
34-
throw new Error("unexpectedly unsafe integer bytes allowed");
42+
// Wait until output stream is ready
43+
if (!pollable.ready()) {
44+
pollable.block();
3545
}
36-
const bytesAllowed = Number(bytesAllowedRaw);
3746

38-
outputStream.write(
39-
new Uint8Array(chunk.buffer, written, bytesAllowed)
40-
);
41-
if (written + bytesAllowed > Number.MAX_VALUE) {
42-
throw new Error("integer overflow for written bytes");
47+
// Find out how much we are allowed to write
48+
const bytesAllowedRaw = outputStream.checkWrite();
49+
50+
// If we can't write as much as we want, we must
51+
const remaining = BigInt(chunk.length) - written;
52+
let pendingAmt;
53+
if (remaining <= bytesAllowedRaw) {
54+
pendingAmt = chunk.length;
55+
} else if (remaining > bytesAllowedRaw) {
56+
pendingAmt = bytesAllowedRaw;
4357
}
44-
written += bytesAllowed;
58+
59+
// Write a view of the chunk in
60+
const view = new Uint8Array(chunk, Number(written), pendingAmt);
61+
outputStream.write(view);
62+
63+
written += BigInt(pendingAmt);
4564
}
4665
}
66+
67+
// Clean up pollable & stream
68+
pollable[Symbol.dispose]();
4769
outputStream[Symbol.dispose]();
4870
}
4971

5072
// Set the outgoing response body w/ no trailers
5173
OutgoingBody.finish(outgoingBody, undefined);
5274

53-
// Set the outparam
75+
// Set the response outparam
5476
ResponseOutparam.set(outgoingWasiResp, {
5577
tag: 'ok',
5678
val: outgoingResponse,

packages/jco-std/src/wasi/0.2.x/hono.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
/* eslint-disable @typescript-eslint/no-explicit-any */
2+
/* eslint-disable @typescript-eslint/no-empty-object-type */
23

34
import type { Hono, Schema as HonoSchema, Env as HonoEnv } from 'hono';
45
import { FetchEventLike } from 'hono/types';

packages/jco-std/test/e2e/apps.ts

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,6 @@ const FIXTURE_APPS_DIR = fileURLToPath(new URL("../fixtures/apps", import.meta.u
1515

1616
const JCO_STD_DIR = fileURLToPath(new URL("../../", import.meta.url));
1717

18-
const DEFAULT_TEST_WIT_WORLD = "hono-fetch-event";
19-
2018
/** Get the binary path to wasmtime if it doesn't exist */
2119
async function getWasmtimeBin(env?: Record<string, string>): Promise<string> {
2220
try {
@@ -64,8 +62,6 @@ suite("hono apps", async () => {
6462
// If the folder doesn't have an app script, we can quit early
6563
if (!scriptExists) { continue; }
6664

67-
if (appDir.name !== 'wasi-http-hono') { continue; }
68-
6965
const appFolderName = appDir.name;
7066

7167
// Load the test script, and pull configuration out, if present
@@ -90,7 +86,7 @@ suite("hono apps", async () => {
9086
// Get wasmtime dir path, ensure it exists
9187
const wasmtimeBin = await getWasmtimeBin();
9288

93-
test(`[${appFolderName}]`, async () => {
89+
test.concurrent(`[${appFolderName}]`, async () => {
9490
log(`testing app [${appFolderName}]`);
9591

9692
// Bundle the application w/ deps via rolldown
@@ -119,7 +115,7 @@ suite("hono apps", async () => {
119115
worldName: witWorldName,
120116
};
121117

122-
let { component } = await componentize(componentJS, opts);
118+
const { component } = await componentize(componentJS, opts);
123119

124120
// Write out the component to a file
125121
const componentOutputPath = join(componentOutputDir, "component.wasm");

0 commit comments

Comments
 (0)