如何通过 REST 控制器使用 Spring(Boot)重写 URL?

max*_*yme 3 spring url-rewriting http-status-code-301 spring-boot request-mapping

假设我有以下控制器及其父类:

@RestController
public class BusinessController extends RootController {

    @GetMapping(value = "users", produces = {"application/json"})
    @ResponseBody
    public String users() {
        return "{ \"users\": [] }"
    }

    @GetMapping(value = "companies", produces = {"application/json"})
    @ResponseBody
    public String companies() {
        return "{ \"companies\": [] }"
    }

}

@RestController
@RequestMapping(path = "api")
public class RootController {

}
Run Code Online (Sandbox Code Playgroud)

通过调用以下 URL 来检索数据:

http://app.company.com/api/users
http://app.company.com/api/companies
Run Code Online (Sandbox Code Playgroud)

现在假设我想重命名/api路径,但通过在新 URI 旁边/rest返回 HTTP 状态代码来保持其“可用”301

例如客户请求:

GET /api/users HTTP/1.1
Host: app.company.com
Run Code Online (Sandbox Code Playgroud)

服务器请求:

HTTP/1.1 301 Moved Permanently
Location: http://app.company.com/rest/users
Run Code Online (Sandbox Code Playgroud)

所以我计划在我的父控制器中将其更改为"api""rest"

@RestController
@RequestMapping(path = "rest")
public class RootController {

}
Run Code Online (Sandbox Code Playgroud)

然后引入一个“遗留”控制器:

@RestController
@RequestMapping(path = "api")
public class LegacyRootController {

}
Run Code Online (Sandbox Code Playgroud)

但现在如何让它“重写”“遗留”URI?

这就是我正在努力解决的问题,无论是在 StackOverflow 还是其他地方,我都找不到任何与 Spring 相关的内容。

另外,我有很多控制器和很多方法端点,所以我无法手动执行此操作(即通过编辑每个 @RequestMapping/@GetMapping 注释)。

我正在做的项目是基于Spring Boot 2.1

编辑:我删除了/business路径,因为实际上继承在“默认情况下”不起作用(请参阅诸如Spring MVC @RequestMapping Inheritance在启动时修改 @RequestMappings 之类的问题和解答) - 对此感到抱歉。

max*_*yme 5

我终于找到了一种方法来实现这一点,既作为javax.servlet.Filter实现又作为org.springframework.web.server.WebFilter实现。

事实上,我引入适配器模式是为了转换两者:

  • org.springframework.http.server.ServletServerHttpResponse(非反应性)和
  • org.springframework.http.server.reactive.ServerHttpResponse(反应性)

org.springframework.http.HttpRequest因为与 Spring 的 HTTP 请求的包装器共享(让我访问URI和)相反HttpHeaders,响应的包装器不共享执行此操作的通用接口,所以我必须模拟一个(这里特意以类似的方式命名,HttpResponse) 。

@Component
public class RestRedirectWebFilter implements Filter, WebFilter {

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain chain)
            throws IOException, ServletException {
        ServletServerHttpRequest request = new ServletServerHttpRequest((HttpServletRequest) servletRequest);
        ServletServerHttpResponse response = new ServletServerHttpResponse((HttpServletResponse) servletResponse);

        if (actualFilter(request, adapt(response))) {
            chain.doFilter(servletRequest, servletResponse);
        }
    }

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain) {
        if (actualFilter(exchange.getRequest(), adapt(exchange.getResponse()))) {
            return chain.filter(exchange);
        } else {
            return Mono.empty();
        }
    }

    /**
     * Actual filtering.
     * 
     * @param request
     * @param response
     * @return boolean flag specifying if filter chaining should continue.
     */
    private boolean actualFilter(HttpRequest request, HttpResponse response) {
        URI uri = request.getURI();
        String path = uri.getPath();
        if (path.startsWith("/api/")) {
            String newPath = path.replaceFirst("/api/", "/rest/");
            URI location = UriComponentsBuilder.fromUri(uri).replacePath(newPath).build().toUri();
            response.getHeaders().setLocation(location);
            response.setStatusCode(HttpStatus.MOVED_PERMANENTLY);
            response.flush();
            return false;
        }
        return true;
    }

    interface HttpResponse extends HttpMessage {

        void setStatusCode(HttpStatus status);

        void flush();

    }

    private HttpResponse adapt(ServletServerHttpResponse response) {
        return new HttpResponse() {
            public HttpHeaders getHeaders() {
                return response.getHeaders();
            }

            public void setStatusCode(HttpStatus status) {
                response.setStatusCode(status);
            }

            public void flush() {
                response.close();
            }
        };
    }

    private HttpResponse adapt(org.springframework.http.server.reactive.ServerHttpResponse response) {
        return new HttpResponse() {
            public HttpHeaders getHeaders() {
                return response.getHeaders();
            }

            public void setStatusCode(HttpStatus status) {
                response.setStatusCode(status);
            }

            public void flush() {
                response.setComplete();
            }
        };
    }

}
Run Code Online (Sandbox Code Playgroud)