function
function•3mo ago

Deno.serve() + sharp + streams

Hi, i'd like to read an image, convert it, and respond with the result. I'd like to optimise for performance (request time) and low memory usage as much as possible, which is why i tried to use Streams. Unfortunately, i couldn't get my code to work with Streams and i'm very confused between the JavaScript native Streams, Deno's stdlib, and Node's streams module. Here's my tiny example which works but is using toBuffer() which i think could be optimised using Streams:
import sharp from "npm:sharp";

Deno.serve(async () => new Response(
await sharp("img.jpg").webp().toBuffer()
));
import sharp from "npm:sharp";

Deno.serve(async () => new Response(
await sharp("img.jpg").webp().toBuffer()
));
How can i change this code to use Streams and go as fast as possible?
20 Replies
function
functionOP•3mo ago
I tried so many different options of .pipe and creating Readable and Writable streams using Node built-ins, native, and Deno stdlib that i felt like posting them all would just be spam. I hope it's easier to follow what i'm trying to achieve by giving a working example that simply doesn't use Streams yet.
redstuff
redstuff•3mo ago
You have to convert them to the expected stream of the library, e.g. deno to node streams expected in sharp:
function toNodeReadable(stream: ReadableStream<Uint8Array>): Readable {
const reader = stream.getReader();
return new Readable({
async read() {
try {
const { done, value } = await reader.read();
if (done) {
this.push(null);
} else {
this.push(value);
}
} catch (error) {
this.destroy(error instanceof Error ? error : undefined);
}
},
});
}

toNodeReadable(file.stream()).pipe(sharp().jpeg({ quality: 100 })),
function toNodeReadable(stream: ReadableStream<Uint8Array>): Readable {
const reader = stream.getReader();
return new Readable({
async read() {
try {
const { done, value } = await reader.read();
if (done) {
this.push(null);
} else {
this.push(value);
}
} catch (error) {
this.destroy(error instanceof Error ? error : undefined);
}
},
});
}

toNodeReadable(file.stream()).pipe(sharp().jpeg({ quality: 100 })),
function
functionOP•3mo ago
@redstuff that's the other way around 😄 i need a ReadableStream, not a Nodejs Readable which the sharp object is (it's a Duplex to be more accurate) Deno.serve only supports web standard ReadableStream and not Nodejs streams
redstuff
redstuff•3mo ago
aha, sure - this example was when you have a deno stream you want to stream to a node stream. The result indeed has to be converted back. I can share our attempt, its indeed not perfect:
export function toDenoReadableStream(stream: Readable): ReadableStream<Uint8Array> {
return new ReadableStream({
start(controller) {
stream.on("data", (chunk) => {
controller.enqueue(chunk);
});

stream.on("error", (err) => {
// logger.error(err);
controller.error(err);
});

stream.on("end", () => {
try {
// Interacting with the controller if already "closed" will throw an error
// https://developer.mozilla.org/en-US/docs/Web/API/ReadableStreamDefaultController/error
controller.close();
} catch {
// Ignore error when closing - if already closed?
}
});
},
cancel(reason) {
stream.destroy(reason);
},
});
}
export function toDenoReadableStream(stream: Readable): ReadableStream<Uint8Array> {
return new ReadableStream({
start(controller) {
stream.on("data", (chunk) => {
controller.enqueue(chunk);
});

stream.on("error", (err) => {
// logger.error(err);
controller.error(err);
});

stream.on("end", () => {
try {
// Interacting with the controller if already "closed" will throw an error
// https://developer.mozilla.org/en-US/docs/Web/API/ReadableStreamDefaultController/error
controller.close();
} catch {
// Ignore error when closing - if already closed?
}
});
},
cancel(reason) {
stream.destroy(reason);
},
});
}
function
functionOP•3mo ago
that's pretty much exactly one of the solutions i also came up with, but it's slightly slower than http.createServer (running with node) + sharpObject.pipe(res) that is with a large 20MB input image file and large output (convert to lossless webp) so basically i can't get deno to be any faster than node
redstuff
redstuff•3mo ago
Mhm, probably need some smartness here for it to be faster - above my paygrade. Define slower though?
function
functionOP•3mo ago
i have not tried running the node code with deno, but i'd love to use the deno stdlib instead of node http module slower as in 900ms with node vs 920ms with deno on average (localhost) same sharp settings which are native bindings, so the processing should always take the same time
redstuff
redstuff•3mo ago
I think best attack vector here - is to make sharp deno streams compatible
function
functionOP•3mo ago
i can only explain this difference with the stream conversion node has Readable.toWeb() which is supposed to do exactly what i need but is like 3x slower than any other method and also broken for me (image get served but is blank) that method was added in node v17 but is still experimental
redstuff
redstuff•3mo ago
Ouch - that did not work for us either.
function
functionOP•3mo ago
i have an issue of type question open on the sharp github to maybe figure something out
function
functionOP•3mo ago
GitHub
Converting the Sharp/Node Duplex to web standard ReadableStream · I...
Question about an existing feature What are you trying to achieve? I'm trying to convert a Sharp object as returned by sharp("...").webp() to a web standard ReadableStream<Uint8Arr...
function
functionOP•3mo ago
just for reference
redstuff
redstuff•3mo ago
You should probably shime in here about it not working.~ ...and slower
function
functionOP•3mo ago
i'm not sure which side - deno or sharp - can improve in this combination
redstuff
redstuff•3mo ago
sorry, its there 🙂
function
functionOP•3mo ago
i guess toWeb being slow (and maybe broken?) is on node one solution that i can see is if sharp could output a ReadableStream right away
redstuff
redstuff•3mo ago
I would experment with that... if I had time.
function
functionOP•3mo ago
anyway, thank you for your input
redstuff
redstuff•3mo ago
I will update you later - if I get time to look into this further.

Did you find this page helpful?