Skip to content
This repository was archived by the owner on Jul 6, 2025. It is now read-only.

Commit 066b651

Browse files
Craig DoremusCraig Doremus
authored andcommitted
Merge branch 'master' into add-test-redirect. The --location flag has been added to the test run for redirect_test.ts
2 parents 4e0fec6 + 1d58b17 commit 066b651

File tree

11 files changed

+167
-104
lines changed

11 files changed

+167
-104
lines changed

CONTRIBUTING.md

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -22,16 +22,16 @@ You will need [Deno](https://deno.land/) 1.8+.
2222

2323
```bash
2424
# ssr/development with HMR
25-
ALEPH_DEV=true deno run -A --import-map=./import_map.json cli.ts dev ./examples/hello-world -L debug
25+
ALEPH_DEV=true deno run -A --unstable --import-map=./import_map.json cli.ts dev ./examples/hello-world -L debug
2626

2727
# ssr/production
28-
ALEPH_DEV=true deno run -A --import-map=./import_map.json cli.ts start ./examples/hello-world -L debug
28+
ALEPH_DEV=true deno run -A --unstable --import-map=./import_map.json cli.ts start ./examples/hello-world -L debug
2929

3030
# ssg
31-
ALEPH_DEV=true deno run -A --import-map=./import_map.json cli.ts build ./examples/hello-world -L debug
31+
ALEPH_DEV=true deno run -A --unstable --import-map=./import_map.json cli.ts build ./examples/hello-world -L debug
3232

3333
# run all tests
34-
deno test -A --location=http://127.0.0.1 --import-map=./import_map.json
34+
deno test -A --unstable --location=http://127.0.0.1 --import-map=./import_map.json
3535
```
3636

3737
## Project Structure

cli/build.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { Application } from '../server/mod.ts'
1+
import { Application } from '../server/app.ts'
22

33
export const helpMessage = `
44
Usage:

cli/dev.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { getFlag, parsePortNumber } from '../server/helper.ts'
2-
import { Application, serve } from '../server/mod.ts'
2+
import { Application } from '../server/app.ts'
3+
import { serve } from '../server/stdserver.ts'
34

45
export const helpMessage = `
56
Usage:

cli/start.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { getFlag, parsePortNumber } from '../server/helper.ts'
2-
import { Application, serve } from '../server/mod.ts'
2+
import { Application } from '../server/app.ts'
3+
import { serve } from '../server/stdserver.ts'
34
import log from '../shared/log.ts'
45

56
export const helpMessage = `

server/api.ts

Lines changed: 8 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,11 @@ import type { MultipartFormData } from 'https://deno.land/[email protected]/mime/multip
44
import { MultipartReader } from 'https://deno.land/[email protected]/mime/multipart.ts'
55
import log from '../shared/log.ts'
66
import type { APIRequest, ServerRequest, ServerResponse } from '../types.ts'
7-
8-
let brotli: ((data: Uint8Array) => Uint8Array) | null = null
9-
let gzip: ((data: Uint8Array) => Uint8Array) | null = null
7+
import compress from './compress.ts'
108

119
type Response = {
1210
status: number
1311
headers: Headers
14-
compress: boolean
1512
done: boolean
1613
}
1714

@@ -22,7 +19,7 @@ export class Request implements APIRequest {
2219
#cookies: ReadonlyMap<string, string>
2320
#resp: Response
2421

25-
constructor(req: ServerRequest, params: Record<string, string>, query: URLSearchParams, compress: boolean = true) {
22+
constructor(req: ServerRequest, params: Record<string, string>, query: URLSearchParams) {
2623
this.#req = req
2724
this.#params = params
2825
this.#query = query
@@ -39,7 +36,6 @@ export class Request implements APIRequest {
3936
headers: new Headers({
4037
Server: 'Aleph.js',
4138
}),
42-
compress,
4339
done: false
4440
}
4541
}
@@ -53,7 +49,7 @@ export class Request implements APIRequest {
5349
}
5450

5551
get hostname(): string {
56-
return (this.#req.conn.remoteAddr as Deno.NetAddr).hostname;
52+
return (this.#req.conn.remoteAddr as Deno.NetAddr).hostname
5753
}
5854

5955
get headers(): Headers {
@@ -172,39 +168,12 @@ export class Request implements APIRequest {
172168
contentType = 'text/plain; charset=utf-8'
173169
this.#resp.headers.set('Content-Type', contentType)
174170
}
175-
if (this.#resp.compress) {
176-
let shouldCompress = false
177-
if (contentType) {
178-
if (contentType.startsWith('text/')) {
179-
shouldCompress = true
180-
} else if (/^application\/(javascript|json|xml|wasm)/i.test(contentType)) {
181-
shouldCompress = true
182-
} else if (/^image\/svg\+xml/i.test(contentType)) {
183-
shouldCompress = true
184-
}
185-
}
186-
if (shouldCompress && body.length > 1024) {
187-
const ae = this.headers.get('accept-encoding') || ''
188-
if (ae.includes('br')) {
189-
this.#resp.headers.set('Vary', 'Origin')
190-
this.#resp.headers.set('Content-Encoding', 'br')
191-
if (brotli === null) {
192-
const { compress } = await import('https://deno.land/x/[email protected]/mod.ts')
193-
brotli = compress
194-
}
195-
body = brotli(body)
196-
} else if (ae.includes('gzip')) {
197-
this.#resp.headers.set('Vary', 'Origin')
198-
this.#resp.headers.set('Content-Encoding', 'gzip')
199-
if (gzip === null) {
200-
const denoflate = await import('https://deno.land/x/[email protected]/mod.ts')
201-
gzip = (data: Uint8Array) => denoflate.gzip(data, undefined)
202-
}
203-
body = gzip(body)
204-
}
205-
}
171+
if (contentType) {
172+
body = compress.apply(this.#req, this.#resp, contentType, body)
173+
}
174+
if (!this.#resp.headers.has('Date')) {
175+
this.#resp.headers.set('Date', (new Date).toUTCString())
206176
}
207-
this.#resp.headers.set('Date', (new Date).toUTCString())
208177
this.#resp.done = true
209178
try {
210179
await this.respond({

server/compress.ts

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
import type { ServerRequest, ServerResponse } from '../types.ts'
2+
3+
const brotliMod = 'https://deno.land/x/[email protected]/mod.ts'
4+
const flateMod = 'https://deno.land/x/[email protected]/mod.ts'
5+
6+
class Compression {
7+
#brotli: ((data: Uint8Array) => Uint8Array) | null = null
8+
#gzip: ((data: Uint8Array) => Uint8Array) | null = null
9+
#ready: boolean = false
10+
11+
async init() {
12+
if (this.#brotli === null) {
13+
const { compress } = await import(brotliMod)
14+
this.#brotli = compress
15+
}
16+
if (this.#gzip === null) {
17+
const denoflate = await import(flateMod)
18+
this.#gzip = (data: Uint8Array) => denoflate.gzip(data, undefined)
19+
}
20+
this.#ready = true
21+
}
22+
23+
apply(req: ServerRequest, resp: ServerResponse, contentType: string, content: Uint8Array): Uint8Array {
24+
if (!this.#ready) {
25+
return content
26+
}
27+
28+
let shouldCompress = false
29+
if (contentType) {
30+
if (contentType.startsWith('text/')) {
31+
shouldCompress = true
32+
} else if (/^application\/(javascript|json|xml|wasm)/i.test(contentType)) {
33+
shouldCompress = true
34+
} else if (/^image\/svg\+xml/i.test(contentType)) {
35+
shouldCompress = true
36+
}
37+
}
38+
39+
if (shouldCompress && content.length > 1024) {
40+
const ae = req.headers.get('accept-encoding') || ''
41+
if (ae.includes('br') && this.#brotli !== null) {
42+
resp.headers?.set('Vary', 'Origin')
43+
resp.headers?.set('Content-Encoding', 'br')
44+
return this.#brotli(content)
45+
} else if (ae.includes('gzip') && this.#gzip !== null) {
46+
resp.headers?.set('Vary', 'Origin')
47+
resp.headers?.set('Content-Encoding', 'gzip')
48+
return this.#gzip(content)
49+
}
50+
}
51+
52+
return content
53+
}
54+
}
55+
56+
export default new Compression()

server/config.ts

Lines changed: 15 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -147,15 +147,22 @@ export async function loadAndUpgradeImportMap(workingDir: string): Promise<Impor
147147
for (const filename of Array.from(['import_map', 'import-map', 'importmap']).map(name => `${name}.json`)) {
148148
importMapFile = join(workingDir, filename)
149149
if (existsFileSync(importMapFile)) {
150-
const data = JSON.parse(await Deno.readTextFile(importMapFile))
151-
const imports: Record<string, string> = toPlainStringRecord(data.imports)
152-
const scopes: Record<string, Record<string, string>> = {}
153-
if (util.isPlainObject(data.scopes)) {
154-
Object.entries(data.scopes).forEach(([scope, imports]) => {
155-
scopes[scope] = toPlainStringRecord(imports)
156-
})
150+
try {
151+
const data = JSON.parse(await Deno.readTextFile(importMapFile))
152+
const imports: Record<string, string> = toPlainStringRecord(data.imports)
153+
const scopes: Record<string, Record<string, string>> = {}
154+
if (util.isPlainObject(data.scopes)) {
155+
Object.entries(data.scopes).forEach(([scope, imports]) => {
156+
scopes[scope] = toPlainStringRecord(imports)
157+
})
158+
}
159+
Object.assign(importMap, { imports, scopes })
160+
} catch (e) {
161+
log.error(`invalid '${filename}':`, e.message)
162+
if (!confirm('Continue?')) {
163+
Deno.exit(1)
164+
}
157165
}
158-
Object.assign(importMap, { imports, scopes })
159166
break
160167
}
161168
}

server/mod.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,4 @@
11
export * from './app.ts'
22
export * from './server.ts'
3+
export * from './stdserver.ts'
4+
export * from './oak.ts'

server/oak.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
import type { Middleware, Context } from 'https://deno.land/x/oak/mod.ts'
2+
import { Application } from './app.ts'
3+
import { Server } from './server.ts'
4+
5+
/** Create an oak middleware for Aleph server. */
6+
export function alephOak(app: Application): Middleware {
7+
const server = new Server(app)
8+
9+
return (ctx: Context) => {
10+
const req = ctx.request as any
11+
server.handle(req.originalRequest || req.serverRequest)
12+
ctx.respond = false
13+
}
14+
}

server/server.ts

Lines changed: 3 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
import { join } from 'https://deno.land/[email protected]/path/mod.ts'
2-
import { serve as stdServe, serveTLS } from 'https://deno.land/[email protected]/http/server.ts'
32
import { acceptWebSocket, isWebSocketCloseEvent } from 'https://deno.land/[email protected]/ws/mod.ts'
43
import { trimModuleExt } from '../framework/core/module.ts'
54
import { rewriteURL } from '../framework/core/routing.ts'
@@ -28,10 +27,10 @@ export class Server {
2827
}
2928

3029
const app = this.#app
31-
const { basePath, compress, headers, rewrites } = app.config
30+
const { basePath, headers, rewrites } = app.config
3231
const url = rewriteURL(r.url, basePath, rewrites)
3332
const pathname = decodeURI(url.pathname)
34-
const req = new Request(r, {}, url.searchParams, !app.isDev && compress)
33+
const req = new Request(r, {}, url.searchParams)
3534

3635
// set custom headers
3736
for (const key in headers) {
@@ -158,7 +157,7 @@ export class Server {
158157
const [{ params, query }, { jsFile, hash }] = route
159158
const { default: handle } = await import(`file://${jsFile}#${hash.slice(0, 6)}`)
160159
if (util.isFunction(handle)) {
161-
await handle(new Request(req, params, query, !app.isDev && compress))
160+
await handle(new Request(req, params, query))
162161
} else {
163162
req.status(500).json({ status: 500, message: 'bad api handler' })
164163
}
@@ -186,49 +185,3 @@ export class Server {
186185
}
187186
}
188187
}
189-
190-
/** Options for creating a standard Aleph server. */
191-
export type ServeOptions = {
192-
/** The Aleph Server Application to serve. */
193-
app: Application
194-
/** The port to listen on. */
195-
port: number
196-
/** A literal IP address or host name that can be resolved to an IP address.
197-
* If not specified, defaults to `0.0.0.0`. */
198-
hostname?: string
199-
/** Server certificate file. */
200-
certFile?: string
201-
/** Server public key file. */
202-
keyFile?: string
203-
}
204-
205-
/** Create a standard Aleph server. */
206-
export async function serve({ app, port, hostname, certFile, keyFile }: ServeOptions) {
207-
const server = new Server(app)
208-
await app.ready
209-
210-
while (true) {
211-
try {
212-
let s: AsyncIterable<ServerRequest>
213-
if (certFile && keyFile) {
214-
s = serveTLS({ port, hostname, certFile, keyFile })
215-
} else {
216-
s = stdServe({ port, hostname })
217-
}
218-
log.info(`Server ready on http://${hostname || 'localhost'}:${port}${app.config.basePath}`)
219-
for await (const r of s) {
220-
server.handle(r)
221-
}
222-
} catch (err) {
223-
if (err instanceof Deno.errors.AddrInUse) {
224-
if (!app.isDev) {
225-
log.fatal(`port ${port} already in use!`)
226-
}
227-
log.warn(`port ${port} already in use, try ${port + 1}...`)
228-
port++
229-
} else {
230-
log.fatal(err.message)
231-
}
232-
}
233-
}
234-
}

0 commit comments

Comments
 (0)