Image resize microservice. Learning nodejs and typescript.
- 3 resize strategies: [
COVER
|FIT
|STRETCH
] Which respectively try tocover
the given dimensions,fit
into them, orstretch
to match the aspect ratio. - Support for multiple edge cases in input files. Examples bundled.
- special characters:
cu spații & diacritice.jpg
- multiple formats, even transparency:
fsm.png
. Can technically support all formats supported by graphicsmagick if not for extension whitelist. - Extension whitelist
- subfolders in source directory:
subfolder/imagine.jpg
- Invalidate cache on original file
mtime
changes - both
.jpeg
and.jpg
extensions supported
- special characters:
- docker setup
- Resized file caching
- Http cache headers also offered by framework.
- Stateless operation. Multiple instances can work with the same sources and cache directories.
- Resized files are cached based on actual size, not requested size. This makes it that resizing
using
FIT
orCOVER
strategies which preserve aspect ratio don't present the chance of ignoring an available cached dimension. - some naive unit tests
- stats page and monitoring
- Automatic cache eviction, but the CacheGateway interface setter signature does contain
ttl
parameter for implementing it on a separate gateway.
- simple rate limiting
- rate limit for cache fails might also be feasible
The application has limited attack vectors. Besides potential exploits found in the expressJs framework the application offers as little surface area for potential hackers as possible:
- Input parameters:
/static/* files
- must match existing files which MUST be inside the./static
foldersize
- parameter is checked for- length
- structure (regex)
- content - MAX size is configurable
If-None-Match
http parameter - used internally by expressJs for etag matching
- As little info to attackers as possible. Errors shown to users contain no sensitive data.
- Input files are read-only from the application's perspective
Additionally some additional protections can be put in place to mitigate DOS risks based on client behavior.
* Same ip should not ignore cache-control: max-age
headers excessively if set.
* Same ip should not ask for an absurd number of different image sizes, eg: 100x100, 100x101, 100x102. etc.
The application leverages expressJs middleware-centered architecture
URLs look like http://localhost:8888/static/bh.jpg?size=2000x2000
The /static
part triggers the image resizing controller
The size
parameter must obey configuration MAX_SIZE
value, currently {width: 9999, height: 9999}
Images are NOT enlarged no mater what. If asking for a resolution larger than the original, the original is served instead.
Building on the behavior above, not specifying the size parameter is operationally equivalent to asking for size 9999x9999
Images are looked up in the folder specified by the STATIC_PATH
env variable and cached under the folder specified by the CACHE_PATH
env variable
Images are resized first (if needed) and only afterwards is the buffer written to the cache.
The current (basic) implementation of a cache gateway saves files under a hierarchy similar to the source files.
Source | Cache |
---|---|
/webroot/static/test.jpg |
/webroot/cache/test.jpg/100x100.jpg |
/webroot/static/test.jpeg |
/webroot/cache/test.jpeg/100x100.jpeg |
/webroot/static/subfolder/test.png |
/webroot/cache/subfolder/test.png/100x100.png |
The arrangement above makes it so that cache can be deleted on a per-file basis.
Run from repository directory:
sudo docker build -t homework .
sudo docker run \
--publish 8888:80 \
--env CACHE_PATH=/var/www/imgcache/cache \
--env STATIC_PATH=/var/www/imgcache/static \
--env RESIZE_STRATEGY=FIT \
--env NODE_ENV=production \
--env PORT=80 \
--volume `pwd`/static:/var/www/imgcache/static \
homework
Open a web browser, curl or wget the application: http://localhost:8888/static/bh.jpg?size=2000x2000
- Build the image
sudo docker run \
--publish 8888:80 \
--env CACHE_PATH=/var/www/imgcache/cache \
--env STATIC_PATH=/var/www/imgcache/static \
--env RESIZE_STRATEGY=FIT \
--env NODE_ENV=production \
--env PORT=80 \
--volume `pwd`/static:/var/www/imgcache/static \
homework
- Run from repository directory:
sudo docker build -t homework .
- Initialize swarm mode
sudo docker swarm init
- Deploy 'homework' stack
sudo docker stack deploy -c docker-compose.yml homework
- Check results
http://localhost:8888/static/bh.jpg?size=2000x2000 - Log into grafana - http://localhost:3003
- user:
root
- password:
root
- user:
- Import the supplied dashboard
./docker/grafana.json
The application is covered by a small test suite. You can run the test suite by running npm test
inside the running container.
Use sudo docker ps
to get the running container name and then run
sudo docker container exec -it <containerName> npm test