This repository was archived by the owner on Jul 7, 2020. It is now read-only.
Repository navigation
Expand file tree
/
Copy pathtemplate.ts
More file actions
167 lines (140 loc) · 5.11 KB
/
Copy pathtemplate.ts
File metadata and controls
167 lines (140 loc) · 5.11 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
import { Response, Request, NextFunction } from 'express';
import * as phantom from 'phantom';
import { IWebPageResponse } from 'phantom';
import * as os from 'os';
import * as crypto from 'crypto';
import * as path from 'path';
import * as Ajv from 'ajv';
import * as puppeteer from 'puppeteer';
import { ClientError, ServerError } from '../models/Errors';
import TemplateParameters = Definitions.TemplateParameters;
export default class TemplateController {
schema: any;
constructor(schema: any) {
this.schema = schema;
}
/**
* POST /html
* Render template as HTML
*/
async renderHTML(req: Request, res: Response, next: NextFunction) {
// Validate payload: Uses swagger model definition
const ajv = new Ajv();
const valid = ajv.validate(this.schema.definitions.TemplateParameters, req.body);
if (!valid) {
return next(new ClientError('Invalid request payload: ' + ajv.errorsText()));
}
// Render template
const params = <TemplateParameters> req.body;
res.render('sample.html.mustache', params);
}
/**
* POST /pdf/phantom
* Render template as PDF with PhantomJS
*/
async renderPDFPhantom(req: Request, res: Response, next: NextFunction) {
const instance = await phantom.create();
const page = await instance.createPage();
await page.property('viewportSize', {
// Ratio of A4 paper
width: 1050,
height: 1485,
});
// We need this hook to receive page status code
const pageOpened = new Promise<IWebPageResponse>((resolve, reject) => {
page.on('onResourceReceived', resolve);
});
const settings = {
operation: 'POST',
encoding: 'utf8',
headers: {
'Content-Type': 'application/json'
},
data: JSON.stringify(req.body)
};
// No matter the Host we know that our app will be running locally.
// This URL is accessed by a phantom process locally that's why
// we are using localhost.
const uri = `http://localhost:${req.socket.localPort}/html`;
console.log('Opening', uri, 'with settings', settings);
const [status, pageRes] = await Promise.all([
page.open(uri, settings),
pageOpened
]);
const content = await page.property<string>('plainText');
if (status === 'success' && pageRes.status === 200) {
// Everything is fine -> return PDF
const dir = os.tmpdir();
const name = crypto.randomBytes(12).toString('hex') + '.pdf';
const pdfPath = path.join(dir, name);
console.log('Saving file to', pdfPath);
await page.render(pdfPath);
res.sendFile(pdfPath);
} else if (pageRes.status >= 400 && pageRes.status < 500) {
// There was a client error
next(new ClientError(content));
} else if (pageRes.status >= 500) {
// There was a server error
next(new ServerError(content));
} else {
// PhantomJS failed to open the page
next(new ServerError('HTML rendering failed'));
}
// We always need to terminate phantom process
instance.exit();
}
/**
* POST /pdf/puppeteer
* Render template as PDF with Puppeteer
*/
async renderPDFPuppeteer(req: Request, res: Response, next: NextFunction) {
const browser = await puppeteer.launch({
// https://github.com/GoogleChrome/puppeteer/blob/master/docs/troubleshooting.md#tips
args: ['--disable-dev-shm-usage', '--no-sandbox', '--disable-setuid-sandbox']
});
const page = await browser.newPage();
let numberOfRequests = 0;
await page.setRequestInterception(true);
page.on('request', (interceptedRequest: puppeteer.Request) => {
// We need to override the first request we send
// to render HTML. Other requests for CSS and images
// should not be touched.
// https://github.com/GoogleChrome/puppeteer/issues/1062
const overrides = (numberOfRequests === 0) ?
{
method: 'POST',
headers: {
'Content-Type': 'application/json',
[process.env.TRACING_HEADER]: res.locals.tracingId,
},
postData: JSON.stringify(req.body),
} :
{};
numberOfRequests++;
interceptedRequest.continue(overrides);
});
// No matter the Host we know that our app will be running locally in a k8s pod.
// This URL is accessed by a chrome process locally that's why
// we are using localhost.
const uri = `http://localhost:${req.socket.localPort}/html`;
console.log('Opening', uri, 'to render PDF');
const response = await page.goto(uri, { waitUntil: 'networkidle2' });
if (!response) {
next(new ServerError('No response from HTML rendering'));
} else if (response.ok) {
await page.emulateMedia('screen');
const pdfBuffer = await page.pdf({ format: 'A4', printBackground: true });
res.send(pdfBuffer);
} else if (response.status >= 400) {
const content = await response.text();
next(response.status < 500
? new ClientError(content)
: new ServerError(content)
);
} else {
next(new ServerError('HTML rendering failed'));
}
// We always need to terminate puppeteer process
await browser.close();
}
}