forked from alvarcarto/url-to-pdf-api
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
0 parents
commit 89e1b32
Showing
20 changed files
with
576 additions
and
0 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 |
---|---|---|
@@ -0,0 +1,15 @@ | ||
#!/bin/bash | ||
|
||
# Guide: | ||
# | ||
# 1. Copy this file to .env | ||
# | ||
# cp .env-sample .env | ||
# | ||
# 2. Fill the blanks | ||
|
||
export NODE_ENV=development | ||
export PORT=9000 | ||
export ALLOW_HTTP=true | ||
|
||
echo "Environment variables set!" |
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,17 @@ | ||
{ | ||
"env": { | ||
"browser": true, | ||
"amd": true, | ||
"node": true, | ||
"es6": true | ||
}, | ||
"extends": "airbnb-base", | ||
"rules": { | ||
"no-implicit-coercion": "error", | ||
"no-process-env": "error", | ||
"no-path-concat": "error", | ||
"import/no-extraneous-dependencies": ["error", {"devDependencies": true}], | ||
"no-use-before-define": ["error", { "functions": false }], | ||
"no-underscore-dangle": "off" | ||
} | ||
} |
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,50 @@ | ||
.DS_Store | ||
.idea | ||
|
||
# Logs | ||
logs | ||
*.log | ||
npm-debug.log* | ||
|
||
# Runtime data | ||
pids | ||
*.pid | ||
*.seed | ||
*.pid.lock | ||
|
||
# Directory for instrumented libs generated by jscoverage/JSCover | ||
lib-cov | ||
|
||
# Coverage directory used by tools like istanbul | ||
coverage | ||
|
||
# nyc test coverage | ||
.nyc_output | ||
|
||
# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) | ||
.grunt | ||
|
||
# node-waf configuration | ||
.lock-wscript | ||
|
||
# Compiled binary addons (http://nodejs.org/api/addons.html) | ||
build/Release | ||
|
||
# Dependency directories | ||
node_modules | ||
jspm_packages | ||
|
||
# Optional npm cache directory | ||
.npm | ||
|
||
# Optional eslint cache | ||
.eslintcache | ||
|
||
# Optional REPL history | ||
.node_repl_history | ||
|
||
# Output of 'npm pack' | ||
*.tgz | ||
|
||
# Yarn Integrity file | ||
.yarn-integrity |
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 @@ | ||
web: NODE_ENV=production node src/index.js |
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,33 @@ | ||
# URL to PDF | ||
|
||
> Web page PDF rendering done right. Packaged to an easy API. | ||
A simple API which converts a given URL to a PDF. **Why is it "done right"?** | ||
|
||
* Rendered with Headless Chrome, using [Puppeteer](https://github.com/GoogleChrome/puppeteer) | ||
* Sensible defaults | ||
* Easy deployment to Heroku. I love Lambda but.. Deploy to Heroku button. | ||
|
||
|
||
**Requires Node 8+ (async, await).** | ||
|
||
## Get started | ||
|
||
* `cp .env.sample .env` | ||
* Fill in the blanks in `.env` | ||
* `source .env` or `bash .env` | ||
|
||
Or use [autoenv](https://github.com/kennethreitz/autoenv). | ||
|
||
* `npm install` | ||
* `npm start` Start express server locally | ||
* Server runs at http://localhost:9000 or what `$PORT` env defines | ||
|
||
|
||
## Techstack | ||
|
||
* Node 8+ (async, await), written in ES7 | ||
* [Express.js](https://expressjs.com/) app with a nice internal architecture, based on [these conventions](https://github.com/kimmobrunfeldt/express-example). | ||
* Hapi-style Joi validation with [express-validation](https://github.com/andrewkeig/express-validation) | ||
* Heroku + [Puppeteer buildpack](https://github.com/jontewks/puppeteer-heroku-buildpack) | ||
* [Puppeteer](https://github.com/GoogleChrome/puppeteer) to control Chrome |
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,27 @@ | ||
{ | ||
"name": "url-to-pdf-api", | ||
"description": "Web page PDF rendering done right. Packaged to an easy API.", | ||
"keywords": [ | ||
"pdf", | ||
"html", | ||
"html to pdf", | ||
"html 2 pdf", | ||
"render" | ||
], | ||
"website": "https://github.com/kimmobrunfeldt/url-to-pdf-api", | ||
"repository": "https://github.com/kimmobrunfeldt/url-to-pdf-api", | ||
"env": { | ||
"ALLOW_HTTP": { | ||
"description": "When set to \"true\", unsecure requests are allowed.", | ||
"value": "false" | ||
} | ||
}, | ||
"buildpacks": [ | ||
{ | ||
"url": "https://github.com/jontewks/puppeteer-heroku-buildpack" | ||
}, | ||
{ | ||
"url": "http://github.com/heroku/heroku-buildpack-nodejs.git" | ||
} | ||
] | ||
} |
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,33 @@ | ||
{ | ||
"name": "url-to-pdf-api", | ||
"version": "1.0.0", | ||
"description": "Web page PDF rendering done right. Packaged to an easy API.", | ||
"main": "src/index.js", | ||
"scripts": { | ||
"start": "node src/index.js" | ||
}, | ||
"repository": { | ||
"type": "git", | ||
"url": "git+https://github.com/kimmobrunfeldt/url-to-pdf-api.git" | ||
}, | ||
"author": "Kimmo Brunfeldt", | ||
"license": "MIT", | ||
"bugs": { | ||
"url": "https://github.com/kimmobrunfeldt/url-to-pdf-api/issues" | ||
}, | ||
"homepage": "https://github.com/kimmobrunfeldt/url-to-pdf-api#readme", | ||
"dependencies": { | ||
"bluebird": "^3.5.0", | ||
"body-parser": "^1.18.2", | ||
"compression": "^1.7.1", | ||
"cors": "^2.8.4", | ||
"express": "^4.15.5", | ||
"express-validation": "^1.0.2", | ||
"joi": "^11.1.1", | ||
"lodash": "^4.17.4", | ||
"morgan": "^1.9.0", | ||
"puppeteer": "^0.11.0", | ||
"server-destroy": "^1.0.1", | ||
"winston": "^2.3.1" | ||
} | ||
} |
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,47 @@ | ||
const express = require('express'); | ||
const morgan = require('morgan'); | ||
const bodyParser = require('body-parser'); | ||
const compression = require('compression'); | ||
const cors = require('cors'); | ||
const logger = require('./util/logger')(__filename); | ||
const errorResponder = require('./middleware/error-responder'); | ||
const ipLogger = require('./middleware/ip-logger'); | ||
const errorLogger = require('./middleware/error-logger'); | ||
const requireHttps = require('./middleware/require-https'); | ||
const createRouter = require('./router'); | ||
const config = require('./config'); | ||
|
||
function createApp() { | ||
const app = express(); | ||
// App is served behind Heroku's router. | ||
// This is needed to be able to use req.ip or req.secure | ||
app.enable('trust proxy', 1); | ||
app.disable('x-powered-by'); | ||
|
||
if (config.NODE_ENV !== 'production') { | ||
app.use(morgan('dev')); | ||
} | ||
|
||
const corsOpts = { | ||
origin: config.CORS_ORIGIN, | ||
methods: ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS', 'HEAD', 'PATCH'], | ||
}; | ||
logger.info('Using CORS options:', corsOpts); | ||
app.use(cors(corsOpts)); | ||
app.use(bodyParser.json({ limit: '1mb' })); | ||
app.use(compression({ | ||
// Compress everything over 10 bytes | ||
threshold: 10, | ||
})); | ||
|
||
// Initialize routes | ||
const router = createRouter(); | ||
app.use('/', router); | ||
|
||
app.use(errorLogger()); | ||
app.use(errorResponder()); | ||
|
||
return app; | ||
} | ||
|
||
module.exports = createApp; |
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,11 @@ | ||
/* eslint-disable no-process-env */ | ||
const requireEnvs = require('./util/require-envs'); | ||
|
||
// Env vars should be casted to correct types | ||
const config = { | ||
PORT: Number(process.env.PORT) || 9000, | ||
NODE_ENV: process.env.NODE_ENV, | ||
LOG_LEVEL: process.env.LOG_LEVEL, | ||
}; | ||
|
||
module.exports = config; |
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,37 @@ | ||
const fs = require('fs'); | ||
const puppeteer = require('puppeteer'); | ||
const BPromise = require('bluerbird'); | ||
const _ = require('lodash'); | ||
|
||
BPromise.promisifyAll(fs); | ||
|
||
async function render(_opts = {}) { | ||
const opts = _.merge({ | ||
viewport: { | ||
width: 1200, | ||
height: 800, | ||
}, | ||
goto: { | ||
waitUntil: 'networkidle', | ||
}, | ||
pdf: { | ||
format: 'A4', | ||
} | ||
}, _opts); | ||
|
||
const browser = await puppeteer.launch(); | ||
const page = await browser.newPage(); | ||
await page.setViewport(opts.viewport) | ||
await page.goto(params.url, opts.goto); | ||
await page.pdf(_.merge({}, opts.pdf, { | ||
path: 'page.pdf', | ||
})); | ||
|
||
await browser.close(); | ||
|
||
return fs.readFileAsync('page.pdf', { encoding: null }); | ||
} | ||
|
||
module.exports = { | ||
render, | ||
}; |
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,14 @@ | ||
const ex = require('../util/express'); | ||
const pdfCore = require('../core/pdf-core'); | ||
|
||
const getRender = ex.createJsonRoute((req) => { | ||
const params = { | ||
url: req.query.url, | ||
}; | ||
|
||
return pdfCore.render(params); | ||
}); | ||
|
||
module.exports = { | ||
getRender, | ||
}; |
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,39 @@ | ||
const createApp = require('./app'); | ||
const enableDestroy = require('server-destroy'); | ||
const BPromise = require('bluebird'); | ||
const logger = require('./util/logger')(__filename); | ||
const config = require('./config'); | ||
|
||
BPromise.config({ | ||
warnings: config.NODE_ENV !== 'production', | ||
longStackTraces: true, | ||
}); | ||
|
||
const app = createApp(); | ||
const server = app.listen(config.PORT, () => { | ||
logger.info( | ||
'Express server listening on http://localhost:%d/ in %s mode', | ||
config.PORT, | ||
app.get('env') | ||
); | ||
}); | ||
enableDestroy(server); | ||
|
||
function closeServer(signal) { | ||
logger.info(`${signal} received`); | ||
logger.info('Closing http.Server ..'); | ||
server.destroy(); | ||
} | ||
|
||
// Handle signals gracefully. Heroku will send SIGTERM before idle. | ||
process.on('SIGTERM', closeServer.bind(this, 'SIGTERM')); | ||
process.on('SIGINT', closeServer.bind(this, 'SIGINT(Ctrl-C)')); | ||
|
||
server.on('close', () => { | ||
logger.info('Server closed'); | ||
process.emit('cleanup'); | ||
|
||
logger.info('Giving 100ms time to cleanup..'); | ||
// Give a small time frame to clean up | ||
setTimeout(process.exit, 100); | ||
}); |
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,59 @@ | ||
const _ = require('lodash'); | ||
const logger = require('../util/logger')(__filename); | ||
|
||
function createErrorLogger(opts) { | ||
opts = _.merge({ | ||
logRequest: status => { | ||
return status >= 400 && status !== 404 && status !== 503; | ||
}, | ||
logStackTrace: status => { | ||
return status >= 500 && status !== 503; | ||
} | ||
}, opts); | ||
|
||
return function errorHandler(err, req, res, next) { | ||
const status = err.status ? err.status : 500; | ||
const logLevel = getLogLevel(status); | ||
const log = logger[logLevel]; | ||
|
||
if (opts.logRequest(status)) { | ||
logRequestDetails(logLevel, req, status); | ||
} | ||
|
||
if (opts.logStackTrace(status)) { | ||
log(err, err.stack); | ||
} | ||
else { | ||
log(err.toString()); | ||
} | ||
|
||
next(err); | ||
}; | ||
} | ||
|
||
function getLogLevel(status) { | ||
return status >= 500 ? 'error' : 'warn'; | ||
} | ||
|
||
function logRequestDetails(logLevel, req, status) { | ||
logger[logLevel]('Request headers:', deepSupressLongStrings(req.headers)); | ||
logger[logLevel]('Request parameters:', deepSupressLongStrings(req.params)); | ||
logger.logEncrypted(logLevel, 'Request body:', req.body); | ||
} | ||
|
||
function deepSupressLongStrings(obj) { | ||
let newObj = {}; | ||
_.each(obj, (val, key) => { | ||
if (_.isString(val) && val.length > 100) { | ||
newObj[key] = val.slice(0, 100) + '... [CONTENT SLICED]'; | ||
} else if (_.isPlainObject(val)) { | ||
return deepSupressLongStrings(val); | ||
} else { | ||
newObj[key] = val; | ||
} | ||
}); | ||
|
||
return newObj; | ||
} | ||
|
||
module.exports = createErrorLogger; |
Oops, something went wrong.