Skip to content

Instantly share code, notes, and snippets.

@robbertvanginkel
Last active April 12, 2025 15:18
Show Gist options
  • Save robbertvanginkel/d9abba2526fc8cfe5eca700888ed27f3 to your computer and use it in GitHub Desktop.
Save robbertvanginkel/d9abba2526fc8cfe5eca700888ed27f3 to your computer and use it in GitHub Desktop.
gRPC on workers
// Turn on gRPC for domain using
// https://developers.cloudflare.com/support/network/understanding-cloudflare-grpc-support/#enable-grpc, then CF
// rewrites grpc requests to grpc-web (like a reverse envoy filter, see https://blog.cloudflare.com/road-to-grpc/#converting-to-http-1-1)
// which can be handled by workers. Does not work with workers.dev.
export default {
async fetch(request, env, context) {
// Use a stream so CF doesn't add content-lenght
// which would prevent grpc-web -> grpc conversion.
const { readable, writable } = new TransformStream();
let writer = writable.getWriter();
let enc = new TextEncoder();
let responseString = enc.encode(`request content type was ${request.headers.get('content-type')}`);
context.waitUntil(
writer.write(envelope(new Uint8Array([0x0a /* field 1 varint tag */, ...uVarInt(responseString.length), ...responseString])))
);
context.waitUntil(
new Promise(resolve => setTimeout(resolve, 5000)).then(() => {
// Note: no space between header and value, CF grpc-web -> grpc
// conversion inserts one (but really shouldn't).
writer.write(envelope(enc.encode("Grpc-Status:9\r\nGrpc-Message:ohoh\r\n"), true))
}).then(() => writer.close())
);
return new Response(readable, {
status: 200,
headers: new Headers({
'content-type': 'application/grpc-web',
})
})
},
}
// poor mans varint encoding for pb message
function uVarInt(value) {
let buf = [];
while (value > 0x7f) {
buf.push((value & 0x7f) | 0x80);
value = value >>> 7;
}
buf.push(value);
return buf;
}
// write a grpc-web envelope
function envelope(bytes, trailers = false) {
let size = Uint8Array.of(
(bytes.length & 0xff000000) >> 24,
(bytes.length & 0x00ff0000) >> 16,
(bytes.length & 0x0000ff00) >> 8,
(bytes.length & 0x000000ff) >> 0);
return new Uint8Array([trailers ? 0x80 : 0x00, ...size, ...bytes]);
}
@rikka0w0
Copy link

Will this work if my worker is using the workers.dev domain?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment