picfit is a reusable Go server to manipulate (resize, thumbnail, etc.) images built on top of negroni and gorilla mux.
It will act as a proxy of your storage engine and will be served ideally behind an http cache system like varnish.
It supports multiple storages backends and multiple key/value stores.
- Make sure you have a Go language compiler >= 1.3 (required) and git installed.
- Make sure you have the following go system dependencies in your $PATH: bzr, svn, hg, git
- Ensure your GOPATH is properly set.
- Download it:
git clone https://github.com/thoas/picfit.git
- Run
make build
You have now a binary version of picfit in the bin
directory which
fits perfectly with your architecture.
We will provide Debian package when we will be completely stable ;)
Configuration should be stored in a readable file and in JSON format.
config.json
{
"kvstore": {
"type": "[KVSTORE]"
},
"storage": {
"src": {
"type": "[STORAGE]"
}
}
}
[KVSTORE]
can be:
- redis - Store generated keys in Redis, see below how you can customize connection parameters
- cache - Store generated keys in an in-memory cache
[STORAGE]
can be:
- fs - Store generated images in your File system
- http+fs - Store generated images in your File system and loaded using HTTP protocol
- s3 - Store generated images in Amazon S3
- http+s3 - Store generated images in Amazon S3 and loaded using HTTP protocol
- no key/value store
- no image storage
- images are given in absolute url
config.json
{
"port": 3001,
}
Images are generated on the fly at each request
- key/value in-memory store
- file system storage
An image is generated from your source storage (src
) and uploaded
asynchronously to this storage.
A unique key is generated and stored in a in-memory key/value store to process a dedicated request only once.
config.json
{
"port": 3001,
"storage": {
"src": {
"type": "fs",
"location": "/path/to/directory/"
}
},
"kvstore": {
"type": "cache"
},
}
- key/value store provided by Redis
- Amazon S3 storage
- shard filename
config.json
{
"kvstore": {
"type": "redis",
"host": "127.0.0.1",
"port": "6379",
"password": "",
"db": 0
},
"port": 3001,
"storage": {
"src": {
"type": "s3",
"access_key_id": "[ACCESS_KEY_ID]",
"secret_access_key": "[SECRET_ACCESS_KEY]",
"bucket_name": "[BUCKET_NAME]",
"acl": "[ACL]",
"region": "[REGION_NAME]",
"location": "path/to/directory"
}
},
"shard": {
"width": 1,
"depth": 2
}
}
Keys will be stored on Redis, (you better setup persistence).
Image files will be loaded and stored on Amazon S3 at the location path/to/directory
in the bucket [BUCKET_NAME]
.
[ACL]
can be:
- private
- public-read
- public-read-write
- authenticated-read
- bucket-owner-read
- bucket-owner-full-control
[REGION_NAME]
can be:
- us-gov-west-1
- us-east-1
- us-west-1
- us-west-2
- eu-west-1
- eu-central-1
- ap-southeast-1
- ap-southeast-2
- ap-northeast-1
- sa-east-1
- cn-north-1
Filename will be sharded:
depth
- 2 directorieswidth
- 1 letter for each directory
Example:
06102586671300cd02ae90f1faa16897.png
will become 0/6/102586671300cd02ae90f1faa16897.jpg
It would be useful if you are using the file system storage backend.
- key/value store provided by Redis
- File system to load images
- Amazon S3 storage to process images
config.json
{
"kvstore": {
"type": "redis",
"host": "127.0.0.1",
"port": "6379",
"password": "",
"db": 0
},
"port": 3001,
"storage": {
"src": {
"type": "fs",
"location": "path/to/directory"
},
"dst": {
"type": "s3",
"access_key_id": "[ACCESS_KEY_ID]",
"secret_access_key": "[SECRET_ACCESS_KEY]",
"bucket_name": "[BUCKET_NAME]",
"acl": "[ACL]",
"region": "[REGION_NAME]",
"location": "path/to/directory"
}
}
}
You will be able to load and store your images from different storages backend.
In this example, images will be loaded from the file system storage and generated to the Amazon S3 storage.
- key/value store provided by Redis
- File system to load images using HTTP method
- Amazon S3 storage to process images
config.json
{
"kvstore": {
"type": "redis",
"host": "127.0.0.1",
"port": "6379",
"password": "",
"prefix": "dummy:",
"db": 0
},
"port": 3001,
"storage": {
"src": {
"type": "http+fs",
"base_url": "http://media.example.com",
"location": "path/to/directory"
},
"dst": {
"type": "s3",
"access_key_id": "[ACCESS_KEY_ID]",
"secret_access_key": "[SECRET_ACCESS_KEY]",
"bucket_name": "[BUCKET_NAME]",
"acl": "[ACL]",
"region": "[REGION_NAME]",
"location": "path/to/directory"
}
}
}
In this example, images will be loaded from the file system storage
using HTTP with base_url
option and generated to the Amazon S3 storage.
Keys will be stored on Redis using the prefix dummy:
.
To run the application, issue the following command:
$ picfit -c config.json
By default, this will run the application on port 3001 and can be accessed by visiting:
http://localhost:3001
The port number can be configured with port
option in your config file.
To see a list of all available options, run:
$ picfit --help
Parameters to call the picfit service are:
<img src="http://localhost:3001/{method}?url={url}&path={path}&w={width}&h={height}&upscale={upscale}&sig={sig}&op={operation}&fmt={format}&q={quality}°={degree}&pos={position}"
- path - The filepath to load the image using your source storage
- operation - The operation to perform, see Operations
- sig - The signature key which is the representation of your query string and your secret key, see Security
- method - The method to perform, see Methods
- url - The url of the image to generate (not required if
path
provided) - width - The desired width of the image, if
0
is provided the service will calculate the ratio withheight
- height - The desired height of the image, if
0
is provided the service will calculate the ratio withwidth
- upscale - If your image is smaller than your desired dimensions, the service will upscale it by default to fit your dimensions, you can disable this behavior by providing
0
- format - The output format to save the image, by default the format will be the source format (a
GIF
image source will be saved asGIF
), see Formats - quality - The quality to save the image, by default the quality will be the highest possible, it will be only applied on
JPEG
format - degree - The degree (
90
,180
,270
) to rotate the image - position - The position to flip the image
To use this service, include the service url as replacement for your images, for example:
<img src="https://www.google.fr/images/srpr/logo11w.png" />
will become:
<img src="http://localhost:3001/display?url=https%3A%2F%2Fwww.google.fr%2Fimages%2Fsrpr%2Flogo11w.png&w=100&h=100&op=resize&upscale=0"
This will retrieve the image used in the url
parameter and resize it
to 100x100.
If an image is stored in your source storage at the location path/to/file.png
,
then you can call the service to load this file:
<img src="http://localhost:3001/display?w=100&h=100&path=path/to/file.png&op=resize" or <img src="http://localhost:3001/display/resize/100x100/path/to/file.png"
picfit currently supports the following formats:
image/jpeg
with the keywordjpg
orjpeg
image/png
with the keywordpng
image/gif
with the keywordgif
image/bmp
with the keywordbmp
This operation will able you to resize the image to the specified width and height.
If width or height value is 0, the image aspect ratio is preserved.
- w - The desired image's width
- h - The desired image's height
You have to pass the resize
value to the op
parameter to use this operation.
Thumbnail scales the image up or down using the specified resample filter, crops it to the specified width and height and returns the transformed image.
- w - The desired width of the image
- h - The desired height of the image
You have to pass the thumbnail
value to the op
parameter
to use this operation.
Flip flips the image vertically (from top to bottom) or horizontally (from left to right) and returns the transformed image.
- pos - The desired position to flip the image,
h
will flip the image horizontally,v
will flip the image vertically
You have to pass the flip
value to the op
parameter
to use this operation.
Rotate rotates the image to the desired degree and returns the transformed image.
- deg - The desired degree to rotate the image
You have to pass the rotate
value to the op
parameter
to use this operation.
Display the image, useful when you are using an img
tag.
The generated image will be stored asynchronously on your destination storage backend.
A couple of headers (Content-Type
, If-Modified-Since
) will be set
to allow you to use an http cache system.
Redirect to an image.
Your file will be generated synchronously then the redirection will be performed.
The first query will be slower but next ones will be faster because the name of the generated file will be stored in your key/value store.
Retrieve information about an image.
Your file will be generated synchronously then you will get the following information:
- filename - Filename of your generated file
- path - Path of your generated file
- url - Absolute url of your generated file (only if
base_url
is available on your destination storage)
The first query will be slower but next ones will be faster because the name of the generated file will be stored in your key/value store.
Expect the following result:
{
"filename":"a661f8d197a42d21d0190d33e629e4.png",
"path":"cache/6/7/a661f8d197a42d21d0190d33e629e4.png",
"url":"https://ds9xhxfkunhky.cloudfront.net/cache/6/7/a661f8d197a42d21d0190d33e629e4.png"
}
Upload is disabled by default for security reason. Before enabling it, you must understand you have to secure yourself this endpoint like only allowing the /upload route in your nginx or apache webserver for the local network.
Exposing the /upload endpoint without a security mechanism is not SAFE.
You can enable it by adding the option and a source storage to your configuration file.
config.json
{
"storage": {
"src": {
"type": "[STORAGE]"
}
},
"options": {
"enable_upload": true
}
}
Test it with the excellent httpie:
http -f POST localhost:3000/upload data@myupload
You will retrieve the uploaded image information in JSON
format.
In order to secure requests and avoid unknown third parties to
use the service, the application can require a request to provide a signature.
To enable this feature, set the secret_key
option in your config file.
The signature is an hexadecimal digest generated from the client key and the query string using the HMAC-SHA1 message authentication code (MAC) algorithm.
The below python code provides an implementation example:
import hashlib import hmac import six import urllib def sign(key, *args, **kwargs): m = hmac.new(key, None, hashlib.sha1) for arg in args: if isinstance(arg, dict): m.update(urllib.urlencode(arg)) elif isinstance(arg, six.string_types): m.update(arg) return m.hexdigest()
The signature is passed to the application by appending the sig
parameter to the query string; e.g.
w=100&h=100&sig=c9516346abf62876b6345817dba2f9a0c797ef26
.
Note, the application does not include the leading question mark when verifying
the supplied signature. To verify your signature implementation, see the
signature
command described in the Tools section.
To verify that your client application is generating correct signatures, use the command:
$ picfit signature --key=abcdef "w=100&h=100&op=resize" Query String: w=100&h=100&op=resize Signature: 6f7a667559990dee9c30fb459b88c23776fad25e Signed Query String: w=100&h=100&op=resize&sig=6f7a667559990dee9c30fb459b88c23776fad2
picfit logs events by default in stderr
and stdout
. You can implement sentry
to log errors using raven.
To enable this feature, set sentry
option in your config file.
config.json
{
"sentry": {
"dsn": "[YOUR_SENTRY_DSN]",
"tags": {
"foo": "bar"
}
}
}
Debug is disabled by default.
To enable this feature set debug
option to true
in your config file:
config.json
{
"debug": true
}
picfit supports CORS headers customization in your config file.
To enable this feature, set allowed_origins
and allowed_methods
,
for example:
config.json
{
"allowed_origins": ["*.ulule.com"],
"allowed_methods": ["GET", "HEAD"]
}
The quality rendering of the image engine can be controlled globally without adding it at each request:
config.json
{
"options": {
"quality": 70
}
}
With this option, each image will be saved in 70
quality.
By default the quality is the highest possible: 95
The format can be forced globally without adding it at each request:
config.json
{
"options": {
"format": "png"
}
}
With this option, each image will be forced to be saved in .png
.
By default the format will be chosen in this order:
- The
fmt
parameter if exists in query string - The original image format
- The default format provided in the application
The upload handler is disabled by default for security reason, you can enable it in your config:
config.json
{
"options": {
"enable_upload": true
}
}
It's recommended that the application run behind a CDN for larger applications or behind varnish for smaller ones.
Provisioning is handled by Ansible, you will find files in the repository.
You must have Ansible installed on your laptop, basically if you have python already installed you can do
$ pip install ansible
If you want to run the installed version from vagrant
$ vagrant up
Then connect to vagrant
$ vagrant ssh
The config is located to /etc/picfit/config.json
on the vagrant box.
see issues
Don't hesitate to send patch or improvements.
Client libraries will help you generate picfit urls with your secret key.
- picfit-go: a Go client library
- Ulule: an european crowdfunding platform
Thanks to these beautiful projects.