Last active
August 29, 2015 14:04
-
-
Save joepie91/c1a4561d148a1be21a1f to your computer and use it in GitHub Desktop.
AnonNews Image Proxy
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
Promise = require "bluebird" | |
express = require "express" | |
router = express.Router() | |
gm = require "gm" | |
util = require "../util" | |
fs = require "fs" | |
path = require "path" | |
moment = require "moment" | |
request = require "request" | |
urlLib = require "url" | |
stream = require "stream" | |
router.get "/image", (req, res, next) -> | |
cacheTimeout = 5 * 60 | |
cacheDirectory = "cache" | |
userAgent = "AnonNews v3 Image Proxy (ignore; Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/36.0.1985.125 Safari/537.36)" | |
if not req.query.url? | |
throw util.InputError "No URL specified." | |
minWidth = req.query.min_width ? "x" | |
minHeight = req.query.min_height ? "x" | |
maxWidth = req.query.max_width ? "x" | |
maxHeight = req.query.max_height ? "x" | |
url = req.query.url | |
cacheHash = util.md5sum url | |
cacheFile = "#{cacheHash}-#{minWidth}-#{minHeight}-#{maxWidth}-#{maxHeight}.dat" | |
cachePath = path.join cacheDirectory, cacheFile | |
# Promisifying breaks fs.stat, so we'll just do it with a normal callback. | |
fs.stat cachePath, (err, stat) -> | |
if err? | |
cacheExists = false | |
useCache = false | |
else | |
cacheExists = true | |
if Math.abs(moment(stat.mtime).diff(moment(), "seconds")) > cacheTimeout | |
useCache = false | |
else | |
useCache = true | |
# Try to get it from the remote source. | |
remoteSource = null | |
new Promise (resolve, reject) -> | |
if useCache == false | |
remoteSource = request.get url, {headers: {"user-agent": userAgent}} | |
remoteSource.on "response", (response) -> | |
if response.statusCode == 200 | |
resolve() | |
else | |
reject new Error("Status code #{response.statusCode} received from source.") | |
remoteSource.on "error", (err) -> | |
reject err | |
else | |
resolve() | |
.catch (error) -> | |
# The remote file could not be fetched, use the local cache instead. | |
useCache = true | |
.then () -> | |
if useCache | |
if cacheExists | |
res.set | |
"x-proxydiag-source": "cache" | |
"x-proxydiag-path": cachePath | |
source = fs.createReadStream cachePath | |
else | |
# The remote source is unreachable or does not exist, and we have no cache. | |
res.status 502 | |
res.send "HTTP 502 Bad Gateway; The remote source is unavailable." | |
return | |
else | |
res.set | |
"x-proxydiag-source": "remote" | |
"x-proxydiag-path": url | |
source = remoteSource | |
minWidth = if minWidth == "x" then null else minWidth | |
minHeight = if minHeight == "x" then null else minHeight | |
maxWidth = if maxWidth == "x" then null else maxWidth | |
maxHeight = if maxHeight == "x" then null else maxHeight | |
# If dimensions were specified, we need to run the image through GraphicsMagick first. | |
if not useCache and (minWidth? or minHeight? or maxWidth? or maxHeight?) | |
# Thumbnail mode enabled... | |
resizePromise = new Promise (resolve, reject) -> | |
extension = urlLib.parse(url).path.split("/").pop().split(".").pop() | |
image = gm(source, "image.#{extension}") | |
new Promise (resolve2, reject2) -> | |
image.size {bufferStream: true}, (err, result) -> | |
if err? | |
reject2 err | |
else if not result? | |
reject2 "No result." | |
else | |
resolve2 result | |
.then (size) -> | |
imageRatio = size.height / size.width | |
if minWidth? or minHeight? | |
# 'Fill' mode; the entire target frame must be filled by the image. | |
# Both width and height are equal to or larger than required. | |
targetRatio = minHeight / minWidth | |
if not minWidth or (minHeight? and imageRatio < targetRatio) | |
# Scale according to height | |
image.resize null, minHeight | |
else | |
# Scale according to width | |
image.resize minWidth | |
else if maxWidth? or maxHeight? | |
# 'Fit' mode; the image will be fit wholly into the target frame. | |
# Both width and height are equal to or smaller than required. | |
targetRatio = maxHeight / maxWidth | |
if not maxWidth? or (maxHeight? and imageRatio > targetRatio) | |
# Scale according to height | |
image.resize null, maxHeight | |
else | |
# Scale according to width | |
image.resize maxWidth | |
source = image.stream() | |
res.set "x-proxydiag-resized": 1 | |
resolve() | |
else | |
# Mock | |
resizePromise = new Promise (resolve, reject) -> | |
res.set "x-proxydiag-resized", 0 | |
resolve() | |
resizePromise.then -> | |
source.pipe res | |
if not useCache | |
cacheStream = fs.createWriteStream cachePath | |
source.pipe cacheStream | |
module.exports = router |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment