Я пытаюсь перенести JHipster из Zuul в Spring Cloud Gateway. В текущей реализации Zuul есть GatewayResource
, который используется для получения списка маршрутов и их экземпляров службы.
package com.mycompany.myapp.web.rest;
import com.mycompany.myapp.web.rest.vm.RouteVM;
import java.util.ArrayList;
import java.util.List;
import org.springframework.cloud.client.discovery.DiscoveryClient;
import org.springframework.cloud.netflix.zuul.filters.Route;
import org.springframework.cloud.netflix.zuul.filters.RouteLocator;
import org.springframework.http.*;
import org.springframework.security.access.annotation.Secured;
import com.mycompany.myapp.security.AuthoritiesConstants;
import org.springframework.web.bind.annotation.*;
/**
* REST controller for managing Gateway configuration.
*/
@RestController
@RequestMapping("/api/gateway")
public class GatewayResource {
private final RouteLocator routeLocator;
private final DiscoveryClient discoveryClient;
public GatewayResource(RouteLocator routeLocator, DiscoveryClient discoveryClient) {
this.routeLocator = routeLocator;
this.discoveryClient = discoveryClient;
}
/**
* {@code GET /routes} : get the active routes.
*
* @return the {@link ResponseEntity} with status {@code 200 (OK)} and with body the list of routes.
*/
@GetMapping("/routes")
@Secured(AuthoritiesConstants.ADMIN)
public ResponseEntity<List<RouteVM>> activeRoutes() {
List<Route> routes = routeLocator.getRoutes();
List<RouteVM> routeVMs = new ArrayList<>();
routes.forEach(route -> {
RouteVM routeVM = new RouteVM();
routeVM.setPath(route.getFullPath());
routeVM.setServiceId(route.getId());
routeVM.setServiceInstances(discoveryClient.getInstances(route.getLocation()));
routeVMs.add(routeVM);
});
return ResponseEntity.ok(routeVMs);
}
}
Конечная точка /api/gateway/routes
возвращает данные, подобные следующим:
[
{
"path": "/services/blog/**",
"serviceId": "blog",
"serviceInstances": [
{
"serviceId": "BLOG",
"secure": false,
"instanceId": "blog:17c5482e0ccf49f19efb6dba8c5e5aa1",
"instanceInfo": {
"instanceId": "blog:17c5482e0ccf49f19efb6dba8c5e5aa1",
"app": "BLOG",
"appGroupName": null,
"ipAddr": "192.168.0.20",
"sid": "na",
"homePageUrl": "http://192.168.0.20:8081/",
"statusPageUrl": "http://192.168.0.20:8081/management/info",
"healthCheckUrl": "http://192.168.0.20:8081/management/health",
"secureHealthCheckUrl": null,
"vipAddress": "blog",
"secureVipAddress": "blog",
"countryId": 1,
"dataCenterInfo": {
"@class": "com.netflix.appinfo.InstanceInfo$DefaultDataCenterInfo",
"name": "MyOwn"
},
"hostName": "192.168.0.20",
"status": "UP",
"overriddenStatus": "UNKNOWN",
"leaseInfo": {
"renewalIntervalInSecs": 5,
"durationInSecs": 10,
"registrationTimestamp": 1581934876730,
"lastRenewalTimestamp": 1581935287273,
"evictionTimestamp": 0,
"serviceUpTimestamp": 1581934876214
},
"isCoordinatingDiscoveryServer": false,
"metadata": {
"zone": "primary",
"profile": "dev",
"management.port": "8081",
"version": "0.0.1-SNAPSHOT"
},
"lastUpdatedTimestamp": 1581934876730,
"lastDirtyTimestamp": 1581934876053,
"actionType": "ADDED",
"asgName": null
},
"port": 8081,
"host": "192.168.0.20",
"metadata": {
"zone": "primary",
"profile": "dev",
"management.port": "8081",
"version": "0.0.1-SNAPSHOT"
},
"uri": "http://192.168.0.20:8081",
"scheme": null
}
]
}
]
Как реализовать ту же конечную точку в Spring Cloud Gateway? Моя конфигурация в application.yml
выглядит следующим образом:
spring:
application:
name: jhipster
cloud:
gateway:
default-filters:
- TokenRelay
discovery:
locator:
enabled: true
lower-case-service-id: true
predicates:
- name: Path
args:
pattern: "'/services/'+serviceId.toLowerCase()+'/**'"
filters:
- name: RewritePath
args:
regexp: "'/services/' + serviceId.toLowerCase() + '/(?<remaining>.*)'"
replacement: "'/${remaining}'"
route-id-prefix: ""
httpclient:
pool:
max-connections: 1000
Я попытался использовать RouteLocator
следующим образом:
package com.mycompany.myapp.web.rest;
import com.mycompany.myapp.security.AuthoritiesConstants;
import com.mycompany.myapp.web.rest.vm.RouteVM;
import org.springframework.cloud.client.discovery.DiscoveryClient;
import org.springframework.cloud.gateway.route.*;
import org.springframework.http.ResponseEntity;
import org.springframework.security.access.annotation.Secured;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import reactor.core.publisher.Flux;
import java.util.ArrayList;
import java.util.List;
/**
* REST controller for managing Gateway configuration.
*/
@RestController
@RequestMapping("/api/gateway")
public class GatewayResource {
private final RouteLocator routeLocator;
private final DiscoveryClient discoveryClient;
public GatewayResource(RouteLocator routeLocator, DiscoveryClient discoveryClient) {
this.routeLocator = routeLocator;
this.discoveryClient = discoveryClient;
}
/**
* {@code GET /routes} : get the active routes.
*
* @return the {@link ResponseEntity} with status {@code 200 (OK)} and with body the list of routes.
*/
@GetMapping("/routes")
@Secured(AuthoritiesConstants.ADMIN)
public ResponseEntity<List<RouteVM>> activeRoutes() {
Flux<Route> routes = routeLocator.getRoutes();
List<RouteVM> routeVMs = new ArrayList<>();
routes.subscribe(route -> {
System.out.println("route: " + route.toString());
RouteVM routeVM = new RouteVM();
routeVM.setPath(route.getPredicate().toString());
String serviceId = route.getId().substring(route.getId().indexOf("_") + 1).toLowerCase();
routeVM.setServiceId(serviceId);
routeVM.setServiceInstances(discoveryClient.getInstances(serviceId));
routeVMs.add(routeVM);
});
return ResponseEntity.ok(routeVMs);
}
}
Это близко, поскольку возвращает следующее:
[
{
"path": "Paths: [/services/blog/**], match trailing slash: true",
"serviceId": "blog",
"serviceInstances": [
{
"uri": "http://192.168.0.20:8081",
"serviceId": "BLOG",
"port": 8081,
"host": "192.168.0.20",
"instanceId": "blog:17c5482e0ccf49f19efb6dba8c5e5aa1",
"secure": false,
"instanceInfo": {
"instanceId": "blog:17c5482e0ccf49f19efb6dba8c5e5aa1",
"app": "BLOG",
"appGroupName": null,
"ipAddr": "192.168.0.20",
"sid": "na",
"homePageUrl": "http://192.168.0.20:8081/",
"statusPageUrl": "http://192.168.0.20:8081/management/info",
"healthCheckUrl": "http://192.168.0.20:8081/management/health",
"secureHealthCheckUrl": null,
"vipAddress": "blog",
"secureVipAddress": "blog",
"countryId": 1,
"dataCenterInfo": {
"@class": "com.netflix.appinfo.InstanceInfo$DefaultDataCenterInfo",
"name": "MyOwn"
},
"hostName": "192.168.0.20",
"status": "UP",
"overriddenStatus": "UNKNOWN",
"leaseInfo": {
"renewalIntervalInSecs": 5,
"durationInSecs": 10,
"registrationTimestamp": 1581934876730,
"lastRenewalTimestamp": 1581965726885,
"evictionTimestamp": 0,
"serviceUpTimestamp": 1581934876214
},
"isCoordinatingDiscoveryServer": false,
"metadata": {
"zone": "primary",
"profile": "dev",
"management.port": "8081",
"version": "0.0.1-SNAPSHOT"
},
"lastUpdatedTimestamp": 1581934876730,
"lastDirtyTimestamp": 1581934876053,
"actionType": "ADDED",
"asgName": null
},
"metadata": {
"zone": "primary",
"profile": "dev",
"management.port": "8081",
"version": "0.0.1-SNAPSHOT"
},
"scheme": null
}
]
},
{
"path": "Paths: [/services/jhipster/**], match trailing slash: true",
"serviceId": "jhipster",
"serviceInstances": [{...}]
}
]
Печать маршрутов выглядит следующим образом:
route: Route{id='ReactiveCompositeDiscoveryClient_BLOG', uri=lb://BLOG, order=0, predicate=Paths: [/services/blog/**], match trailing slash: true, gatewayFilters=[[org.springframework.cloud.security.oauth2.gateway.TokenRelayGatewayFilterFactory$$Lambda$1404/0x0000000800a2dc40@2b9e183d, order = 1], [[RewritePath /services/blog/(?<remaining>.*) = '/${remaining}'], order = 1]], metadata={}}
route: Route{id='ReactiveCompositeDiscoveryClient_JHIPSTER', uri=lb://JHIPSTER, order=0, predicate=Paths: [/services/jhipster/**], match trailing slash: true, gatewayFilters=[[org.springframework.cloud.security.oauth2.gateway.TokenRelayGatewayFilterFactory$$Lambda$1404/0x0000000800a2dc40@59032a74, order = 1], [[RewritePath /services/jhipster/(?<remaining>.*) = '/${remaining}'], order = 1]], metadata={}}
Пара вопросов:
- Как получить путь предиката?
route.getPredicate().toString()
дает мне "Paths: [/services/blog/**], match trailing slash: true"
, и я просто хочу /services/blog/**
. - Почему
spring.cloud.gateway.discovery.location.route-id-prefix: ""
не удаляет префикс по умолчанию? Я должен удалить его вручную, используя route.getId().substring(route.getId().indexOf("_") + 1).toLowerCase()
. - Почему
RouteLocator
возвращает маршрут /services/jhipster
? Это маршрут к воротам. С Zuul был возвращен только один маршрут.