Reputation: 59345
Below in my first example is a simple web server using deno's new serve
api. If you open up curl and run curl -X POST -H "Content-Type: application/json" http://localhost:8000
quickly in two tabs. You'll see the expected result
request made ✅
request made ✅
request served 👍
request served 👍
Here's the code that outputs the logs above:
import { reservedPath } from "https://gist.githubusercontent.com/reggi/f989eb2b395fa348595a2ec2a7099e95/raw/bf0e75061f2e2454a244cef019ffac2cf1fc38fc/reserved_path.ts";
const file = await Deno.readFile('./image.png')
function delay(ms: number) {
return new Promise(resolve => setTimeout(resolve, ms));
}
Deno.serve(async (_req) => {
console.log(`request made ✅`)
if (reservedPath(_req.url)) {
return new Response("Not Found", { status: 404 });
}
const headers = new Headers();
headers.set("Content-Type", "image/png");
await delay(300)
console.log(`request served 👍`)
return new Response(file, { headers });
})
However I'm facing an issue specifically with a wasm build of ImageMagick magick-wasm which is used wrapped in imagemagick-deno for some convenience files and converting the codebase to use .ts
files directly.
Here's a working server, when I open up http://localhost:8000/ I get the image, however when I run curl I get the output below. Which means to me, this code is blocking the server. It means that users on the server block each other. This WASM ImageMagick library does seem to allow parallel non-blocking code (as long as it doesn't return) as seen here with that knowledge I tried to force a server to stream an empty readable stream, then use update the stream with the image when it was finished processing, this didn't work either.
request made ✅
request served 👍
request made ✅
request served 👍
Here's the code:
import { reservedPath } from "https://gist.githubusercontent.com/reggi/f989eb2b395fa348595a2ec2a7099e95/raw/bf0e75061f2e2454a244cef019ffac2cf1fc38fc/reserved_path.ts";
import { CompositeOperator, Gravity, ImageMagick, Point, initializeImageMagick } from "https://deno.land/x/[email protected]/mod.ts";
const file = await Deno.readFile('./image.png')
await initializeImageMagick()
Deno.serve((_req) => {
console.log(`request made ✅`)
if (reservedPath(_req.url)) {
return new Response("Not Found", { status: 404 });
}
const headers = new Headers();
headers.set("Content-Type", "image/png");
const i = ImageMagick.read(file, (v) => {
v.compositeGravity(v, Gravity.North, CompositeOperator.Overlay, new Point(-20, -20))
v.compositeGravity(v, Gravity.North, CompositeOperator.Overlay, new Point(20, 20))
return v.write(g => g)
})
console.log(`request served 👍`)
return new Response(i, { headers });
})
ImageMagick.read
requires a callback, it seems it returns whatever it returns, so it returns a promise if I return a promise from within. It's kind of both sync and async at the same time. It's kind of confusing to ration about. I've tried many combinations of making different callbacks within it async and waiting various stages of that code, I'm not sure that's the issue. At first glance if I were you I'd be confused by that part, but I think it's a red-herring I think there's something deeper going on.
Is there something specific within Deno.serve
causing this? Is this something known that WASM code does?
Upvotes: 0
Views: 274
Reputation: 59345
As of right now (JUL 28 2023) this does not work on deno deploy.
server.ts
import { reservedPath } from "https://gist.githubusercontent.com/reggi/f989eb2b395fa348595a2ec2a7099e95/raw/bf0e75061f2e2454a244cef019ffac2cf1fc38fc/reserved_path.ts";
const file = await Deno.readFile('./image.png')
Deno.serve((_req) => {
console.log(`request made ✅`)
if (reservedPath(_req.url)) {
return new Response("Not Found", { status: 404 })
}
const headers = new Headers();
headers.set("Content-Type", "image/png");
const worker = new Worker(new URL("./worker.ts", import.meta.url).href, {
type: "module",
name: "worker-" + (100 * Math.random()).toFixed(),
});
worker.postMessage({ task: { byteArray: file } });
// Wait for a response from the worker to be fulfilled
// Fulfill the promise when the mesage is passed.
const result = new Promise((resolve, reject) => {
worker.onmessage = (msg) => {
resolve(msg.data);
};
worker.onerror = (err) => {
err.preventDefault();
reject(err.message);
};
});
return result.then((output: any) => {
console.log(`request served 👍`)
console.log({ output })
return new Response(output, { headers });
})
.catch((reason) => {
console.log("Caught reason");
return Response.json({
reason,
}, {
status: 400,
});
})
.finally(() => worker.terminate());
});
worker.ts
/// <reference no-default-lib="true" />
/// <reference lib="deno.worker" />
import { CompositeOperator, Gravity, ImageMagick, Point, initializeImageMagick } from "https://deno.land/x/[email protected]/mod.ts";
type WorkerParams = {
byteArray: Uint8Array
}
await initializeImageMagick()
console.log("worker starting");
self.onmessage = (evt) => {
const { task }: { task: WorkerParams } = evt.data;
console.log("worker received task", task);
return ImageMagick.read(task.byteArray, (v) => {
v.compositeGravity(v, Gravity.North, CompositeOperator.Overlay, new Point(-20, -20))
v.compositeGravity(v, Gravity.North, CompositeOperator.Overlay, new Point(20, 20))
return v.write(g => {
return self.postMessage(g);
})
})
};
Upvotes: 0