UNPKG

web-print-service

Version:

Print a website service

300 lines (289 loc) 10.1 kB
const express = require("express"); const bodyParser = require('body-parser'); const app = express(); app.use(bodyParser.json({limit: '5mb'})); app.use(bodyParser.urlencoded({limit: '5mb',extended: true})); const puppeteer = require('puppeteer'); const fs = require('fs'); //const {execSync} = require('child_process'); const os = require('os'); const ptp = os.platform()==="win32" ?require("pdf-to-printer"): require("unix-print"); const cron = require('node-cron'); const validURL = (url) => { try { new URL(url); return true; } catch { return false; } }; let browser; const getBrowserInstance = async () => { if (!browser || browser.isClosed()) { browser = await puppeteer.launch({ ignoreHTTPSErrors: true, args: ['--disable-gpu', '--single-process', '--no-zygote', '--no-sandbox', '--hide-scrollbars'] }); } return browser; }; const MAX_PUPPETEER_INSTANCES = 10; // Giới hạn phiên Puppeteer let puppeteerInstances = 0; const createPage = async () => { if (puppeteerInstances >= MAX_PUPPETEER_INSTANCES) { throw new Error('Too many Puppeteer instances'); } puppeteerInstances++; const browser = await getBrowserInstance(); const page = await browser.newPage(); page.on('close', () => puppeteerInstances--); return page; }; const homePage = (cb)=>{ ptp.getPrinters().then(ps=>{ let html =`<html> <title>Print service</title> <body> <h1>Print service</h1> <p>By truongpv (invncur@gmail.com)<p> <h3>Printers</h3> <ul> ${ps.map(p=>{ return `<li>${p.printer||p} - status: ${p.status}</li>` })} </ul> <h3>API</h3> <ul> <li> url: http://localhost:8081/html-print </li> <li> method: POST </li> <li> Body data (raw json): 1 JSON Object has property "html_content". Example: { "html_context": "Hello Print service" } </li> <li> query: <ul> <li> landscape: 1 or 0. default:0 </li> <li> page_size: (option) Specifies the paper size. A2, A3, A4, A5, A6, letter, legal, tabloid, statement, or a name selectable from your printer settings. </li> </ul> </li> <li> Example: <div style='background-color:#ddd;padding:5px'>curl --location 'http://localhost:8081/html-print' \ --header 'Content-Type: application/json' \ --data '{ "html_content":"<div>Test Print</div>" }'</div> </li> </ul> </body> </html>` cb(null,html); }).catch(e=>{ cb("Can't get printers"); }) } const printPage = async (req,res,error_required)=>{ let p = req.query.url; let htmlContent = (req.body||{}).html_content; if(!p && !htmlContent){ return res.status(400).send(error_required); } if(p && !htmlContent){ let buff = Buffer.from(p, 'base64'); p = buff.toString('utf8'); if (!validURL(p)) { return res.status(400).send("Invalid URL"); } } let width = req.query.width; //let height = req.query.height; //find default printer let printer = req.query.printer; try{ if(!printer){ printer = await ptp.getDefaultPrinter(); if(!printer){ return res.status(400).send("Can't get default printer"); } if(printer.status=="offline") return res.status(400).send(`Default printer (${printer.printer}) is offline`); printer = printer.printer||printer; console.log("use printer default",printer); }else{ console.log("use printer",printer); } }catch(e){ console.error("Error find printer default",e); return res.status(400).send("Can't get default printer"); } if(!printer) return res.status(400).send("Not found printer"); let dir = __dirname + "/reports"; try{ if (!fs.existsSync(dir)) { fs.mkdirSync(dir); console.log("Directory is created."); } }catch(e){ console.error("Can't create dir reports",e.message||e,dir); return res.status(400).send(e.message); } let fileReport = `${dir}/rp-${new Date().getTime()}.pdf`; //open page let page; try{ page = await createPage(); console.log("create page puppeteer",p) if(htmlContent){ await page.setContent(htmlContent); }else{ await page.goto(p, {waitUntil: 'networkidle0'}); } }catch(e){ console.error("Error opening Puppeteer page", e.message); return res.status(400).send(e.message); } await page.addStyleTag({ content: 'body { margin: 0;}', }) let _height = await page.evaluate(() =>{ let printContent = document.getElementById('print-content'); if(!printContent) return 0; return printContent.clientHeight; }); if(_height) height = _height; try{ await page.pdf({ path: fileReport, printBackground: true, width:(width?`${width}px`:undefined) }); await page.close(); console.log("create pdf with width",width,fileReport); }catch(e){ console.error("Error export to pdf",e.message); await page.close(); return res.status(400).send(e.message); } if(os.platform()==="win32"){ const options = { printer, scale: "fit", orientation: req.query.landscape?"90":undefined, paperSize: req.query.page_size }; ptp.print(fileReport,options) .then(()=>{ res.send("Report was sent to print"); }) .catch((e)=>{ console.error(e.message) res.status(400).send(e.message); }) /*try{ execSync(`${__dirname}/dist/PDF.exe -print-settings "fit" -print-to "${printer}" ${fileReport}`); res.send("Report was sent to print"); }catch(e){ console.error(e.message) res.status(400).send(e.message); }*/ }else{ const options = []; if(req.query.landscape){ options.push(`-o landscape`); } options.push("-o fit-to-page"); if(req.query.page_size){ options.push(`-o media=${req.query.page_size}`); } ptp.print(fileReport,printer,options) .then(()=>{ res.send("Report was sent to print"); }) .catch((e)=>{ console.error(e.message) res.status(400).send(e.message); }) } } const start =(port=8081)=>{ //dọn dẹp rác cron.schedule('0 0 * * *', () => { const dir = __dirname + '/reports'; fs.readdir(dir, (err, files) => { if (err) return console.error(err); files.forEach((file) => { const filePath = `${dir}/${file}`; const stats = fs.statSync(filePath); const now = new Date().getTime(); const age = now - stats.mtime.getTime(); if (age > 7 * 24 * 60 * 60 * 1000) { // 7 ngày fs.unlink(filePath, (err) => { if (err) console.error(err); }); } }); }); }); //allow cross domain app.options('/*', function(req, res) { res.header('Access-Control-Allow-Origin', req.headers.origin || '*'); res.header('Access-Control-Allow-Methods', 'GET,POST,PUT,HEAD,DELETE,OPTIONS'); res.header('Access-Control-Allow-Headers', 'content-Type,x-requested-with,X-Access-Token,Authorization,Content-Encoding,Accept-Encoding'); res.sendStatus(200); }); app.use(function(req, res, next) { res.header('Access-Control-Allow-Origin', req.headers.origin || '*'); res.header('Access-Control-Allow-Methods', 'GET,POST,PUT,HEAD,DELETE,OPTIONS'); res.header('Access-Control-Allow-Headers', 'content-Type,x-requested-with,X-Access-Token,Authorization,Content-Encoding,Accept-Encoding'); next(); }); app.get('/', (req, res) => { console.log("query",req.query) homePage((e,html)=>{ if(e) return res.status(500).send(e); res.send(html); }) }); app.post('/', (req, res) => { console.log("body",req.body) homePage((e,html)=>{ if(e) return res.status(500).send(e); res.send(html); }) }); app.get(`/web-print`, (req,res)=>{ printPage(req,res,"parameter url is required") }); app.post(`/html-print` , (req,res)=>{ printPage(req,res,"Body with html_content is required") }); //workers const cluster = require('cluster'); const http = require('http'); const numCPUs = require('os').cpus().length; if (cluster.isMaster) { console.log(`Master ${process.pid} is running`); // Fork workers. for (let i = 0; i < numCPUs; i++) { cluster.fork(); } cluster.on('exit', (worker, code, signal) => { console.log(`worker ${worker.process.pid} died`); }); } else { console.log(`Worker ${process.pid} started`); app.listen(port, () => console.log(`Print service is running at http://localhost:${port}`)) } } module.exports ={ start }