Skip to content

Instantly share code, notes, and snippets.

@joepie91
Last active August 29, 2015 14:04
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save joepie91/c1a4561d148a1be21a1f to your computer and use it in GitHub Desktop.
Save joepie91/c1a4561d148a1be21a1f to your computer and use it in GitHub Desktop.
AnonNews Image Proxy
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