-
Notifications
You must be signed in to change notification settings - Fork 4
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Added structured logging Added graceful shutdown
- Loading branch information
1 parent
03e17cb
commit d038d9c
Showing
18 changed files
with
361 additions
and
126 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,6 +1,10 @@ | ||
# do not copy/add files below to docker image on-build | ||
.git | ||
.gitignore | ||
.dockerignore | ||
Dockerfile | ||
/examples | ||
node_modules | ||
examples | ||
.git | ||
.github | ||
.gitignore | ||
.dockerignore | ||
.editorconfig | ||
Dockerfile* | ||
README.md | ||
LICENSE |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,43 @@ | ||
# http://editorconfig.org | ||
|
||
root = true | ||
|
||
[*] | ||
charset = utf-8 | ||
end_of_line = lf | ||
indent_size = 4 | ||
indent_style = tab | ||
insert_final_newline = true | ||
tab_width = 4 | ||
trim_trailing_whitespace = true | ||
|
||
# Overwrite specific file types | ||
[*.{yml,yaml}] | ||
indent_size = 2 | ||
indent_style = space | ||
|
||
[*.php] | ||
ij_php_spaces_around_pipe_in_union_type = true | ||
|
||
[*.ts] | ||
ij_typescript_chained_call_dot_on_new_line = true | ||
|
||
# Overwrite for poeditor | ||
[resources/language/*.json] | ||
indent_style = space | ||
indent_size = 4 | ||
|
||
# Overwrite specifics from vendors | ||
[Component/**.php] | ||
indent_size = 4 | ||
indent_style = tab | ||
|
||
# Ignore vendor path | ||
[vendor/**] | ||
root = unset | ||
charset = none | ||
end_of_line = none | ||
indent_style = none | ||
insert_final_newline = none | ||
tab_width = none | ||
trim_trailing_whitespace = none |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,6 +1,41 @@ | ||
FROM surnet/alpine-node-wkhtmltopdf:8.11.3-0.12.5-full-font | ||
FROM oven/bun:1-alpine as base | ||
WORKDIR /usr/src/app | ||
|
||
COPY index.js . | ||
FROM base AS install | ||
# Install all dependencies | ||
RUN mkdir -p /temp/all | ||
COPY package.json bun.lockb /temp/all/ | ||
RUN cd /temp/all && bun install --frozen-lockfile | ||
# Install prod dependencies | ||
RUN mkdir -p /temp/prod | ||
COPY package.json bun.lockb /temp/prod/ | ||
RUN cd /temp/prod && bun install --frozen-lockfile --production | ||
|
||
EXPOSE 8000 | ||
CMD ["node", "index.js"] | ||
FROM base AS prerelease | ||
COPY --from=install /temp/all/node_modules node_modules | ||
COPY . . | ||
|
||
FROM surnet/alpine-wkhtmltopdf:3.19.0-0.12.6-small as wkhtmltopdf | ||
|
||
FROM base AS release | ||
RUN apk add --no-cache \ | ||
libstdc++ \ | ||
libx11 \ | ||
libxrender \ | ||
libxext \ | ||
libssl3 \ | ||
ca-certificates \ | ||
fontconfig \ | ||
freetype \ | ||
ttf-dejavu \ | ||
ttf-droid \ | ||
ttf-freefont \ | ||
ttf-liberation | ||
COPY --from=wkhtmltopdf /bin /usr/local/bin | ||
COPY --from=install /temp/prod/node_modules node_modules | ||
COPY --from=prerelease /usr/src/app/src . | ||
COPY --from=prerelease /usr/src/app/package.json . | ||
|
||
USER bun | ||
EXPOSE 8000/tcp | ||
CMD ["bun", "run", "index.ts"] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
telemetry = false |
This file was deleted.
Oops, something went wrong.
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,2 +1,2 @@ | ||
#!/usr/bin/env bash | ||
curl http://localhost:8000 -d '<h1>Hello world from SHELL</h1>' > test.pdf | ||
curl http://localhost:8000 -H 'Content-Type: text/html' -d '<html><body><h1>Hello world from SHELL</h1></body></html>' > "$(dirname $0)/test.pdf" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
{ | ||
"name": "html-pdf-export", | ||
"module": "src/index.ts", | ||
"type": "module", | ||
"dependencies": { | ||
"nanoid": "^5.0.6", | ||
"pino": "^8.19.0" | ||
}, | ||
"devDependencies": { | ||
"@types/bun": "latest" | ||
}, | ||
"peerDependencies": { | ||
"typescript": "^5.0.0" | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
import { spawn, which } from 'bun'; | ||
|
||
export type HtmlToPdfClient = (req: Request, outputPath: string) => Promise<void>; | ||
export const htmlToPdfClient: HtmlToPdfClient = async (req, outputPath) => { | ||
const bin = which('wkhtmltopdf'); | ||
if (bin === null) { | ||
throw new Error('Missing HTML to PDF binary'); | ||
} | ||
const proc = spawn( | ||
['wkhtmltopdf', '--quiet', '--print-media-type', '--no-outline', '-', outputPath], | ||
{stdin: req, stderr: 'pipe'}, | ||
); | ||
|
||
const exitCode = await proc.exited; | ||
const errors: string = await Bun.readableStreamToText(proc.stderr); | ||
if (errors) { | ||
throw new Error(errors); | ||
} | ||
|
||
// if no errors but unsuccessful exit code, throw a generic error | ||
if (exitCode !== 0) { | ||
throw new Error(`Failed to convert HTML to PDF, the process exited with code ${exitCode}`); | ||
} | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
import { createServer } from './server.ts'; | ||
import { trapShutdown } from './shutdown.ts'; | ||
|
||
const server = createServer(); | ||
|
||
trapShutdown(async () => server.stop()); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
import pino, { type Logger as PinoLogger } from 'pino'; | ||
|
||
export const loggerUsingPino = () => pino({ | ||
name: 'html-pdf-export', | ||
}); | ||
|
||
export type Logger = () => PinoLogger; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,63 @@ | ||
import { write } from 'bun'; | ||
import { afterEach, beforeEach, expect, mock, test } from 'bun:test'; | ||
import pino, { type Logger, type LoggerExtras } from 'pino'; | ||
import type { HtmlToPdfClient } from './html-to-pdf-client.ts'; | ||
import { createServer } from './server.ts'; | ||
|
||
mock.module('nanoid', () => ({nanoid: () => 'fake-random-id'})); | ||
|
||
const port = 0; // 0 means give a random unassigned port | ||
const host = 'http://localhost'; | ||
const method = 'POST'; | ||
const body = "<h1>Hello world</h1>"; | ||
const headers = {'content-type': 'text/html'}; | ||
const logger = { | ||
info: mock() as pino.LogFn, | ||
error: mock() as pino.LogFn, | ||
child: mock() as LoggerExtras['child'], | ||
} as Logger; | ||
const htmlToPdfClient: HtmlToPdfClient = async (req, outputPath) => { | ||
const html = await req.text(); | ||
await write(outputPath, html); | ||
}; | ||
|
||
let server: ReturnType<typeof createServer>; | ||
beforeEach(() => server = createServer({port, htmlToPdfClient, logger: () => logger})); | ||
afterEach(() => server.stop()); | ||
|
||
test('logs request id', async () => { | ||
await server.fetch(new Request(host)); | ||
expect(logger.child).toHaveBeenCalledWith({requestId: 'fake-random-id'}); | ||
}); | ||
|
||
const invalidRequestMethods = ['GET', 'HEAD', 'PUT', 'DELETE', 'CONNECT', 'OPTIONS', 'TRACE', 'PATCH']; | ||
test.each(invalidRequestMethods)('cannot do %s requests', async method => { | ||
const res = await server.fetch(new Request(host, {method})); | ||
expect(res.status).toBe(405); | ||
expect(logger.error).toHaveBeenCalledWith('Invalid request method'); | ||
}); | ||
|
||
test('requires a request body', async () => { | ||
const res = await server.fetch(new Request(host, {method})); | ||
expect(res.status).toBe(400); | ||
expect(logger.error).toHaveBeenCalledWith('Missing request body'); | ||
}); | ||
|
||
test('requires a content-type request header', async () => { | ||
const res = await server.fetch(new Request(host, {method, body})); | ||
expect(res.status).toBe(400); | ||
expect(logger.error).toHaveBeenCalledWith('Missing content-type request header'); | ||
}); | ||
|
||
test('requires a request with text/html content-type header', async () => { | ||
const res = await server.fetch(new Request(host, {method, body, headers: {'content-type': ''}})); | ||
expect(res.status).toBe(400); | ||
expect(logger.error).toHaveBeenCalledWith('Invalid content-type request header'); | ||
}); | ||
|
||
test('success', async () => { | ||
const res = await server.fetch(new Request(host, {method, body, headers})); | ||
expect(res.status).toBe(200); | ||
expect(await res.text()).toBe(body); | ||
expect(res.headers.get('content-type')).toBe('application/pdf'); | ||
}); |
Oops, something went wrong.