聊聊springcloud的GatewayControllerEndpoint

go4it發表於2019-03-04

本文主要研究一下springcloud的GatewayControllerEndpoint

GatewayAutoConfiguration

spring-cloud-gateway-core-2.0.0.RC1-sources.jar!/org/springframework/cloud/gateway/config/GatewayAutoConfiguration.java

@Configuration
@ConditionalOnProperty(name = "spring.cloud.gateway.enabled", matchIfMissing = true)
@EnableConfigurationProperties
@AutoConfigureBefore(HttpHandlerAutoConfiguration.class)
@AutoConfigureAfter({GatewayLoadBalancerClientAutoConfiguration.class, GatewayClassPathWarningAutoConfiguration.class})
@ConditionalOnClass(DispatcherHandler.class)
public class GatewayAutoConfiguration {
	//......
		@Bean
	public AfterRoutePredicateFactory afterRoutePredicateFactory() {
		return new AfterRoutePredicateFactory();
	}

	@Bean
	public BeforeRoutePredicateFactory beforeRoutePredicateFactory() {
		return new BeforeRoutePredicateFactory();
	}

	@Bean
	public BetweenRoutePredicateFactory betweenRoutePredicateFactory() {
		return new BetweenRoutePredicateFactory();
	}

	@Bean
	public CookieRoutePredicateFactory cookieRoutePredicateFactory() {
		return new CookieRoutePredicateFactory();
	}

	@Bean
	public HeaderRoutePredicateFactory headerRoutePredicateFactory() {
		return new HeaderRoutePredicateFactory();
	}

	@Bean
	public HostRoutePredicateFactory hostRoutePredicateFactory() {
		return new HostRoutePredicateFactory();
	}

	@Bean
	public MethodRoutePredicateFactory methodRoutePredicateFactory() {
		return new MethodRoutePredicateFactory();
	}

	@Bean
	public PathRoutePredicateFactory pathRoutePredicateFactory() {
		return new PathRoutePredicateFactory();
	}

	@Bean
	public QueryRoutePredicateFactory queryRoutePredicateFactory() {
		return new QueryRoutePredicateFactory();
	}

	@Bean
	public ReadBodyPredicateFactory readBodyPredicateFactory(ServerCodecConfigurer codecConfigurer) {
		return new ReadBodyPredicateFactory(codecConfigurer);
	}

	//......
	@Configuration
	@ConditionalOnClass(Health.class)
	protected static class GatewayActuatorConfiguration {

		@Bean
		@ConditionalOnEnabledEndpoint
		public GatewayControllerEndpoint gatewayControllerEndpoint(RouteDefinitionLocator routeDefinitionLocator, List<GlobalFilter> globalFilters,
																List<GatewayFilterFactory> GatewayFilters, RouteDefinitionWriter routeDefinitionWriter,
																RouteLocator routeLocator) {
			return new GatewayControllerEndpoint(routeDefinitionLocator, globalFilters, GatewayFilters, routeDefinitionWriter, routeLocator);
		}
	}
}
複製程式碼

可以看到最後有一個GatewayActuatorConfiguration,在有actuator類庫的前提下則會配置,配置的是GatewayControllerEndpoint

GatewayControllerEndpoint

spring-cloud-gateway-core-2.0.0.RC1-sources.jar!/org/springframework/cloud/gateway/actuate/GatewayControllerEndpoint.java

@RestControllerEndpoint(id = "gateway")
public class GatewayControllerEndpoint implements ApplicationEventPublisherAware {

	private static final Log log = LogFactory.getLog(GatewayControllerEndpoint.class);

	private RouteDefinitionLocator routeDefinitionLocator;
	private List<GlobalFilter> globalFilters;
	private List<GatewayFilterFactory> GatewayFilters;
	private RouteDefinitionWriter routeDefinitionWriter;
	private RouteLocator routeLocator;
	private ApplicationEventPublisher publisher;

	public GatewayControllerEndpoint(RouteDefinitionLocator routeDefinitionLocator, List<GlobalFilter> globalFilters,
									 List<GatewayFilterFactory> GatewayFilters, RouteDefinitionWriter routeDefinitionWriter,
									 RouteLocator routeLocator) {
		this.routeDefinitionLocator = routeDefinitionLocator;
		this.globalFilters = globalFilters;
		this.GatewayFilters = GatewayFilters;
		this.routeDefinitionWriter = routeDefinitionWriter;
		this.routeLocator = routeLocator;
	}

	@Override
	public void setApplicationEventPublisher(ApplicationEventPublisher publisher) {
		this.publisher = publisher;
	}

	// TODO: Add uncommited or new but not active routes endpoint

	@PostMapping("/refresh")
	public Mono<Void> refresh() {
	    this.publisher.publishEvent(new RefreshRoutesEvent(this));
		return Mono.empty();
	}

	@GetMapping("/globalfilters")
	public Mono<HashMap<String, Object>> globalfilters() {
		return getNamesToOrders(this.globalFilters);
	}

	@GetMapping("/routefilters")
	public Mono<HashMap<String, Object>> routefilers() {
		return getNamesToOrders(this.GatewayFilters);
	}

	private <T> Mono<HashMap<String, Object>> getNamesToOrders(List<T> list) {
		return Flux.fromIterable(list).reduce(new HashMap<>(), this::putItem);
	}

	private HashMap<String, Object> putItem(HashMap<String, Object> map, Object o) {
		Integer order = null;
		if (o instanceof Ordered) {
			order = ((Ordered)o).getOrder();
		}
		//filters.put(o.getClass().getName(), order);
		map.put(o.toString(), order);
		return map;
	}

	// TODO: Flush out routes without a definition
	@GetMapping("/routes")
	public Mono<List<Map<String, Object>>> routes() {
		Mono<Map<String, RouteDefinition>> routeDefs = this.routeDefinitionLocator.getRouteDefinitions()
				.collectMap(RouteDefinition::getId);
		Mono<List<Route>> routes = this.routeLocator.getRoutes().collectList();
		return Mono.zip(routeDefs, routes).map(tuple -> {
			Map<String, RouteDefinition> defs = tuple.getT1();
			List<Route> routeList = tuple.getT2();
			List<Map<String, Object>> allRoutes = new ArrayList<>();

			routeList.forEach(route -> {
				HashMap<String, Object> r = new HashMap<>();
				r.put("route_id", route.getId());
				r.put("order", route.getOrder());

				if (defs.containsKey(route.getId())) {
					r.put("route_definition", defs.get(route.getId()));
				} else {
					HashMap<String, Object> obj = new HashMap<>();

					obj.put("predicate", route.getPredicate().toString());

					if (!route.getFilters().isEmpty()) {
						ArrayList<String> filters = new ArrayList<>();
						for (GatewayFilter filter : route.getFilters()) {
							filters.add(filter.toString());
						}

						obj.put("filters", filters);
					}

					if (!obj.isEmpty()) {
						r.put("route_object", obj);
					}
				}
				allRoutes.add(r);
			});

			return allRoutes;
		});
	}

/*
http POST :8080/admin/gateway/routes/apiaddreqhead uri=http://httpbin.org:80 predicates:=`["Host=**.apiaddrequestheader.org", "Path=/headers"]` filters:=`["AddRequestHeader=X-Request-ApiFoo, ApiBar"]`
*/
	@PostMapping("/routes/{id}")
	@SuppressWarnings("unchecked")
	public Mono<ResponseEntity<Void>> save(@PathVariable String id, @RequestBody Mono<RouteDefinition> route) {
		return this.routeDefinitionWriter.save(route.map(r ->  {
			r.setId(id);
			log.debug("Saving route: " + route);
			return r;
		})).then(Mono.defer(() ->
			Mono.just(ResponseEntity.created(URI.create("/routes/"+id)).build())
		));
	}

