以前とりあえず作ってみたサーバーが不安定なので、書き直すことにした。
Dockerで動いてGETパラメータでURLを渡したらPDFが返ってくるというシンプルなもの。
PDFのサイズ(現在はA4固定)等のオプションは起動時に固定してしまうので、変えられない。
ローカルネットワーク内でAPIとして使うので、外に直接公開することは考えていない。
そのほか詳細は以前の記事参照
uyamazak.hatenablog.com
Dockerfile
FROM node:9
RUN mkdir /varuna/
WORKDIR /varuna/
# Install google-chrome from .deb file using gdebi for dependencies.
RUN apt-get update && \
apt-get install -y gdebi
COPY google-chrome-stable_current_amd64.deb ./
RUN gdebi --non-interactive google-chrome-stable_current_amd64.deb
# Install chrome-headless-render-pdf
RUN npm install \
chrome-headless-render-pdf@1.5 \
express \
connect-timeout
RUN chmod 700 ./node_modules/chrome-headless-render-pdf/cli/chrome-headless-render-pdf.js
# add option --no-sandbox
COPY node_modules/chrome-headless-render-pdf/index.js node_modules/chrome-headless-render-pdf/index.js
# Install fonts
COPY fonts /usr/share/fonts
# COPY my app dir
COPY app app
EXPOSE 8000
CMD ["node", "app/pdf-server-proto.js"]
chromeのインストールファイルgoogle-chrome-stable_current_amd64.debは下記ページの「別のプラットフォーム向けの Chrome をダウンロード」から取得して同じディレクトリに配置しておく。
www.google.com
ついでにnodeのバージョンも9にした。
chrome-headless-render-pdfも1.5になり、paper-width、paper-heightを渡せるようになったが、まだ Docker上で動かすのに必要な--no-sandboxを渡せないので、これだけindex.jsの192行目に追加してとりあえず使う。
プルリしたいけどまだ自信ない。
www.npmjs.com
サーバー本体
pdf-server-proto.js
const execSync = require('child_process').execSync;
const chromeBinary = '/usr/bin/google-chrome-stable'
const chromeVersion = execSync(chromeBinary + " --no-sandbox --version");
console.log("chrome version:", chromeVersion.toString());
const options = {
printLogs: true,
printErrors: true,
chromeBinary: chromeBinary,
noMargins: true,
landscape: true,
paperWidth: 11.70,
paperHeight: 8.26772,
includeBackground: true
};
console.log("RenderPDF options\n", options);
const RenderPDF = require('chrome-headless-render-pdf/index');
const renderer = new RenderPDF(options);
renderer.spawnChrome();
renderer.waitForDebugPort();
const express = require('express');
const app = express();
const timeout = require('connect-timeout');
const timeout_msec = 5000;
app.use(timeout(timeout_msec));
app.listen(8000, function(){
console.log('Listening on 8000');
});
app.get('/', async (req, res) => {
console.log("async start");
var url = req.query.url;
if (! url) {
res.status(404);
res.end('parameter "url" is not set');
return;
}
try {
const buff = await renderer.renderPdf(url, renderer.generatePdfOptions());
res.contentType("application/pdf");
res.send(buff);
res.status(200);
} catch (e) {
renderer.error('error:', e);
console.log(e);
res.status(503);
}
res.end();
});
app.get('/hc', function (req, res) {
console.log('health check ok');
res.status(200);
res.end('ok');
});
process.on('SIGINT', function() {
console.log('process exit with SIGINT');
renderer.killChrome();
process.exit();
});
ざっと下記の部分を直した
- 2重にexecSync(chrome-headless-render-pdfコマンドと、その内部)していたのを、RenderPDFを直接使うようにして省いた。
- 一度ファイルに書き出して、それを読み込んでいたのを直接バッファを返すようにした
- chromeをアクセスのたび起動したり、停止したりしていたのを、起動しっぱなしにした。
- 最初にChromeのバージョンとかoptionを出してみたり。
まだ本番利用はしていないが、レスポンスはかなり早くなり(A4一枚の書類で200ms程度)、たまに起きる謎の待ち時間、フリーズも見られなくなった。
動きは普通のChromeなので、外部のjs、cssなどのライブラリはなるべくインライン化し、サイズの大きなWEBフォント読み込みはやめてサーバーにインストールしておくと早くなる。キャッシュはしないっぽい?
変数宣言はできるだけconstにしておいた。
まだまだ己のnode.js力の足りなさを実感する。
async、awaitは初めて使ったけどこれなら理解できそう。