thymeleaf/thymeleaf-spring

Issues resolving image @{} while using both context path and spring cloud gateway as reverse proxy

antechrestos opened this issue · 2 comments

Runtime context

  • springboot version 2.5.5
  • spring cloud 2020.0.4
  • Thymeleaf 2.0.12

Context

I have two application: the first one is a spring cloud gateway running (let's say locally to make it easier it) with a simple discovery

server:
  port: 8080

spring:
  cloud:
     gateway:
       discovery:
         locator:
           url-expression: "uri"
           predicates:
             - name: Path
               args:
                 patterns:
                   - "'/' + serviceId + '/**'" 
           filters:
             - name: RewritePath
               args:
                 regexp: "'/' + serviceId + '/?(?<remaining>.*)'" 
                 replacement: "'/${remaining}'"
    discovery:
      client:
        simple:
          instances:
            gui:
               - uri: http://localhost:${aas.local.ports.admin}
                  metadata:
                    management.context-path: /manage

And the proxified spring boot named gui with following configuration

server:
  port: 8081
  forward-headers-strategy: "FRAMEWORK"

With this configuration (without context path for gui application), any use of @{/js/resource.js} for example, will work like charm when gui application is accessed through spring gateway application.

For instance, provided that /home endpoint of gui application serve a template that has th:src=@{/js/my-js.js}, when accessing

http://localhost:8080/gui/home, the computed url of js in served page will be http://localhost:8080/gui/js/my-js.js.

Also, while working directly on gui application, I can access to my endpoint through http://localhost:8081/home and it will resolve js address to http://localhost:8081/js/my-js.js

So far so good.

However, when I put a context path on my gui application, things are a little bit different.

I set then server.servlet.context-path: /my/context/path on gui application and while navigating to http://localhost:8081/gui/my/context/path/home, it serves me the computed template with computed url pointing to http://localhost:8080/gui/js/my-js.js instead of http://localhost:8080/gui/my/context/path/js/my-js.js .

Accessing gui directly (http://localhost:8081/my/context/path/home) does resolve js with context path to http://localhost:8081/my/context/path/js/my-js.js

Anything that I am missing?

Thank you

After investigation, seems like spring filter is eating the context path ... Enjoy your meal 😄

For anyone encountering this behaviour

public class ForceContextPathFilter extends OncePerRequestFilter {

  
    public ForceContextPathFilter(String contextPath) {
      
        this.contextPath = contextPath;
    }

    @Override
    protected boolean shouldNotFilter(HttpServletRequest request) throws ServletException {
        return false; // filter path you don't want to impact
    }

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws ServletException, IOException {
        if (contextPathIsSet() && !contextPath.equals(request.getContextPath())) {
            UriComponents uriComponents = UriComponentsBuilder.fromHttpRequest(new ServletServerHttpRequest(request)).build();
           final String scheme = uriComponents.getScheme();
           final String host = uriComponents.getHost();
           final String port = uriComponents.getPort();
           final String path = new UrlPathHelper().getPathWithinApplication(request);
            HttpServletRequestWrapper wrappedRequest = new HttpServletRequestWrapper(request) {

                 @Override
                 public String getContextPath() {
                   return super.getContextPath() + ForceContextPathFilter.this.contextPath;
                 }
                 
                @Override
                public String getRequestURI() {
                    return this.getContextPath()+path;
                }
                
                @Override
                public StringBuffer getRequestURL() {
                    return new StringBuffer()
                          .append(scheme)
                          .append("://")
                          .append(host)
                          .append(":")
                          .append(port)
                          .append(this.getContextPath())
                          .append(path);
                }
            };
            chain.doFilter(wrappedRequest, response);
        } else {
            chain.doFilter(request, response);
        }
    }

    private boolean contextPathIsSet() {
        return !"".equals(contextPath) && !"/".equals(contextPath);
    }
}