	@DeleteMapping("/routes/{id}")
	public Mono<ResponseEntity<Object>> delete(@PathVariable String id) {
		return this.routeDefinitionWriter.delete(Mono.just(id))
				.then(Mono.defer(() -> Mono.just(ResponseEntity.ok().build())))
				.onErrorResume(t -> t instanceof NotFoundException, t -> Mono.just(ResponseEntity.notFound().build()));
	}

	@GetMapping("/routes/{id}")
	public Mono<ResponseEntity<RouteDefinition>> route(@PathVariable String id) {
		//TODO: missing RouteLocator
		return this.routeDefinitionLocator.getRouteDefinitions()
				.filter(route -> route.getId().equals(id))
				.singleOrEmpty()
				.map(route -> ResponseEntity.ok(route))
				.switchIfEmpty(Mono.just(ResponseEntity.notFound().build()));
	}

	@GetMapping("/routes/{id}/combinedfilters")
	public Mono<HashMap<String, Object>> combinedfilters(@PathVariable String id) {
		//TODO: missing global filters
		return this.routeLocator.getRoutes()
				.filter(route -> route.getId().equals(id))
				.reduce(new HashMap<>(), this::putItem);
	}
}
複製程式碼

可以看到提供瞭如下幾個rest api

  • POST /refresh
  • GET /globalfilters
  • GET /routefilters
  • GET /routes
  • POST /routes/{id}
  • GET /routes/{id}
  • GET /routes/{id}/combinedfilters

例項

/actuator/gateway/routes

[
  {
    "route_id": "CompositeDiscoveryClient_DISCOVERY-SERVICE",
    "route_definition": {
      "id": "CompositeDiscoveryClient_DISCOVERY-SERVICE",
      "predicates": [
        {
          "name": "Path",
          "args": {
            "pattern": "/DISCOVERY-SERVICE/**"
          }
        }
      ],
      "filters": [
        {
          "name": "RewritePath",
          "args": {
            "regexp": "/DISCOVERY-SERVICE/(?<remaining>.*)",
            "replacement": "/${remaining}"
          }
        }
      ],
      "uri": "lb://DISCOVERY-SERVICE",
      "order": 0
    },
    "order": 0
  },
  {
    "route_id": "CompositeDiscoveryClient_GATEWAY-SERVICE",
    "route_definition": {
      "id": "CompositeDiscoveryClient_GATEWAY-SERVICE",
      "predicates": [
        {
          "name": "Path",
          "args": {
            "pattern": "/GATEWAY-SERVICE/**"
          }
        }
      ],
      "filters": [
        {
          "name": "RewritePath",
          "args": {
            "regexp": "/GATEWAY-SERVICE/(?<remaining>.*)",
            "replacement": "/${remaining}"
          }
        }
      ],
      "uri": "lb://GATEWAY-SERVICE",
      "order": 0
    },
    "order": 0
  }
]
複製程式碼

/actuator/gateway/globalfilters

{
  "org.springframework.cloud.gateway.filter.LoadBalancerClientFilter@f425231": 10100,
  "org.springframework.cloud.gateway.filter.NettyWriteResponseFilter@5cbd94b2": -1,
  "org.springframework.cloud.gateway.filter.NettyRoutingFilter@506aabf6": 2147483647,
  "org.springframework.cloud.gateway.filter.RouteToRequestUrlFilter@756aadfc": 10000,
  "org.springframework.cloud.gateway.filter.ForwardRoutingFilter@705a8dbc": 2147483647,
  "org.springframework.cloud.gateway.filter.AdaptCachedBodyGlobalFilter@6824b913": 2147483637,
  "org.springframework.cloud.gateway.filter.WebsocketRoutingFilter@40729f01": 2147483646
}
複製程式碼

/actuator/gateway/routefilters

