trpc-uwebsockets
Version:
tRPC adapter for uWebSockets.js server
311 lines (246 loc) • 7.78 kB
text/typescript
/* eslint-disable @typescript-eslint/no-unused-vars */
import uWs from 'uWebSockets.js';
import { describe, expect, test } from 'vitest';
import {
decorateHttpResponse,
uWsSendResponse,
uWsSendResponseStreamed,
} from './fetchCompat';
// source: packages/server/src/adapters/node-http/incomingMessageToRequest.test.ts
function createServer(opts: { maxBodySize: number | null }) {
const app = uWs.App();
app.get('/empty', async (res, _req) => {
const resDecorated = decorateHttpResponse(res);
res.onAborted(() => {
resDecorated.aborted = true;
});
const resFetch = new Response('', {
status: 200,
});
await uWsSendResponseStreamed(resFetch, resDecorated);
});
app.get('/regular', async (res, _req) => {
const resDecorated = decorateHttpResponse(res);
res.onAborted(() => {
resDecorated.aborted = true;
});
const headers = new Headers();
headers.append('content-type', 'vi/test');
headers.append('set-cookie', 'one=1');
headers.append('set-cookie', 'two=2');
const resFetch = new Response('hello world', {
status: 200,
statusText: '200 OK',
headers: headers,
});
await uWsSendResponse(resDecorated, resFetch);
});
app.get('/streamed', async (res, _req) => {
const resDecorated = decorateHttpResponse(res);
res.onAborted(() => {
resDecorated.aborted = true;
});
const headers = new Headers();
headers.append('content-type', 'vi/test');
headers.append('set-cookie', 'one=1');
headers.append('set-cookie', 'two=2');
const fetchRes = new Response('hello world', {
status: 200,
statusText: '200 OK',
headers: headers,
});
await uWsSendResponseStreamed(fetchRes, resDecorated);
});
app.get('/slow/:size/:count/:sleepMs', async (res, req) => {
const resDecorated = decorateHttpResponse(res);
res.onAborted(() => {
resDecorated.aborted = true;
});
const size = parseInt(req.getParameter('size')!);
const count = parseInt(req.getParameter('count')!);
const sleepMs = parseInt(req.getParameter('sleepMs')!);
const stream = new ReadableStream({
start(controller) {
let i = 0;
const enqueueChunk = () => {
if (i < count) {
// console.log('loop i', i, 'count', count, 'sleepMs', sleepMs);
controller.enqueue('A'.repeat(size));
i++;
setTimeout(enqueueChunk, sleepMs);
} else {
// console.log('loop closing!', 'count', count);
controller.close();
}
};
enqueueChunk();
},
cancel() {
console.log('SLOW RESPONSE cancel() was called');
console.log('doing nothing for now');
// stop = true;
},
});
const fetchRes = new Response(stream, {
status: 200,
});
await uWsSendResponseStreamed(fetchRes, resDecorated);
});
let socket: uWs.us_listen_socket | false | null = null;
app.listen('0.0.0.0', 0, (token) => {
socket = token;
});
if (!socket) {
throw new Error('could not make a socket');
}
const port = uWs.us_socket_local_port(socket);
// console.log('Listening to port ' + port);
return {
async close() {
if (!socket) {
throw new Error('could not close socket as socket is already closed');
}
uWs.us_listen_socket_close(socket);
socket = null;
},
fetch: async (
opts: RequestInit & {
path?: string;
}
) => {
return await fetch(`http://localhost:${port}${opts.path ?? ''}`, {
...opts,
});
},
};
}
describe('response', () => {
test('empty body', async () => {
const server = createServer({ maxBodySize: null });
const res = await server.fetch({
path: '/empty',
method: 'GET',
});
expect(res.ok).toBe(true);
expect(res.status).toBe(200);
expect((await res.text()).length).toBe(0);
await server.close();
});
test('regular', async () => {
const server = createServer({ maxBodySize: null });
const res = await server.fetch({
path: '/regular',
method: 'GET',
});
expect(res.ok).toBe(true);
expect(res.status).toBe(200);
expect(res.headers.get('content-type')).toBe('vi/test');
expect(res.headers.get('set-cookie')).toBe('one=1, two=2');
expect(res.headers.has('content-length')).toBe(true);
await server.close();
});
test('streamed empty', async () => {
const server = createServer({ maxBodySize: null });
const res = await server.fetch({
path: '/streamed',
method: 'GET',
});
expect(res.ok).toBe(true);
expect(res.status).toBe(200);
expect(res.headers.get('content-type')).toBe('vi/test');
expect(res.headers.get('set-cookie')).toBe('one=1, two=2');
expect(res.headers.get('transfer-encoding')).toBe('chunked');
await server.close();
});
test('streamed large ', async () => {
const server = createServer({ maxBodySize: null });
const size = 2 ** 20;
const count = 10;
const timeMs = 0;
// console.time('large-streamed');
const res = await server.fetch({
path: `/slow/${size}/${count}/${timeMs}`,
method: 'GET',
});
expect(res.ok).toBe(true);
expect(res.status).toBe(200);
expect((await res.text()).length).toBe(size * count);
// console.timeEnd('large-streamed');
await server.close();
});
test('streamed slow ', async () => {
const server = createServer({ maxBodySize: null });
const size = 10;
const count = 5;
const sleepMs = 20;
const res = await server.fetch({
path: `/slow/${size}/${count}/${sleepMs}`,
method: 'GET',
});
expect(res.ok).toBe(true);
expect(res.status).toBe(200);
expect((await res.text()).length).toBe(size * count);
});
test('aborted slow', async () => {
expect.assertions(1);
const server = createServer({ maxBodySize: null });
const size = 100;
const count = 5;
const sleepMs = 20;
const controller = new AbortController();
try {
const res = await server.fetch({
path: `/slow/${size}/${count}/${sleepMs}`,
method: 'GET',
signal: controller.signal,
});
setTimeout(() => {
controller.abort();
}, 40);
// important to actually listen for the body
// if this is missing then the test is over and AbortError is not triggered
await res.text();
} catch (err: any) {
expect(err.name).toBe('AbortError');
}
});
test('aborted request', async () => {
expect.assertions(1);
const server = createServer({ maxBodySize: null });
const size = 2 ** 22;
const controller = new AbortController();
controller.abort(); // start with aborted signal already
try {
const res = await server.fetch({
path: `/large/${size}`,
method: 'GET',
signal: controller.signal,
});
await res.text();
} catch (err: any) {
expect(err.name).toBe('AbortError');
}
await server.close();
});
test('aborted request in flight', async () => {
expect.assertions(1);
const server = createServer({ maxBodySize: null });
const size = 2 ** 24;
const controller = new AbortController();
controller.abort(); // start with aborted signal already
try {
const res = await server.fetch({
path: `/large/${size}`,
method: 'GET',
signal: controller.signal,
});
setTimeout(() => {
controller.abort(); // start with aborted signal already
}, 5);
await res.text();
} catch (err: any) {
expect(err.name).toBe('AbortError');
}
await server.close();
});
});