Comparing version 0.0.1 to 0.1.0
module.exports = { | ||
ipx: { | ||
input: { | ||
adapter: 'file.js', | ||
adapter: 'fs.js', | ||
dir: 'storage' | ||
}, | ||
cache: { | ||
adapter: 'file.js', | ||
adapter: 'fs.js', | ||
dir: 'cache', | ||
cleanCron: '* * * * * *', | ||
maxUnusedMinutes: 2 | ||
cleanCron: '0 0 3 * * *', | ||
maxUnusedMinutes: 24 * 60 | ||
} | ||
} | ||
} |
{ | ||
"name": "ipx", | ||
"version": "0.0.1", | ||
"version": "0.1.0", | ||
"license": "MIT", | ||
@@ -11,3 +11,3 @@ "contributors": [ | ||
"start": "bak start", | ||
"dev": "nodemon node_modules/.bin/bak start" | ||
"dev": "DEBUG=ipx* nodemon node_modules/.bin/bak start" | ||
}, | ||
@@ -18,2 +18,4 @@ "dependencies": { | ||
"cron": "^1.3.0", | ||
"debug": "^3.1.0", | ||
"etag": "^1.8.1", | ||
"fs-extra": "^4.0.3", | ||
@@ -20,0 +22,0 @@ "is-valid-path": "^0.1.1", |
# 🖼 IPX | ||
High performance, secure and easy to use and image proxy based on [Sharp](https://github.com/lovell/sharp) and [libvips](https://github.com/jcupitt/libvips). | ||
High performance, secure and easy to use image proxy based on [Sharp](https://github.com/lovell/sharp) and [libvips](https://github.com/jcupitt/libvips). | ||
@@ -14,2 +14,3 @@ **WARNING:: THIS PROJECT AND DOCS ARE STILL WIP!** | ||
- Smart and auto cache cleaning. | ||
- Twelve factor friendly. | ||
@@ -20,3 +21,3 @@ ## API | ||
Example: `http://cdn.example.com/webp/w:200/avatars/bafollo.png` | ||
Operations are separated by a colon `,` (Example: `op1,op2`) and their arguments separated using underscore `_` (Example: `s_200_300`) | ||
@@ -27,2 +28,43 @@ ََUse `_` value in place for `{format}` or `{operations}` to keep original values of source image. | ||
### Examples | ||
- Just change format to `webp` and keep other things same as source: | ||
`http://cdn.example.com/webp/_/avatars/buffalo.png` | ||
- Keep original format (`png`) and set width to `200`: | ||
`http://cdn.example.com/_/w_200/avatars/buffalo.png` | ||
- Resize to `200x300px` using `embed` method and change format to `jpg`: | ||
`http://cdn.example.com/jpg/s_200_300,embed/avatars/buffalo.png` | ||
## Docker deployment | ||
Latest docker image is automatically built under [pooya/ipx](https://hub.docker.com/r/pooya/ipx). | ||
Quick start: | ||
```bash | ||
docker run \ | ||
-it \ | ||
--rm \ | ||
--volume ./storage:/app/storage:ro \ | ||
--volume ./cache:/app/cache \ | ||
--port 3000:3000 | ||
pooya/ipx | ||
``` | ||
Using docker-compose: | ||
```yml | ||
image: pooya/ipx | ||
volumes: | ||
- ./storage:/app/storage:ro | ||
- ./cache:/app/cache | ||
ports: | ||
- 3000:3000 | ||
``` | ||
## Operations | ||
@@ -32,3 +74,2 @@ | ||
-------------|-----------------------|-------------|--------------------------------------------------------- | ||
`f` | `format` | f:webp | Change format. | ||
`s` | `width`, `height` | s:200:300 | Resize image. | ||
@@ -41,4 +82,23 @@ `w` | `width` | w:200 | Change image with. | ||
## Config | ||
Config can be customized using `IPX_*` environment variables or `config/local.js` file. Here are defaults: | ||
```js | ||
ipx: { | ||
input: { | ||
adapter: 'IPX_INPUT_ADAPTER', // Default: fs.js | ||
dir: 'IPX_INPUT_DIR' // Default: storage | ||
}, | ||
cache: { | ||
adapter: 'IPX_CACHE_ADAPTER', // Default: fs.js | ||
dir: 'IPX_CACHE_DIR', // Default: cache | ||
cleanCron: 'IPX_CACHE_CLEAN_CRON', // Default: 0 0 3 * * * (every night at 3:00 AM) | ||
maxUnusedMinutes: 'IPX_CACHE_CLEAN_MINUTES' // Default: 24 * 60 (24 hours) | ||
} | ||
} | ||
``` | ||
## License | ||
MIT - Pooya Parsa |
const Boom = require('boom') | ||
const Config = require('config') | ||
const IPX = require('./ipx') | ||
const etag = require('etag') | ||
@@ -10,2 +11,6 @@ const ipx = new IPX(Config.get('ipx')) | ||
path: '{format}/{operations}/{src*}', | ||
options: { | ||
// https://hapijs.com/api#-routeoptionscache | ||
cache: false | ||
}, | ||
async handler ({ params = {} }, h) { | ||
@@ -18,7 +23,23 @@ const { format, operations, src } = params | ||
const img = await ipx.get({ format, operations, src }) | ||
const response = h.response(img.data) | ||
// Get basic info about request | ||
const info = await ipx.getInfo({ format, operations, src }) | ||
if (img.format) { | ||
response.type('image/' + img.format) | ||
// Sets the response 'ETag' and 'Last-Modified' headers and | ||
// checks for any conditional request headers to decide | ||
// if the response is going to qualify for an HTTP 304 (Not Modified) | ||
// https://hapijs.com/api#-hentityoptions | ||
let response = h.entity({ | ||
etag: etag(info.cacheKey).replace(/"/g, ''), | ||
modified: info.stats.mtime | ||
}) | ||
// Get real response | ||
if (!response) { | ||
const data = await ipx.getData(info) | ||
response = h.response(data) | ||
} | ||
// Set additional headers | ||
if (info.format) { | ||
response.type('image/' + info.format) | ||
} else { | ||
@@ -25,0 +46,0 @@ response.type('image') |
@@ -6,2 +6,3 @@ const OPERATIONS = require('./operations') | ||
const { CronJob } = require('cron') | ||
const debug = require('debug')('ipx') | ||
@@ -11,3 +12,3 @@ const operationSeparator = ',' | ||
class Shark { | ||
class IPX { | ||
constructor (options) { | ||
@@ -58,2 +59,3 @@ this.options = options | ||
) | ||
debug('Starting cache clean cron ' + this.options.cache.cleanCron) | ||
this.cacheCleanCron.start() | ||
@@ -105,9 +107,3 @@ } | ||
/** | ||
* Convert and get the result | ||
* @param {String} format | ||
* @param {String} operations | ||
* @param {String} src | ||
*/ | ||
async get ({ format, operations, src }) { | ||
async getInfo ({ format, operations, src }) { | ||
// Validate format | ||
@@ -127,6 +123,12 @@ if (format === '_') { | ||
// Validate src | ||
if (!src || src.indexOf('..') >= 0 || !await this.input.validate(src)) { | ||
if (!src || src.indexOf('..') >= 0) { | ||
throw Boom.notFound() | ||
} | ||
// Get src stat | ||
const stats = await this.input.stats(src) | ||
if (!stats) { | ||
throw Boom.notFound() | ||
} | ||
// Parse and validate operations | ||
@@ -141,18 +143,22 @@ let _operations = this.parseOperations(operations) | ||
// Calculate unique hash key | ||
const cacheKey = | ||
src + | ||
'/' + | ||
(_operations.length ? _operations.map(o => o.cacheKey).join(argSeparator) : '_') + | ||
'.' + | ||
format | ||
// Compute unique hash key | ||
const operationsKey = _operations.length ? _operations.map(o => o.cacheKey).join(argSeparator) : '_' | ||
const statsKey = stats.mtime.getTime().toString(16) + '-' + stats.size.toString(16) | ||
const cacheKey = src + '/' + statsKey + '/' + operationsKey + '.' + format | ||
// Return info | ||
return { | ||
operations: _operations, | ||
stats, | ||
cacheKey, | ||
format, | ||
src | ||
} | ||
} | ||
async getData ({ cacheKey, stats, operations, format, src }) { | ||
// Check cache existence | ||
const cache = await this.cache.get(cacheKey) | ||
if (cache) { | ||
console.log('HIT!') | ||
return { | ||
format, | ||
data: cache | ||
} | ||
return cache | ||
} | ||
@@ -170,3 +176,3 @@ | ||
_operations.forEach(({ operation, args }) => { | ||
operations.forEach(({ operation, args }) => { | ||
try { | ||
@@ -191,6 +197,3 @@ sharp = operation.handler(this, sharp, ...args) | ||
return { | ||
data, | ||
format | ||
} | ||
return data | ||
} | ||
@@ -205,2 +208,2 @@ | ||
module.exports = Shark | ||
module.exports = IPX |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
17020
19
397
100
10
5
+ Addeddebug@^3.1.0
+ Addedetag@^1.8.1
+ Addedetag@1.8.1(transitive)