From 1b236571edc6ad3a8c0c85d81aa7e13e2436fcf2 Mon Sep 17 00:00:00 2001 From: v1rtl Date: Sat, 24 Jun 2023 11:22:29 +0300 Subject: [PATCH] fix `send` to support binary data and upgrade eta example to 3 --- deno.lock | 21 +++++++--- deps.ts | 2 +- examples/eta/mod.ts | 34 ++++++----------- examples/eta/views/index.eta | 4 +- examples/https/mod.ts | 2 +- extensions/res/send/send.ts | 74 ++++++++++++++++++------------------ tests/modules/send.test.ts | 25 ++++++------ tests/util.test.ts | 2 +- types.ts | 2 +- 9 files changed, 83 insertions(+), 83 deletions(-) diff --git a/deno.lock b/deno.lock index 10522af..940659e 100644 --- a/deno.lock +++ b/deno.lock @@ -1,6 +1,8 @@ { "version": "2", "remote": { + "https://cdn.skypack.dev/-/@tinyhttp/content-disposition@v2.0.9-6OmRjd4Au9rYAIohlWTJ/dist=es2019,mode=imports/optimized/@tinyhttp/content-disposition.js": "86b978e239324154099ed0c88c0ea07c025c8639c4170b94a934e695bdb6ae86", + "https://cdn.skypack.dev/@tinyhttp/content-disposition@2.0.9": "8a3b54ebeb00545bbf43b0a450964516b14f30f4255c2161628f044ca349a25b", "https://deno.land/std@0.106.0/fmt/colors.ts": "d2f8355f00a74404668fc5a1e4a92983ce1a9b0a6ac1d40efbd681cb8f519586", "https://deno.land/std@0.182.0/fmt/colors.ts": "d67e3cd9f472535241a8e410d33423980bec45047e343577554d3356e1f0ef4e", "https://deno.land/std@0.182.0/testing/_diff.ts": "1a3c044aedf77647d6cac86b798c6417603361b66b54c53331b312caeb447aea", @@ -78,6 +80,17 @@ "https://deno.land/x/eta@v2.0.1/render.ts": "c6bdc538cb9c2ffc1593ac7ce0fd55c711a65cd6a909b097c913ea47056badb4", "https://deno.land/x/eta@v2.0.1/storage.ts": "46f762d1c04d9095ed6fb694c0d993e9ae507a69bab469a5a3bd23a5a9a79983", "https://deno.land/x/eta@v2.0.1/utils.ts": "e30a240bded14fde639287c8fb1f826c86c22c50264f4c69a266100307c381a9", + "https://deno.land/x/eta@v3.0.3/src/compile-string.ts": "e919c1a5e2cf460dbbd93047ab3548a630e5eb5c7fa019335c08e95b50e86cd7", + "https://deno.land/x/eta@v3.0.3/src/compile.ts": "146782227ddd5c423d07dedb2be40a2a5643c7429d9a3af5ac9a79c21bf7ae95", + "https://deno.land/x/eta@v3.0.3/src/config.ts": "7e2971ba3ec4b31b5f1ae0ea71957f0a2b868285192be77836d65ca27adb4f90", + "https://deno.land/x/eta@v3.0.3/src/core.ts": "53a9a10c8fc06ce88155a125ffb7db724afb7b4e16d6f646b20a29f27baa2cdf", + "https://deno.land/x/eta@v3.0.3/src/err.ts": "e9732b5f7fe729fed9d67868ff7d87ef1002d45d0a8a4e8f7fb1fa9ec2e2d50b", + "https://deno.land/x/eta@v3.0.3/src/file-handling.ts": "ac54a84c4e73f47629d69725b8557080caf4e03cf34413b4a2a3f3a10c0abb25", + "https://deno.land/x/eta@v3.0.3/src/index.ts": "fa1e18db556462293408c7a455b05b5253aae460c97811f88bb023cdc3f95f62", + "https://deno.land/x/eta@v3.0.3/src/parse.ts": "8cb25b4ccf58ea7f08fe9835f7eb8bf45917d6d743d28d1e25cadc9fc908cb32", + "https://deno.land/x/eta@v3.0.3/src/render.ts": "bfd57548d5fdae11c1c1683ff748783c1f85be3db3c24fcc80ce915692b004dd", + "https://deno.land/x/eta@v3.0.3/src/storage.ts": "c40bd31cdd6f1218c86f70067282709e2f2c7fa7eea67547be950e1ed5d4bd4c", + "https://deno.land/x/eta@v3.0.3/src/utils.ts": "1994c8d228195558fb6785fb00a97cf21a457df598a6ab1945170f7a4b7f00d9", "https://deno.land/x/expect@v0.2.9/expect.ts": "128c60f94ff3f977e2a649463238e403f9bdb8e6ab77e65214c0236bd61b0111", "https://deno.land/x/expect@v0.2.9/matchers.ts": "ba7360b73c5031a22449fa98eb4d5dbe7f256a88dd4c22ccae96dc6c01f0b19c", "https://deno.land/x/expect@v0.2.9/mock.ts": "562d4b1d735d15b0b8e935f342679096b64fe452f86e96714fe8616c0c884914", @@ -109,7 +122,6 @@ "https://deno.land/x/status@0.1.0/codes.ts": "4770829290f22ec25af4db680cc05db6ae2248540e66998637b036fda0717254", "https://deno.land/x/status@0.1.0/maps.ts": "74d51c705716efc58d4110837b988579134ad07da4ed64c99b7443bea43eac95", "https://deno.land/x/status@0.1.0/status.ts": "5252fa9cf5fe13f10d97a273b8461d4ae88ed66ec3e3af90b04775cae224084d", - "https://deno.land/x/superfetch@1.0.4/types.ts": "6f4cc8eb685320002138980dd0893f5eeafe2a9725c8918f8dc21743f99e6b18", "https://deno.land/x/superfetch@1.0.5/deps.ts": "b5dd86d0dfc0d6c5b853bc183657778999c39970d256d67746b1f26f3753cfa6", "https://deno.land/x/superfetch@1.0.5/mod.ts": "1e7401ac3c0827e9a6e87d4d5c0994fa7e9ed4d7f67e0f60423b3f085478a1c5", "https://deno.land/x/superfetch@1.0.5/types.ts": "6f4cc8eb685320002138980dd0893f5eeafe2a9725c8918f8dc21743f99e6b18", @@ -124,11 +136,8 @@ "https://deno.land/x/tincan@1.0.1/src/reporter.ts": "84e09297da18c840076be951c02c749678b75dc89b2575f34db1378cbb509939", "https://deno.land/x/tincan@1.0.1/src/runner.ts": "54e1a0934a52469437bd4655df51f0c9148327d94a2b0d6c175f6f992a697b86", "https://deno.land/x/vary@1.0.0/mod.ts": "e7c452694b21336419f16e0891f8ea503adaafc7bde956fb29e1a86f450a68e6", - "https://esm.sh/@tinyhttp/content-disposition@2.0.9": "e422dc24d87d68690b2006d05fdf533bc9861fb60f44cc252b5de869455624c4", - "https://esm.sh/ipaddr.js@2.0.1": "b35c92d6f96eb8f3696f7bca3b2a9127e0c25c862096988d224d244d12bbf32c", - "https://esm.sh/v126/@tinyhttp/content-disposition@2.0.9/deno/content-disposition.mjs": "ee7f64a29f0d2f838e0b66de805b380e1b65b215af4704633f7767543f297e12", - "https://esm.sh/v126/@tinyhttp/content-disposition@2.0.9/dist/index.d.ts": "6d54da4270da92bf75e444a0d7d54ccbd71fcca5bb69916e3edd696985f985ac", - "https://esm.sh/v126/ipaddr.js@2.0.1/deno/ipaddr.mjs": "774ed6749a35b0e19baf8c37eeadb7972827caf8eb42c67781326c8a55566117", + "https://esm.sh/ipaddr.js@2.0.1": "fcbbbfbe9b3ef0896c04ccf197bf5b293cdc2d4d1c2c5d0f5e899cd31c884cce", + "https://esm.sh/v126/ipaddr.js@2.0.1/denonext/ipaddr.mjs": "89b0e479231c6f0e41e47786b3e84fea90cfa2f7f61789c3574378c4ab275cd8", "https://esm.sh/v126/ipaddr.js@2.0.1/lib/ipaddr.js.d.ts": "0a2a9ccd1ad8d5d9183182641c1d1b36e4912521074a063eaa7a09b973735cc5" } } diff --git a/deps.ts b/deps.ts index 87d89f6..6fa88b8 100644 --- a/deps.ts +++ b/deps.ts @@ -18,7 +18,7 @@ export { escapeHtml } from 'https://deno.land/x/escape_html@1.0.0/mod.ts' export * as base64 from 'https://deno.land/std@0.185.0/encoding/base64.ts' export { parseMediaType } from 'https://deno.land/std@0.185.0/media_types/parse_media_type.ts' export * as mediaTyper from 'https://deno.land/x/media_typer@1.0.1/mod.ts' -export { contentDisposition } from 'https://esm.sh/@tinyhttp/content-disposition@2.0.9' +export { contentDisposition } from 'https://cdn.skypack.dev/@tinyhttp/content-disposition@2.0.9' export { basename, extname } from 'https://deno.land/std@0.185.0/path/mod.ts' export { accepts, diff --git a/examples/eta/mod.ts b/examples/eta/mod.ts index e88b08b..88d1316 100644 --- a/examples/eta/mod.ts +++ b/examples/eta/mod.ts @@ -1,30 +1,20 @@ -import { renderFileAsync } from 'https://deno.land/x/eta@v2.0.1/mod.ts' -import type { EtaConfig } from 'https://deno.land/x/eta@v2.0.1/config.ts' +import { Eta } from 'https://deno.land/x/eta@v3.0.3/src/index.ts' +import type { EtaConfig } from 'https://deno.land/x/eta@v3.0.3/src/config.ts' import { App } from '../../mod.ts' +import { path } from '../../deps.ts' +const eta = new Eta({ + views: path.join(Deno.cwd(), 'views'), +}) const app = new App() -app.engine('eta', renderFileAsync) - -function func() { - return new Promise((resolve) => { - setTimeout(() => { - resolve('HI FROM ASYNC') - }, 20) - }) -} - app.use( - async (_, res) => { - await res.render( - 'index.eta', - { name: 'Eta', func }, - { - renderOptions: { - async: true, - cache: true, - }, - }, + (_, res) => { + res.end( + eta.render( + 'index', + { name: 'Eta' }, + ), ) }, ) diff --git a/examples/eta/views/index.eta b/examples/eta/views/index.eta index 3e31a4a..c8eac20 100644 --- a/examples/eta/views/index.eta +++ b/examples/eta/views/index.eta @@ -1,3 +1 @@ -Hello from <%= it.name %> - -Async func: <%= await it.func() %> \ No newline at end of file +Hello from <%= it.name %> \ No newline at end of file diff --git a/examples/https/mod.ts b/examples/https/mod.ts index 2499d2b..0ee562a 100644 --- a/examples/https/mod.ts +++ b/examples/https/mod.ts @@ -1,5 +1,5 @@ import { App } from '../../app.ts' -import { serveTls } from 'https://deno.land/std@0.185.0/http/server.ts' +import { serveTls } from 'https://deno.land/std@0.192.0/http/server.ts' const app = new App() diff --git a/extensions/res/send/send.ts b/extensions/res/send/send.ts index aab023c..5ae89f4 100644 --- a/extensions/res/send/send.ts +++ b/extensions/res/send/send.ts @@ -3,6 +3,10 @@ import { json } from './json.ts' import { createETag, setCharset } from '../utils.ts' import { end } from './end.ts' +const isBuffer = (body: unknown) => + body instanceof Uint8Array || body instanceof Blob || + body instanceof ArrayBuffer || body instanceof ReadableStream + export const send = < Req extends Request & { fresh?: boolean } = Request & { fresh?: boolean }, Res extends DummyResponse = DummyResponse, @@ -10,39 +14,42 @@ export const send = < async (body: unknown) => { let bodyToSend = body - // in case of object - turn it to json - if (typeof bodyToSend === 'object' && bodyToSend !== null) { + if (isBuffer(body)) { + bodyToSend = body + } else if (typeof body === 'object' && body !== null) { + // in case of object - turn it to json bodyToSend = JSON.stringify(body, null, 2) res._init.headers?.set('Content-Type', 'application/json') - } else { - if (typeof bodyToSend === 'string') { - // reflect this in content-type - const type = res._init.headers?.get('Content-Type') + } else if (typeof body === 'string') { + // reflect this in content-type + const type = res._init.headers.get('Content-Type') - if (type && typeof type === 'string') { - res._init.headers?.set('Content-Type', setCharset(type)) - } else {res._init.headers?.set( - 'Content-Type', - setCharset('text/html'), - )} - } + if (type && typeof type === 'string') { + res._init.headers.set('Content-Type', setCharset(type)) + } else {res._init.headers.set( + 'Content-Type', + setCharset('text/html'), + )} } - // populate ETag let etag: string | undefined if ( - bodyToSend && !res._init.headers?.get('etag') && - (etag = await createETag(bodyToSend as string)) + typeof body === 'string' && !res._init.headers.get('etag') ) { - res._init.headers?.set('etag', etag) + etag = await createETag(bodyToSend as string) + } + { + if (etag) res._init.headers.set('etag', etag!) } + // freshness if (req.fresh) res._init.status = 304 + // strip irrelevant headers if (res._init.status === 204 || res._init.status === 304) { - res._init.headers?.delete('Content-Type') - res._init.headers?.delete('Content-Length') - res._init.headers?.delete('Transfer-Encoding') + res._init.headers.delete('Content-Type') + res._init.headers.delete('Content-Length') + res._init.headers.delete('Transfer-Encoding') bodyToSend = '' } @@ -50,25 +57,18 @@ async (body: unknown) => { return end(res)(bodyToSend as BodyInit) } - if (typeof bodyToSend === 'object') { + if (typeof body === 'object') { if (body == null) { - return end(res)('') - } else if ( - bodyToSend instanceof Uint8Array || bodyToSend instanceof File - ) { - if (!res._init.headers?.get('Content-Type')) { + return end(res)(null) + } else if (isBuffer(body)) { + if (!res._init.headers.get('Content-Type')) { res._init.headers.set('content-type', 'application/octet-stream') } - - return end(res)(bodyToSend) - } else { - return json(res)(bodyToSend) - } - } else { - if (typeof bodyToSend !== 'string') { - bodyToSend = (bodyToSend as string).toString() - } - - return end(res)(bodyToSend as BodyInit) + return end(res)(bodyToSend as BodyInit) + } else return json(res)(bodyToSend) + } + if (typeof bodyToSend !== 'string') { + bodyToSend = (bodyToSend as string).toString() } + return end(res)(bodyToSend as string) } diff --git a/tests/modules/send.test.ts b/tests/modules/send.test.ts index 8f16f01..5ef465c 100644 --- a/tests/modules/send.test.ts +++ b/tests/modules/send.test.ts @@ -3,6 +3,8 @@ import { json, send, sendStatus, status } from '../../extensions/res/mod.ts' import { runServer } from '../util.test.ts' import { App } from '../../mod.ts' +const te = new TextEncoder() + describe('json(body)', () => { it('should send a JSON reply when an object is passed', async () => { const app = runServer((_, res) => { @@ -118,17 +120,18 @@ describe('send(body)', () => { .expectHeader('Content-Type', null) .expectHeader('Transfer-Encoding', null) }) - // it('should set Content-Type to application/octet-stream for buffers if the header hasn\'t been set before', async () => { - // const app = runServer((req, res) => - // send(req, res)(Buffer.from('Hello World', 'utf-8')).end() - // ) - - // const res = await makeFetch(app)('/') - // res.expectHeader( - // 'Content-Type', - // 'application/octet-stream', - // ) - // }) + it('should set Content-Type to application/octet-stream for buffers if the header hasn\'t been set before', async () => { + const app = runServer(async (req, res) => { + const r = await send(req, res)(te.encode('Hello World')) + return new Response(r._body, r._init) + }) + + const res = await makeFetch(app)('/') + res.expectHeader( + 'Content-Type', + 'application/octet-stream', + ) + }) it('should set 304 status for fresh requests', async () => { const etag = 'abc' diff --git a/tests/util.test.ts b/tests/util.test.ts index 2197c18..c03d4db 100644 --- a/tests/util.test.ts +++ b/tests/util.test.ts @@ -3,7 +3,7 @@ import type { THRequest } from '../request.ts' import type { DummyResponse, THResponse } from '../response.ts' import type { AppConstructor, Handler } from '../types.ts' import { makeFetch } from '../dev_deps.ts' -import { ConnInfo } from 'https://deno.land/x/superfetch@1.0.4/types.ts' +import type { ConnInfo } from 'https://deno.land/x/superfetch@1.0.5/types.ts' export const supertest = (app: App) => { const fetch = makeFetch((req, conn) => app.handler(req, conn)) diff --git a/types.ts b/types.ts index 0d79f07..8129e51 100644 --- a/types.ts +++ b/types.ts @@ -61,7 +61,7 @@ type TemplateFunc = ( path: string, locals: Record, opts: TemplateEngineOptions, -) => Promise +) => string | Promise export interface ConnInfo { /** The local address of the connection. */