{
  "[RedirectToGatewayFilterFactory@32f96bba configClass = RedirectToGatewayFilterFactory.Config]": null,
  "[StripPrefixGatewayFilterFactory@4a481728 configClass = StripPrefixGatewayFilterFactory.Config]": null,
  "[RemoveResponseHeaderGatewayFilterFactory@67e25252 configClass = AbstractGatewayFilterFactory.NameConfig]": null,
  "[RequestHeaderToRequestUriGatewayFilterFactory@4ace284d configClass = AbstractGatewayFilterFactory.NameConfig]": null,
  "[ModifyRequestBodyGatewayFilterFactory@b3e86d5 configClass = ModifyRequestBodyGatewayFilterFactory.Config]": null,
  "[RemoveRequestHeaderGatewayFilterFactory@611640f0 configClass = AbstractGatewayFilterFactory.NameConfig]": null,
  "[SetStatusGatewayFilterFactory@3fd05b3e configClass = SetStatusGatewayFilterFactory.Config]": null,
  "[PreserveHostHeaderGatewayFilterFactory@4d0e54e0 configClass = Object]": null,
  "[SetResponseHeaderGatewayFilterFactory@1682c08c configClass = AbstractNameValueGatewayFilterFactory.NameValueConfig]": null,
  "[SecureHeadersGatewayFilterFactory@76ececd configClass = Object]": null,
  "[SaveSessionGatewayFilterFactory@4eb9f2af configClass = Object]": null,
  "[AddResponseHeaderGatewayFilterFactory@75b6dd5b configClass = AbstractNameValueGatewayFilterFactory.NameValueConfig]": null,
  "[PrefixPathGatewayFilterFactory@e111c7c configClass = PrefixPathGatewayFilterFactory.Config]": null,
  "[SetRequestHeaderGatewayFilterFactory@7affc159 configClass = AbstractNameValueGatewayFilterFactory.NameValueConfig]": null,
  "[RewritePathGatewayFilterFactory@58f4b31a configClass = RewritePathGatewayFilterFactory.Config]": null,
  "[SetPathGatewayFilterFactory@72eb6200 configClass = SetPathGatewayFilterFactory.Config]": null,
  "[RetryGatewayFilterFactory@21a9a705 configClass = RetryGatewayFilterFactory.Retry]": null,
  "[AddRequestHeaderGatewayFilterFactory@1f1cddf3 configClass = AbstractNameValueGatewayFilterFactory.NameValueConfig]": null,
  "[ModifyResponseBodyGatewayFilterFactory@72b43104 configClass = ModifyResponseBodyGatewayFilterFactory.Config]": null,
  "[AddRequestParameterGatewayFilterFactory@228bda54 configClass = AbstractNameValueGatewayFilterFactory.NameValueConfig]": null
}
複製程式碼

/actuator/gateway/routes/CompositeDiscoveryClient_DISCOVERY-SERVICE

{
  "id": "CompositeDiscoveryClient_DISCOVERY-SERVICE",
  "predicates": [
    {
      "name": "Path",
      "args": {
        "pattern": "/DISCOVERY-SERVICE/**"
      }
    }
  ],
  "filters": [
    {
      "name": "RewritePath",
      "args": {
        "regexp": "/DISCOVERY-SERVICE/(?<remaining>.*)",
        "replacement": "/${remaining}"
      }
    }
  ],
  "uri": "lb://DISCOVERY-SERVICE",
  "order": 0
}
複製程式碼

/actuator/gateway/routes/CompositeDiscoveryClient_DISCOVERY-SERVICE/combinedfilters

{
  "Route{id=`CompositeDiscoveryClient_DISCOVERY-SERVICE`, uri=lb://DISCOVERY-SERVICE, order=0, predicate=org.springframework.cloud.gateway.handler.predicate.PathRoutePredicateFactory$$Lambda$769/1774209584@7d9639ad, gatewayFilters=[OrderedGatewayFilter{delegate=org.springframework.cloud.gateway.filter.factory.RewritePathGatewayFilterFactory$$Lambda$771/1052389952@528c8627, order=1}]}": 0
}
複製程式碼

小結

springcloud gateway提供了一個gateway actuator,該endpiont提供了關於filter及routes的資訊查詢以及指定route資訊更新的rest api,這給web介面提供管理配置功能提供了極大的便利。

doc

相關文章