如何通过 REST 控制器使用 Spring(引导)重写 URL?
How to rewrite URLs with Spring (Boot) via REST Controllers?
假设我有以下控制器及其父级 class:
@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 {
}
通过调用这样的 URL 来检索数据:
http://app.company.com/api/users
http://app.company.com/api/companies
现在假设我想将 /api
路径重命名为 /rest
但通过返回 301
HTTP 状态代码和新 URI
使其保持“可用” ]
例如客户请求:
GET /api/users HTTP/1.1
Host: app.company.com
服务器请求:
HTTP/1.1 301 Moved Permanently
Location: http://app.company.com/rest/users
所以我打算在我的父控制器中从 "api"
更改为 "rest"
:
@RestController
@RequestMapping(path = "rest")
public class RootController {
}
然后介绍一个“遗留”控制器:
@RestController
@RequestMapping(path = "api")
public class LegacyRootController {
}
但现在如何让它“重写”“遗留”URI?
这就是我正在努力解决的问题,无论是在 Whosebug 上还是其他地方,我都找不到任何与此事相关的 Spring。
我还有很多控制器和很多方法端点,所以我不能手动执行此操作(即通过编辑每个 @RequestMapping/@GetMapping 注释)。
我正在进行的项目基于 Spring Boot 2.1
编辑:我删除了 /business
路径,因为实际上继承在“默认情况下”不起作用(请参阅 Spring MVC @RequestMapping Inheritance or 之类的问题和答案)- 对此感到抱歉。
因为看起来您想要保留 301 但又想得到它 return 响应,您可以选择将 RootController
连接到 LegacyRootController
这样您就可以重用 RootController
中的逻辑,但 return 不同的响应代码并在 LegacyRootController
上提供不同的路径
@RestController
@RequestMapping(path = "api")
public class LegacyRootController {
private final RootController rootController;
public LegacyRootController(RootController rootController) {
this.rootController = rootController;
}
@GetMapping(value = "users", produces = {"application/json"})
@ResponseStatus(HttpStatus.MOVED_PERMANENTLY) // Respond 301
@ResponseBody
public String users() {
return rootController.users(); // Use rootController to provide appropriate response.
}
@GetMapping(value = "companies", produces = {"application/json"})
@ResponseStatus(HttpStatus.MOVED_PERMANENTLY)
@ResponseBody
public String companies() {
return rootController.companies();
}
}
这将允许您提供 /api/users
以提供 301 响应,同时还允许您提供 /rest/users
标准响应。
如果您想添加位置 header,您可以让 LegacyRootController
return 一个 ResponseEntity
提供 body 代码和 header 个值。
@GetMapping(value = "users", produces = {"application/json"})
public ResponseEntity<String> users() {
HttpHeaders responseHeaders = new HttpHeaders();
responseHeaders.setLocation("...");
return new ResponseEntity<String>(rootController.users(), responseHeaders, HttpStatus.MOVED_PERMANENTLY);
}
如果你想服务多个不服务于不同状态码的端点,你可以简单地提供多个路径
@RequestMapping(path = {"api", "rest"})
我终于找到了一种实现方法,既可以作为 javax.servlet.Filter
又可以作为 org.springframework.web.server.WebFilter
实现。
其实我引入Adapter模式就是为了改造两者:
org.springframework.http.server.ServletServerHttpResponse
(non-reactive) 和
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();
}
};
}
}
假设我有以下控制器及其父级 class:
@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 {
}
通过调用这样的 URL 来检索数据:
http://app.company.com/api/users
http://app.company.com/api/companies
现在假设我想将 /api
路径重命名为 /rest
但通过返回 301
HTTP 状态代码和新 URI
例如客户请求:
GET /api/users HTTP/1.1
Host: app.company.com
服务器请求:
HTTP/1.1 301 Moved Permanently
Location: http://app.company.com/rest/users
所以我打算在我的父控制器中从 "api"
更改为 "rest"
:
@RestController
@RequestMapping(path = "rest")
public class RootController {
}
然后介绍一个“遗留”控制器:
@RestController
@RequestMapping(path = "api")
public class LegacyRootController {
}
但现在如何让它“重写”“遗留”URI?
这就是我正在努力解决的问题,无论是在 Whosebug 上还是其他地方,我都找不到任何与此事相关的 Spring。
我还有很多控制器和很多方法端点,所以我不能手动执行此操作(即通过编辑每个 @RequestMapping/@GetMapping 注释)。
我正在进行的项目基于 Spring Boot 2.1
编辑:我删除了 /business
路径,因为实际上继承在“默认情况下”不起作用(请参阅 Spring MVC @RequestMapping Inheritance or
因为看起来您想要保留 301 但又想得到它 return 响应,您可以选择将 RootController
连接到 LegacyRootController
这样您就可以重用 RootController
中的逻辑,但 return 不同的响应代码并在 LegacyRootController
@RestController
@RequestMapping(path = "api")
public class LegacyRootController {
private final RootController rootController;
public LegacyRootController(RootController rootController) {
this.rootController = rootController;
}
@GetMapping(value = "users", produces = {"application/json"})
@ResponseStatus(HttpStatus.MOVED_PERMANENTLY) // Respond 301
@ResponseBody
public String users() {
return rootController.users(); // Use rootController to provide appropriate response.
}
@GetMapping(value = "companies", produces = {"application/json"})
@ResponseStatus(HttpStatus.MOVED_PERMANENTLY)
@ResponseBody
public String companies() {
return rootController.companies();
}
}
这将允许您提供 /api/users
以提供 301 响应,同时还允许您提供 /rest/users
标准响应。
如果您想添加位置 header,您可以让 LegacyRootController
return 一个 ResponseEntity
提供 body 代码和 header 个值。
@GetMapping(value = "users", produces = {"application/json"})
public ResponseEntity<String> users() {
HttpHeaders responseHeaders = new HttpHeaders();
responseHeaders.setLocation("...");
return new ResponseEntity<String>(rootController.users(), responseHeaders, HttpStatus.MOVED_PERMANENTLY);
}
如果你想服务多个不服务于不同状态码的端点,你可以简单地提供多个路径
@RequestMapping(path = {"api", "rest"})
我终于找到了一种实现方法,既可以作为 javax.servlet.Filter
又可以作为 org.springframework.web.server.WebFilter
实现。
其实我引入Adapter模式就是为了改造两者:
org.springframework.http.server.ServletServerHttpResponse
(non-reactive) 和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();
}
};
}
}