Image proxy server based on Spring Cloud gateway filters
Once my team got a requirement to modify HTTP image resources at a server on the fly. We started a discussion with variants to change this server but then a rational question comes up: What if there is a client that doesn’t need image modification?
Apparently we can add optional HTTP query parameters to the server request to keep backward compatibility for anyone. It’s appropriate for a lot of cases and the cleanest solution would be to write server web filters and check query parameters for any request to apply image processing logic.
I felt enthusiastic about this solution and eager to write a Spring Boot starter to get the opportunity to use those filters as a dependency inside any servers which provide images. But here comes a huge question about this dependency changes regarding work with microservices architecture.
It is hard to redeploy every microservice on every common dependency changes. Would I like to keep that kind of inconvenient maintenance?
Well, I would not. These days we have an architecture approach to release lightweight jar and fetch all the required dependencies from docker image layers during a service start. We didn’t have that infrastructure but it deserves to be discussed.
I decided to try another solution and use another independent service to modify outcoming images. Obviously, the most rational way is to try to apply a friendly to your infrastructure gateway services. So, it must:
- Keep backward compatibility logic to proxied services
- Be capable to turn off or customize image modification functionality easily
- Automatically be fetched to discovery services and easily configurable with other cloud services
My common stack is Spring Cloud that why I considered: Zuul, Zuul 2, Spring Cloud Gateway.
Initially, I excluded Zuul because of its blocking server functionality. Applying blocking functionality seems to be not the best solution in the context of proxy/gateway services because of essential arguments absent, such as requirements bringing impossibility to use custom thread pools and other difficulties to apply non-blocking IO.
Then I excluded non-blocking Zuul 2 cause Spring Cloud doesn’t provide box solutions for it.
Thus I chose non-block Spring Cloud Gateway.
I was looking forward to write a filter to get an opportunity to route all the requests to the target destination otherwise to proxy all the requests. I could get functionality to modify only required (specified at the spring properties of course) responses. For example to keep passing by
application/json requests and to modify
The solution is quite simple and the most important moment to use proper order:
ROUTE_TO_URL_FILTER_ORDER + 1 whitch means:
That filter should be applied right after the
org.springframework.cloud.gateway.filter.RouteToRequestUrlFilterto override property
GATEWAY_REQUEST_URL_ATTRresponsible for the target route url.
Then we have to declare custom Gateway Filter Factory cause the contract with this framework is:
It must exist bean by class with a name:
I declare this filter as default one for my gateway server:
The most important note here that if you use
ProxyForward filet then your
routes.uri property will be ignored, cause this filter will be applied after the default route to a URL filter as I wrote above.
You can run your server as a proxy with declared ProxyForward filter either the gateway with classic spring cloud gateway properties
Standalone gateway service
I keep proxy filter available inside a project jar but it is possible to run a spring cloud gateway as a classic gateway server with custom routing for specific paths. It is possible to change filter configuration and route requests via a custom filter.
When it is called a path
/rotate of the server then request proceeds to
http://my-image-server.org and the response passes through the custom RotateImage filter before sending it to the client.
The next and the main step was an image processing library selection. I didn’t develop a custom one because my goal was not to reinvent the wheel and write the best algorithm for it.
It is important to notice that my other goal was to get a clean design to replace an image processing library easily and even do it on the side of my dependencies consumers. It is still important to resolve and investigate what kind of image processing algorithm or functionality would be required for a specific application.
First of all, we had to write a logic to resolve the need for image processing or returning the response from the proxied server. The most important here is that it has to be configurable. Spring cloud gateway provides the opportunity to pass custom arguments as properties for your filters like this:
I used to create a config for each custom filter including default values for the Scalr method's parameters applying those kinds of properties.
Image modifications are possible by passing additional query parameters as the request part.
For example, a client called:
curl --location --request GET 'http://github.githubassets.com/images/modules/open_graph/github-octocat.png
If a client wants to modify image via proxy service it proceeds request with queries such as width and etc.:
curl --location --request GET 'http://github.githubassets.com/images/modules/open_graph/github-octocat.png?width=100&height=300
If the proxy server currently has setup such as:
Then the client gets a response with the resized image by width and height from the request. Also, the image format is changed to jpg cause of query
format=jpg parameter at the request part.
You can find all the gateway parameters at the GitHub read.me file
I’ve released this project at the GitHub repository: https://github.com/artemptushkin/proxy-image with a complete usage description at the read.me
If you have requirements such as I did with image processing or a similar one — please join to contribute or you can just fetch dependencies to run on your side. I published a 1.0.4 version on this article release:
I have plans to write a starter for a classic Spring REST API server to enable filters inside those applications directly and open to customization.
Also, I am going to write in another article about the importance of modularity at the Spring applications.
Update: The article about the code design of this project on dev.to
I hope this article helps you in searching for solutions on how to proxy data with Spring Cloud Gateway.