jco icon indicating copy to clipboard operation
jco copied to clipboard

Conversion between incomingHandler and request/response

Open peter-jerry-ye opened this issue 1 year ago • 5 comments

Hello,

Since the wasi-http defines a server that accepts an incoming request and returns a response, and jco have the ability to serve a server, is it possible to provide a conversion between the WasiHttpIncomingHandler and a classic JS handlerfetch(request: Request): Promise<Response> (used by e.g. Cloudflare Worker and Deno)? Or should I develop a completely new shim based on the preview2-shim?

peter-jerry-ye avatar Jul 26 '24 10:07 peter-jerry-ye

Hi, I've been working on exactly that. But requires JSPI or Asyncify to handle the asynchronous aspect. Let me know if you are interested in collaborating.

calvinrp avatar Jul 26 '24 15:07 calvinrp

@calvinrp Hi, I wonder if it's possible to have a synchronous version MVP? I think the asynchronous might be better supported with WASI Preview 3?

peter-jerry-ye avatar Jul 28 '24 06:07 peter-jerry-ye

@peter-jerry-ye I do have sync implementation working just for the incoming response handler, but limited to that.

If you want to do outbound requests or many things that rely upon wasi:io/poll or wasi:io/streams, then you will need async support.

The issue is the JS APIs that you need to use are async but the Wasm functions are sync (in WASI P2). There a few different ways to go about this. JSPI (JavaScript Promise Integration), which is a stack switching proposal, is the ideal implementation. It is behind a feature flag in V8 and expected to be released before the end of year. Asyncify requires a rewrite of the Wasm binary but could be used as a stop gap. Using Atomics and Web Workers is also helpful in some circumstances. Also, to a limited degree there is a path with synchronous XHR, Web Workers and Service Workers.

calvinrp avatar Jul 28 '24 12:07 calvinrp

@calvinrp Thank you very much for your kind explanation. I see that jco server seems to be using Web Workers to solve this issue.

peter-jerry-ye avatar Jul 29 '24 02:07 peter-jerry-ye

Happy to update on progress. It sounds like what I'm working on will be useful to you.

calvinrp avatar Jul 29 '24 12:07 calvinrp

At this point we have JSPI based bindings and async host imports are possible -- I assume this issue doesn't still apply but if it does I'm happy to re-open (it's been a year)!

vados-cosmonic avatar Aug 28 '25 14:08 vados-cosmonic

Sorry for posting in a closed thread, but I had a similar issue and couldn't find an answer.


I want to run a .NET application compiled for wasi-wasm inside a Cloudflare Worker and handle incoming/outgoing http requests.

<ItemGroup>
	<PackageReference Include="BytecodeAlliance.Componentize.DotNet.Wasm.SDK" Version="0.2.0-preview00004" />
</ItemGroup>

<ItemGroup>
	<Wit Include="wit/wit.wasm" World="proxy" Registry="ghcr.io/webassembly/wasi/http:0.2.0" />
</ItemGroup>
public class IncomingHandlerImpl : IIncomingHandler
{
    public static void Handle(ITypes.IncomingRequest request, ITypes.ResponseOutparam responseOut)
    { }
}

After that I transpile the resulting WASM component:

jco transpile MyApp.wasm -o out-dir --no-namespaced-exports --no-typescript --tla-compat

After initializing the module, I encountered a problem: the handler method requires arguments of the IncomingRequest and IncomingRequest types, but they do not have a public constructor/builder, and as a result, I cannot simply wrap the request object from the fetch handler:

function handle(arg0, arg1) {
  if (!_initialized) throwUninitialized();
  if (!(arg0 instanceof IncomingRequest)) {
    throw new TypeError('Resource error: Not a valid "IncomingRequest" resource.');
  }
  var handle0 = arg0[symbolRscHandle];
  if (!handle0) {
    const rep = arg0[symbolRscRep] || ++captureCnt16;
    captureTable16.set(rep, arg0);
    handle0 = rscTableCreateOwn(handleTable16, rep);
  }
  if (!(arg1 instanceof ResponseOutparam)) {
    throw new TypeError('Resource error: Not a valid "ResponseOutparam" resource.');
  }
  var handle1 = arg1[symbolRscHandle];
  if (!handle1) {
    const rep = arg1[symbolRscRep] || ++captureCnt13;
    captureTable13.set(rep, arg1);
    handle1 = rscTableCreateOwn(handleTable13, rep);
  }
  _debugLog('[iface="wasi:http/[email protected]", function="handle"] [Instruction::CallWasm] (async? false, @ enter)');
  const _wasm_call_currentTaskID = startCurrentTask(0, false, 'incomingHandler020Handle');
  incomingHandler020Handle(handle0, handle1);
  endCurrentTask(0);
  _debugLog('[iface="wasi:http/[email protected]", function="handle"][Instruction::Return]', {
    funcName: 'handle',
    paramCount: 0,
    postReturn: false
  });
}

Any ideas on how to solve this?

0UserName avatar Oct 03 '25 16:10 0UserName

Hey @0UserName sorry it was hard to use! I think what you want here is a custom instantiation that you can more easily control.

We have some documentation on this but it's clearly not fantastic -- it's in the Jco Book:

https://bytecodealliance.github.io/jco/manual-wasm-instantiation-with-wasi-overrides.html#manual-instantiation-of-a-transpiled-component-with-no-overrides

Also, there are two interfaces to HTTP -- the fetch handler and also wasi:http -- you may find wasi:http easier to manipulate.

This code isn't released yet (it will be soon!), but an upcoming jco-std project will make this easier for projects like Hono. You can preview that code (which does something similar) here:

https://github.com/vados-cosmonic/jco/blob/feat%3Djco-std/packages/jco-std/src/wasi/0.2.x/http/types/request.ts

Viewing the Hono adapter might give you some ideas on ways to convert/adapt.

vados-cosmonic avatar Oct 04 '25 03:10 vados-cosmonic