Re-instate @EnableZuulServer and simplifiy configuration a bit
With this change users can elect not to install the proxy features but still have a Zuul server with @Beans of type ZuulFilter added automatically. Fixes gh-104
This commit is contained in:
@@ -609,3 +609,22 @@ An application with the `@EnableZuulProxy` could act as a standalone
|
||||
server if you set a default route ("/"), for example `zuul.route.home:
|
||||
/` would route all traffic (i.e. "/**") to the "home" service.
|
||||
|
||||
=== Plain Embedded Zuul
|
||||
|
||||
You can also run a Zuul server without the proxying, or switch on parts of the proxying platform selectively, if you
|
||||
use `@EnableZuulServer` (instead of `@EnableZuulProxy`). Any beans that you add to the application of type `ZuulFilter`
|
||||
will be installed automatically.
|
||||
|
||||
In this case the routes into the Zuul server are
|
||||
still specified by configuring "zuul.routes.*", but there is no service discovery and no proxying, so the
|
||||
"serviceId" and "url" settings are ignored. For example:
|
||||
|
||||
.application.yml
|
||||
[source,yaml]
|
||||
----
|
||||
zuul:
|
||||
routes:
|
||||
api: /api/**
|
||||
----
|
||||
|
||||
maps all paths in "/api/**" to the Zuul filter chain.
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
package org.springframework.cloud.netflix.feign;
|
||||
|
||||
import java.net.URI;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.cloud.netflix.archaius.ConfigurableEnvironmentConfiguration;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
@@ -78,7 +76,6 @@ public class FeignConfiguration {
|
||||
}
|
||||
|
||||
protected <T> T loadBalance(Feign.Builder builder, Class<T> type, String schemeName) {
|
||||
URI.create(schemeName).getHost();
|
||||
if(ribbonClient != null) {
|
||||
return builder.client(ribbonClient).target(type, schemeName);
|
||||
} else {
|
||||
|
||||
@@ -10,12 +10,19 @@ import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
/**
|
||||
* Sets up a Zuul server endpoint and installs some reverse proxy filters in it, so it can
|
||||
* forward requests to backend servers. The backends can be registered manually through
|
||||
* configuration or via Eureka.
|
||||
*
|
||||
* @see EnableZuulServer for how to get a Zuul server without any proxying
|
||||
*
|
||||
* @author Spencer Gibb
|
||||
* @author Dave Syer
|
||||
*/
|
||||
@EnableCircuitBreaker
|
||||
@EnableDiscoveryClient
|
||||
@Target(ElementType.TYPE)
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Import(ZuulConfiguration.class)
|
||||
@Import(ZuulProxyConfiguration.class)
|
||||
public @interface EnableZuulProxy {
|
||||
}
|
||||
|
||||
@@ -1,17 +1,22 @@
|
||||
package org.springframework.cloud.netflix.zuul;
|
||||
|
||||
import org.springframework.cloud.client.circuitbreaker.EnableCircuitBreaker;
|
||||
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
|
||||
import java.lang.annotation.Documented;
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
import org.springframework.context.annotation.Import;
|
||||
|
||||
import java.lang.annotation.*;
|
||||
|
||||
/**
|
||||
* Set up the application to act as a generic Zuul server without any built-in reverse
|
||||
* proxy features. The routes into the Zuul server can be configured through
|
||||
* {@link ZuulProperties} (by default there are none).
|
||||
*
|
||||
* @see EnableZuulProxy to see how to get reverse proxy out of the box
|
||||
*
|
||||
* @author Spencer Gibb
|
||||
* @deprecated @see org.springframework.cloud.netflix.zuul.EnableZuulProxy
|
||||
*/
|
||||
@EnableCircuitBreaker
|
||||
@EnableDiscoveryClient
|
||||
@Target(ElementType.TYPE)
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Documented
|
||||
|
||||
@@ -10,6 +10,7 @@ import java.util.concurrent.atomic.AtomicReference;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import org.springframework.cloud.client.discovery.DiscoveryClient;
|
||||
import org.springframework.cloud.netflix.zuul.ZuulProperties.ZuulRoute;
|
||||
import org.springframework.util.AntPathMatcher;
|
||||
@@ -20,7 +21,7 @@ import org.springframework.util.StringUtils;
|
||||
* @author Spencer Gibb
|
||||
*/
|
||||
@Slf4j
|
||||
public class ProxyRouteLocator {
|
||||
public class ProxyRouteLocator implements RouteLocator {
|
||||
|
||||
public static final String DEFAULT_ROUTE = "/**";
|
||||
|
||||
@@ -49,6 +50,7 @@ public class ProxyRouteLocator {
|
||||
resetRoutes();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<String> getRoutePaths() {
|
||||
return getRoutes().keySet();
|
||||
}
|
||||
@@ -95,11 +97,11 @@ public class ProxyRouteLocator {
|
||||
break;
|
||||
}
|
||||
}
|
||||
return location == null ? null : new ProxyRouteSpec(id, targetPath, location, prefix);
|
||||
return location == null ? null : new ProxyRouteSpec(id, targetPath, location,
|
||||
prefix);
|
||||
}
|
||||
|
||||
// Package access so ZuulHandlerMapping can reset it's mappings
|
||||
void resetRoutes() {
|
||||
public void resetRoutes() {
|
||||
routes.set(locateRoutes());
|
||||
}
|
||||
|
||||
@@ -110,15 +112,17 @@ public class ProxyRouteLocator {
|
||||
addConfiguredRoutes(routesMap);
|
||||
routesMap.putAll(staticRoutes);
|
||||
|
||||
// Add routes for discovery services by default
|
||||
List<String> services = discovery.getServices();
|
||||
for (String serviceId : services) {
|
||||
// Ignore specifically ignored services and those that were manually
|
||||
// configured
|
||||
String key = "/" + serviceId + "/**";
|
||||
if (!properties.getIgnoredServices().contains(serviceId)
|
||||
&& !routesMap.containsKey(key)) {
|
||||
routesMap.put(key, new ZuulRoute(key, serviceId));
|
||||
if (discovery != null) {
|
||||
// Add routes for discovery services by default
|
||||
List<String> services = discovery.getServices();
|
||||
for (String serviceId : services) {
|
||||
// Ignore specifically ignored services and those that were manually
|
||||
// configured
|
||||
String key = "/" + serviceId + "/**";
|
||||
if (!properties.getIgnoredServices().contains(serviceId)
|
||||
&& !routesMap.containsKey(key)) {
|
||||
routesMap.put(key, new ZuulRoute(key, serviceId));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -154,7 +158,7 @@ public class ProxyRouteLocator {
|
||||
}
|
||||
|
||||
protected void addConfiguredRoutes(Map<String, ZuulRoute> routes) {
|
||||
Map<String, ZuulRoute> routeEntries = properties.getRoutesWithDefaultServiceIds();
|
||||
Map<String, ZuulRoute> routeEntries = properties.getRoutes();
|
||||
for (ZuulRoute entry : routeEntries.values()) {
|
||||
String route = entry.getPath();
|
||||
if (routes.containsKey(route)) {
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
/*
|
||||
* Copyright 2013-2014 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.cloud.netflix.zuul;
|
||||
|
||||
import java.util.Collection;
|
||||
|
||||
/**
|
||||
* @author Dave Syer
|
||||
*
|
||||
*/
|
||||
public interface RouteLocator {
|
||||
|
||||
Collection<String> getRoutePaths();
|
||||
|
||||
}
|
||||
@@ -1,39 +1,54 @@
|
||||
package org.springframework.cloud.netflix.zuul;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.actuate.endpoint.Endpoint;
|
||||
import org.springframework.boot.actuate.endpoint.mvc.MvcEndpoint;
|
||||
import org.springframework.context.ApplicationEventPublisher;
|
||||
import org.springframework.context.ApplicationEventPublisherAware;
|
||||
import org.springframework.jmx.export.annotation.ManagedAttribute;
|
||||
import org.springframework.jmx.export.annotation.ManagedOperation;
|
||||
import org.springframework.jmx.export.annotation.ManagedResource;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMethod;
|
||||
import org.springframework.web.bind.annotation.ResponseBody;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Endpoint to display and reset the zuul proxy routes
|
||||
*
|
||||
* @author Spencer Gibb
|
||||
* @author Dave Syer
|
||||
*/
|
||||
public class RoutesEndpoint implements MvcEndpoint {
|
||||
@ManagedResource(description = "Can be used to list and reset the reverse proxy routes")
|
||||
public class RoutesEndpoint implements MvcEndpoint, ApplicationEventPublisherAware {
|
||||
|
||||
private ZuulHandlerMapping handlerMapping;
|
||||
private ProxyRouteLocator routes;
|
||||
private ApplicationEventPublisher publisher;
|
||||
|
||||
@Override
|
||||
public void setApplicationEventPublisher(ApplicationEventPublisher publisher) {
|
||||
this.publisher = publisher;
|
||||
}
|
||||
|
||||
@Autowired
|
||||
public RoutesEndpoint(ZuulHandlerMapping handlerMapping) {
|
||||
this.handlerMapping = handlerMapping;
|
||||
public RoutesEndpoint(ProxyRouteLocator routes) {
|
||||
this.routes = routes;
|
||||
}
|
||||
|
||||
@RequestMapping(method = RequestMethod.POST)
|
||||
@ResponseBody
|
||||
@ManagedOperation
|
||||
public Map<String, String> reset() {
|
||||
return handlerMapping.reset();
|
||||
publisher.publishEvent(new RoutesRefreshedEvent(routes));
|
||||
return getRoutes();
|
||||
}
|
||||
|
||||
@RequestMapping(method = RequestMethod.GET)
|
||||
@ResponseBody
|
||||
@ManagedAttribute
|
||||
public Map<String, String> getRoutes() {
|
||||
return handlerMapping.getRoutes();
|
||||
return routes.getRoutes();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
/*
|
||||
* Copyright 2013-2014 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.cloud.netflix.zuul;
|
||||
|
||||
import org.springframework.context.ApplicationEvent;
|
||||
|
||||
/**
|
||||
* @author Dave Syer
|
||||
*
|
||||
*/
|
||||
@SuppressWarnings("serial")
|
||||
public class RoutesRefreshedEvent extends ApplicationEvent {
|
||||
|
||||
private RouteLocator locator;
|
||||
|
||||
public RoutesRefreshedEvent(RouteLocator locator) {
|
||||
super(locator);
|
||||
this.locator = locator;
|
||||
}
|
||||
|
||||
public RouteLocator getLocator() {
|
||||
return locator;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
/*
|
||||
* Copyright 2013-2014 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.cloud.netflix.zuul;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.LinkedHashSet;
|
||||
|
||||
import org.springframework.cloud.netflix.zuul.ZuulProperties.ZuulRoute;
|
||||
|
||||
/**
|
||||
* @author Dave Syer
|
||||
*
|
||||
*/
|
||||
public class SimpleRouteLocator implements RouteLocator {
|
||||
|
||||
private ZuulProperties properties;
|
||||
|
||||
public SimpleRouteLocator(ZuulProperties properties) {
|
||||
this.properties = properties;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<String> getRoutePaths() {
|
||||
Collection<String> paths = new LinkedHashSet<String>();
|
||||
for (ZuulRoute route : properties.getRoutes().values()) {
|
||||
paths.add(route.getPath());
|
||||
}
|
||||
return paths;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -3,51 +3,37 @@ package org.springframework.cloud.netflix.zuul;
|
||||
import java.util.Map;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.actuate.trace.TraceRepository;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression;
|
||||
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||
import org.springframework.cloud.client.discovery.DiscoveryClient;
|
||||
import org.springframework.cloud.context.config.annotation.RefreshScope;
|
||||
import org.springframework.cloud.netflix.ribbon.SpringClientFactory;
|
||||
import org.springframework.cloud.netflix.zuul.filters.ProxyRequestHelper;
|
||||
import org.springframework.cloud.context.scope.refresh.RefreshScopeRefreshedEvent;
|
||||
import org.springframework.cloud.netflix.zuul.filters.post.SendErrorFilter;
|
||||
import org.springframework.cloud.netflix.zuul.filters.post.SendResponseFilter;
|
||||
import org.springframework.cloud.netflix.zuul.filters.pre.DebugFilter;
|
||||
import org.springframework.cloud.netflix.zuul.filters.pre.PreDecorationFilter;
|
||||
import org.springframework.cloud.netflix.zuul.filters.pre.Servlet30WrapperFilter;
|
||||
import org.springframework.cloud.netflix.zuul.filters.route.RibbonRoutingFilter;
|
||||
import org.springframework.cloud.netflix.zuul.filters.route.SimpleHostRoutingFilter;
|
||||
import org.springframework.context.ApplicationEvent;
|
||||
import org.springframework.context.ApplicationListener;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.event.ContextRefreshedEvent;
|
||||
|
||||
import com.netflix.zuul.ZuulFilter;
|
||||
import com.netflix.zuul.http.ZuulServlet;
|
||||
|
||||
/**
|
||||
* @author Spencer Gibb
|
||||
* @author Dave Syer
|
||||
*/
|
||||
@Configuration
|
||||
@EnableConfigurationProperties(ZuulProperties.class)
|
||||
@ConditionalOnClass(ZuulServlet.class)
|
||||
@ConditionalOnExpression("${zuul.enabled:true}")
|
||||
public class ZuulConfiguration {
|
||||
|
||||
@Autowired(required = false)
|
||||
private TraceRepository traces;
|
||||
|
||||
@Autowired
|
||||
private SpringClientFactory clientFactory;
|
||||
|
||||
@Autowired
|
||||
private DiscoveryClient discovery;
|
||||
|
||||
@Autowired
|
||||
private ZuulProperties zuulProperties;
|
||||
|
||||
@Bean
|
||||
public ProxyRouteLocator routes() {
|
||||
return new ProxyRouteLocator(discovery, zuulProperties);
|
||||
public RouteLocator routes() {
|
||||
return new SimpleRouteLocator(zuulProperties);
|
||||
}
|
||||
|
||||
@Bean
|
||||
@@ -56,9 +42,8 @@ public class ZuulConfiguration {
|
||||
}
|
||||
|
||||
@Bean
|
||||
@RefreshScope
|
||||
public ZuulHandlerMapping zuulHandlerMapping() {
|
||||
return new ZuulHandlerMapping(routes(), zuulController());
|
||||
public ZuulHandlerMapping zuulHandlerMapping(RouteLocator routes) {
|
||||
return new ZuulHandlerMapping(routes, zuulController());
|
||||
}
|
||||
|
||||
@Configuration
|
||||
@@ -75,8 +60,23 @@ public class ZuulConfiguration {
|
||||
}
|
||||
|
||||
@Bean
|
||||
public RoutesEndpoint zuulEndpoint() {
|
||||
return new RoutesEndpoint(zuulHandlerMapping());
|
||||
public ApplicationListener<ApplicationEvent> zuulRefreshRoutesListener() {
|
||||
return new ZuulRefreshListener();
|
||||
}
|
||||
|
||||
private static class ZuulRefreshListener implements
|
||||
ApplicationListener<ApplicationEvent> {
|
||||
|
||||
@Autowired
|
||||
private ZuulHandlerMapping zuulHandlerMapping;
|
||||
|
||||
@Override
|
||||
public void onApplicationEvent(ApplicationEvent event) {
|
||||
if (event instanceof ContextRefreshedEvent
|
||||
|| event instanceof RefreshScopeRefreshedEvent)
|
||||
zuulHandlerMapping.registerHandlers();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// pre filters
|
||||
@@ -85,36 +85,11 @@ public class ZuulConfiguration {
|
||||
return new DebugFilter();
|
||||
}
|
||||
|
||||
@Bean
|
||||
public PreDecorationFilter preDecorationFilter() {
|
||||
return new PreDecorationFilter(routes(), zuulProperties);
|
||||
}
|
||||
|
||||
@Bean
|
||||
public Servlet30WrapperFilter servlet30WrapperFilter() {
|
||||
return new Servlet30WrapperFilter();
|
||||
}
|
||||
|
||||
// route filters
|
||||
@Bean
|
||||
public RibbonRoutingFilter ribbonRoutingFilter() {
|
||||
ProxyRequestHelper helper = new ProxyRequestHelper();
|
||||
if (traces != null) {
|
||||
helper.setTraces(traces);
|
||||
}
|
||||
RibbonRoutingFilter filter = new RibbonRoutingFilter(helper , clientFactory);
|
||||
return filter;
|
||||
}
|
||||
|
||||
@Bean
|
||||
public SimpleHostRoutingFilter simpleHostRoutingFilter() {
|
||||
ProxyRequestHelper helper = new ProxyRequestHelper();
|
||||
if (traces != null) {
|
||||
helper.setTraces(traces);
|
||||
}
|
||||
return new SimpleHostRoutingFilter(helper);
|
||||
}
|
||||
|
||||
// post filters
|
||||
@Bean
|
||||
public SendResponseFilter sendResponseFilter() {
|
||||
@@ -125,4 +100,5 @@ public class ZuulConfiguration {
|
||||
public SendErrorFilter sendErrorFilter() {
|
||||
return new SendErrorFilter();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,19 +1,9 @@
|
||||
package org.springframework.cloud.netflix.zuul;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.cloud.client.discovery.DiscoveryHeartbeatEvent;
|
||||
import org.springframework.cloud.client.discovery.InstanceRegisteredEvent;
|
||||
import org.springframework.cloud.context.scope.refresh.RefreshScopeRefreshedEvent;
|
||||
import org.springframework.context.ApplicationEvent;
|
||||
import org.springframework.context.ApplicationListener;
|
||||
import org.springframework.jmx.export.annotation.ManagedAttribute;
|
||||
import org.springframework.jmx.export.annotation.ManagedOperation;
|
||||
import org.springframework.jmx.export.annotation.ManagedResource;
|
||||
import org.springframework.web.servlet.handler.AbstractUrlHandlerMapping;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.web.servlet.handler.AbstractUrlHandlerMapping;
|
||||
|
||||
/**
|
||||
* MVC HandlerMapping that maps incoming request paths to remote services.
|
||||
@@ -21,37 +11,19 @@ import java.util.concurrent.atomic.AtomicReference;
|
||||
* @author Spencer Gibb
|
||||
* @author Dave Syer
|
||||
*/
|
||||
@ManagedResource(description = "Can be used to list and reset the reverse proxy routes")
|
||||
public class ZuulHandlerMapping extends AbstractUrlHandlerMapping implements
|
||||
ApplicationListener<ApplicationEvent> {
|
||||
public class ZuulHandlerMapping extends AbstractUrlHandlerMapping {
|
||||
|
||||
private ProxyRouteLocator routeLocator;
|
||||
private RouteLocator routeLocator;
|
||||
|
||||
private ZuulController zuul;
|
||||
|
||||
private AtomicReference<Object> latestHeartbeat = new AtomicReference<>();
|
||||
|
||||
@Autowired
|
||||
public ZuulHandlerMapping(ProxyRouteLocator routeLocator, ZuulController zuul) {
|
||||
public ZuulHandlerMapping(RouteLocator routeLocator, ZuulController zuul) {
|
||||
this.routeLocator = routeLocator;
|
||||
this.zuul = zuul;
|
||||
setOrder(-200);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onApplicationEvent(ApplicationEvent event) {
|
||||
if (event instanceof InstanceRegisteredEvent
|
||||
|| event instanceof RefreshScopeRefreshedEvent) {
|
||||
reset();
|
||||
} else if (event instanceof DiscoveryHeartbeatEvent) {
|
||||
DiscoveryHeartbeatEvent e = (DiscoveryHeartbeatEvent) event;
|
||||
if (latestHeartbeat.get() == null || !latestHeartbeat.get().equals(e.getValue())) {
|
||||
latestHeartbeat.set(e.getValue());
|
||||
reset();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected void registerHandlers() {
|
||||
Collection<String> routes = routeLocator.getRoutePaths();
|
||||
if (routes.isEmpty()) {
|
||||
@@ -64,16 +36,4 @@ public class ZuulHandlerMapping extends AbstractUrlHandlerMapping implements
|
||||
}
|
||||
}
|
||||
|
||||
@ManagedOperation
|
||||
public Map<String, String> reset() {
|
||||
routeLocator.resetRoutes();
|
||||
registerHandlers();
|
||||
return getRoutes();
|
||||
}
|
||||
|
||||
@ManagedAttribute
|
||||
public Map<String, String> getRoutes() {
|
||||
return routeLocator.getRoutes();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -6,6 +6,8 @@ import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
|
||||
import javax.annotation.PostConstruct;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
@@ -26,7 +28,8 @@ public class ZuulProperties {
|
||||
private boolean addProxyHeaders = true;
|
||||
private List<String> ignoredServices = new ArrayList<String>();
|
||||
|
||||
public Map<String, ZuulRoute> getRoutesWithDefaultServiceIds() {
|
||||
@PostConstruct
|
||||
public void init() {
|
||||
for (Entry<String, ZuulRoute> entry : this.routes.entrySet()) {
|
||||
ZuulRoute value = entry.getValue();
|
||||
if (!StringUtils.hasText(value.getLocation())) {
|
||||
@@ -39,7 +42,6 @@ public class ZuulProperties {
|
||||
value.path = "/" + entry.getKey() + "/**";
|
||||
}
|
||||
}
|
||||
return this.routes;
|
||||
}
|
||||
|
||||
@Data
|
||||
@@ -91,10 +93,10 @@ public class ZuulProperties {
|
||||
serviceId = location;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private String extractId(String path) {
|
||||
path = path.startsWith("/") ? path.substring(1) : path;
|
||||
path = path.replace("/*","").replace("*", "");
|
||||
path = path.replace("/*", "").replace("*", "");
|
||||
return path;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,120 @@
|
||||
package org.springframework.cloud.netflix.zuul;
|
||||
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.actuate.trace.TraceRepository;
|
||||
import org.springframework.cloud.client.discovery.DiscoveryClient;
|
||||
import org.springframework.cloud.client.discovery.DiscoveryHeartbeatEvent;
|
||||
import org.springframework.cloud.client.discovery.InstanceRegisteredEvent;
|
||||
import org.springframework.cloud.context.scope.refresh.RefreshScopeRefreshedEvent;
|
||||
import org.springframework.cloud.netflix.ribbon.SpringClientFactory;
|
||||
import org.springframework.cloud.netflix.zuul.filters.ProxyRequestHelper;
|
||||
import org.springframework.cloud.netflix.zuul.filters.pre.PreDecorationFilter;
|
||||
import org.springframework.cloud.netflix.zuul.filters.route.RibbonRoutingFilter;
|
||||
import org.springframework.cloud.netflix.zuul.filters.route.SimpleHostRoutingFilter;
|
||||
import org.springframework.context.ApplicationEvent;
|
||||
import org.springframework.context.ApplicationListener;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
/**
|
||||
* @author Spencer Gibb
|
||||
* @author Dave Syer
|
||||
*/
|
||||
@Configuration
|
||||
public class ZuulProxyConfiguration extends ZuulConfiguration {
|
||||
|
||||
@Autowired(required = false)
|
||||
private TraceRepository traces;
|
||||
|
||||
@Autowired
|
||||
private SpringClientFactory clientFactory;
|
||||
|
||||
@Autowired
|
||||
private DiscoveryClient discovery;
|
||||
|
||||
@Autowired
|
||||
private ZuulProperties zuulProperties;
|
||||
|
||||
@Bean
|
||||
@Override
|
||||
public ProxyRouteLocator routes() {
|
||||
return new ProxyRouteLocator(discovery, zuulProperties);
|
||||
}
|
||||
|
||||
@Bean
|
||||
// @RefreshScope
|
||||
public RoutesEndpoint zuulEndpoint() {
|
||||
return new RoutesEndpoint(routes());
|
||||
}
|
||||
|
||||
// pre filters
|
||||
@Bean
|
||||
public PreDecorationFilter preDecorationFilter() {
|
||||
return new PreDecorationFilter(routes(), zuulProperties);
|
||||
}
|
||||
|
||||
// route filters
|
||||
@Bean
|
||||
public RibbonRoutingFilter ribbonRoutingFilter() {
|
||||
ProxyRequestHelper helper = new ProxyRequestHelper();
|
||||
if (traces != null) {
|
||||
helper.setTraces(traces);
|
||||
}
|
||||
RibbonRoutingFilter filter = new RibbonRoutingFilter(helper, clientFactory);
|
||||
return filter;
|
||||
}
|
||||
|
||||
@Bean
|
||||
public SimpleHostRoutingFilter simpleHostRoutingFilter() {
|
||||
ProxyRequestHelper helper = new ProxyRequestHelper();
|
||||
if (traces != null) {
|
||||
helper.setTraces(traces);
|
||||
}
|
||||
return new SimpleHostRoutingFilter(helper);
|
||||
}
|
||||
|
||||
@Bean
|
||||
@Override
|
||||
public ApplicationListener<ApplicationEvent> zuulRefreshRoutesListener() {
|
||||
return new ZuulRefreshListener();
|
||||
}
|
||||
|
||||
private static class ZuulRefreshListener implements
|
||||
ApplicationListener<ApplicationEvent> {
|
||||
|
||||
private AtomicReference<Object> latestHeartbeat = new AtomicReference<>();
|
||||
|
||||
@Autowired
|
||||
private ProxyRouteLocator routeLocator;
|
||||
|
||||
@Autowired
|
||||
ZuulHandlerMapping zuulHandlerMapping;
|
||||
|
||||
@Override
|
||||
public void onApplicationEvent(ApplicationEvent event) {
|
||||
if (event instanceof InstanceRegisteredEvent
|
||||
|| event instanceof RefreshScopeRefreshedEvent
|
||||
|| event instanceof RoutesRefreshedEvent) {
|
||||
reset();
|
||||
}
|
||||
else if (event instanceof DiscoveryHeartbeatEvent) {
|
||||
DiscoveryHeartbeatEvent e = (DiscoveryHeartbeatEvent) event;
|
||||
if (latestHeartbeat.get() == null
|
||||
|| !latestHeartbeat.get().equals(e.getValue())) {
|
||||
latestHeartbeat.set(e.getValue());
|
||||
reset();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private void reset() {
|
||||
routeLocator.resetRoutes();
|
||||
zuulHandlerMapping.registerHandlers();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -46,6 +46,7 @@ public class ProxyRouteLocatorTests {
|
||||
public void testGetMatchingPath() throws Exception {
|
||||
ProxyRouteLocator routeLocator = new ProxyRouteLocator(this.discovery, this.properties);
|
||||
this.properties.getRoutes().put("foo", new ZuulRoute("/foo/**"));
|
||||
properties.init();
|
||||
routeLocator.getRoutes(); // force refresh
|
||||
ProxyRouteSpec route = routeLocator.getMatchingRoute("/foo/1");
|
||||
assertEquals("foo", route.getLocation());
|
||||
@@ -57,6 +58,7 @@ public class ProxyRouteLocatorTests {
|
||||
ProxyRouteLocator routeLocator = new ProxyRouteLocator(this.discovery, this.properties);
|
||||
this.properties.getRoutes().put("foo", new ZuulRoute("/foo/**"));
|
||||
this.properties.setPrefix("/proxy");
|
||||
properties.init();
|
||||
routeLocator.getRoutes(); // force refresh
|
||||
ProxyRouteSpec route = routeLocator.getMatchingRoute("/proxy/foo/1");
|
||||
assertEquals("foo", route.getLocation());
|
||||
@@ -104,6 +106,7 @@ public class ProxyRouteLocatorTests {
|
||||
ZuulRoute zuulRoute = new ZuulRoute("/foo/**");
|
||||
zuulRoute.setStripPrefix(true);
|
||||
this.properties.getRoutes().put("foo", zuulRoute);
|
||||
properties.init();
|
||||
routeLocator.getRoutes(); // force refresh
|
||||
ProxyRouteSpec route = routeLocator.getMatchingRoute("/foo/1");
|
||||
assertEquals("foo", route.getLocation());
|
||||
@@ -114,6 +117,7 @@ public class ProxyRouteLocatorTests {
|
||||
public void testGetRoutes() {
|
||||
ProxyRouteLocator routeLocator = new ProxyRouteLocator(this.discovery, this.properties);
|
||||
this.properties.getRoutes().put(ASERVICE, new ZuulRoute("/"+ASERVICE + "/**"));
|
||||
properties.init();
|
||||
|
||||
Map<String, String> routesMap = routeLocator.getRoutes();
|
||||
|
||||
|
||||
@@ -1,96 +0,0 @@
|
||||
package org.springframework.cloud.netflix.zuul;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
|
||||
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
|
||||
import org.springframework.cloud.netflix.ribbon.RibbonClient;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMethod;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import com.netflix.appinfo.EurekaInstanceConfig;
|
||||
import com.netflix.loadbalancer.BaseLoadBalancer;
|
||||
import com.netflix.loadbalancer.ILoadBalancer;
|
||||
import com.netflix.loadbalancer.Server;
|
||||
import com.netflix.zuul.ZuulFilter;
|
||||
|
||||
// Don't use @SpringBootApplication because we don't want to component scan
|
||||
@Configuration
|
||||
@EnableAutoConfiguration
|
||||
@RestController
|
||||
@EnableZuulProxy
|
||||
@EnableDiscoveryClient
|
||||
@RibbonClient(name = "simple", configuration = SimpleRibbonClientConfiguration.class)
|
||||
public class SampleZuulProxyApplication {
|
||||
|
||||
@RequestMapping("/testing123")
|
||||
public String testing123() {
|
||||
throw new RuntimeException("myerror");
|
||||
}
|
||||
|
||||
@RequestMapping("/local")
|
||||
public String local() {
|
||||
return "Hello local";
|
||||
}
|
||||
|
||||
@RequestMapping(value = "/local/{id}", method = RequestMethod.DELETE)
|
||||
public String delete() {
|
||||
return "Deleted!";
|
||||
}
|
||||
|
||||
@RequestMapping(value = "/local/{id}", method = RequestMethod.GET)
|
||||
public String get() {
|
||||
return "Gotten!";
|
||||
}
|
||||
|
||||
@RequestMapping("/")
|
||||
public String home() {
|
||||
return "Hello world";
|
||||
}
|
||||
|
||||
@Bean
|
||||
public ZuulFilter sampleFilter() {
|
||||
return new ZuulFilter() {
|
||||
@Override
|
||||
public String filterType() {
|
||||
return "pre";
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean shouldFilter() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object run() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int filterOrder() {
|
||||
return 0;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public static void main(String[] args) {
|
||||
SpringApplication.run(SampleZuulProxyApplication.class, args);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Load balancer with fixed server list for "simple" pointing to localhost
|
||||
@Configuration
|
||||
class SimpleRibbonClientConfiguration {
|
||||
@Bean
|
||||
public ILoadBalancer ribbonLoadBalancer(EurekaInstanceConfig instance) {
|
||||
BaseLoadBalancer balancer = new BaseLoadBalancer();
|
||||
balancer.setServersList(Arrays.asList(new Server("localhost", instance
|
||||
.getNonSecurePort())));
|
||||
return balancer;
|
||||
}
|
||||
}
|
||||
@@ -2,13 +2,20 @@ package org.springframework.cloud.netflix.zuul;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
|
||||
import org.springframework.boot.test.IntegrationTest;
|
||||
import org.springframework.boot.test.SpringApplicationConfiguration;
|
||||
import org.springframework.boot.test.TestRestTemplate;
|
||||
import org.springframework.cloud.netflix.ribbon.RibbonClient;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.http.HttpEntity;
|
||||
import org.springframework.http.HttpMethod;
|
||||
import org.springframework.http.HttpStatus;
|
||||
@@ -16,6 +23,15 @@ import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.test.annotation.DirtiesContext;
|
||||
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
|
||||
import org.springframework.test.context.web.WebAppConfiguration;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMethod;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import com.netflix.appinfo.EurekaInstanceConfig;
|
||||
import com.netflix.loadbalancer.BaseLoadBalancer;
|
||||
import com.netflix.loadbalancer.ILoadBalancer;
|
||||
import com.netflix.loadbalancer.Server;
|
||||
import com.netflix.zuul.ZuulFilter;
|
||||
|
||||
@RunWith(SpringJUnit4ClassRunner.class)
|
||||
@SpringApplicationConfiguration(classes = SampleZuulProxyApplication.class)
|
||||
@@ -33,7 +49,7 @@ public class SampleZuulProxyApplicationTests {
|
||||
private ProxyRouteLocator routes;
|
||||
|
||||
@Autowired
|
||||
private ZuulHandlerMapping mapping;
|
||||
private RoutesEndpoint endpoint;
|
||||
|
||||
@Test
|
||||
public void bindRouteUsingPhysicalRoute() {
|
||||
@@ -47,7 +63,6 @@ public class SampleZuulProxyApplicationTests {
|
||||
|
||||
@Test
|
||||
public void getOnSelfViaRibbonRoutingFilter() {
|
||||
mapping.reset();
|
||||
ResponseEntity<String> result = new TestRestTemplate().exchange(
|
||||
"http://localhost:" + port + "/simple/local/1", HttpMethod.GET,
|
||||
new HttpEntity<Void>((Void) null), String.class);
|
||||
@@ -58,7 +73,7 @@ public class SampleZuulProxyApplicationTests {
|
||||
@Test
|
||||
public void deleteOnSelfViaSimpleHostRoutingFilter() {
|
||||
routes.addRoute("/self/**", "http://localhost:" + port + "/local");
|
||||
mapping.reset();
|
||||
endpoint.reset();
|
||||
ResponseEntity<String> result = new TestRestTemplate().exchange(
|
||||
"http://localhost:" + port + "/self/1", HttpMethod.DELETE,
|
||||
new HttpEntity<Void>((Void) null), String.class);
|
||||
@@ -67,3 +82,79 @@ public class SampleZuulProxyApplicationTests {
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
//Don't use @SpringBootApplication because we don't want to component scan
|
||||
@Configuration
|
||||
@EnableAutoConfiguration
|
||||
@RestController
|
||||
@EnableZuulProxy
|
||||
@RibbonClient(name = "simple", configuration = SimpleRibbonClientConfiguration.class)
|
||||
class SampleZuulProxyApplication {
|
||||
|
||||
@RequestMapping("/testing123")
|
||||
public String testing123() {
|
||||
throw new RuntimeException("myerror");
|
||||
}
|
||||
|
||||
@RequestMapping("/local")
|
||||
public String local() {
|
||||
return "Hello local";
|
||||
}
|
||||
|
||||
@RequestMapping(value = "/local/{id}", method = RequestMethod.DELETE)
|
||||
public String delete() {
|
||||
return "Deleted!";
|
||||
}
|
||||
|
||||
@RequestMapping(value = "/local/{id}", method = RequestMethod.GET)
|
||||
public String get() {
|
||||
return "Gotten!";
|
||||
}
|
||||
|
||||
@RequestMapping("/")
|
||||
public String home() {
|
||||
return "Hello world";
|
||||
}
|
||||
|
||||
@Bean
|
||||
public ZuulFilter sampleFilter() {
|
||||
return new ZuulFilter() {
|
||||
@Override
|
||||
public String filterType() {
|
||||
return "pre";
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean shouldFilter() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object run() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int filterOrder() {
|
||||
return 0;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public static void main(String[] args) {
|
||||
SpringApplication.run(SampleZuulProxyApplication.class, args);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
//Load balancer with fixed server list for "simple" pointing to localhost
|
||||
@Configuration
|
||||
class SimpleRibbonClientConfiguration {
|
||||
@Bean
|
||||
public ILoadBalancer ribbonLoadBalancer(EurekaInstanceConfig instance) {
|
||||
BaseLoadBalancer balancer = new BaseLoadBalancer();
|
||||
balancer.setServersList(Arrays.asList(new Server("localhost", instance
|
||||
.getNonSecurePort())));
|
||||
return balancer;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,112 @@
|
||||
package org.springframework.cloud.netflix.zuul;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
|
||||
import org.springframework.boot.test.IntegrationTest;
|
||||
import org.springframework.boot.test.SpringApplicationConfiguration;
|
||||
import org.springframework.boot.test.TestRestTemplate;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.http.HttpEntity;
|
||||
import org.springframework.http.HttpMethod;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.test.annotation.DirtiesContext;
|
||||
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
|
||||
import org.springframework.test.context.web.WebAppConfiguration;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import com.netflix.zuul.ZuulFilter;
|
||||
|
||||
@RunWith(SpringJUnit4ClassRunner.class)
|
||||
@SpringApplicationConfiguration(classes = SimpleZuulServerApplication.class)
|
||||
@WebAppConfiguration
|
||||
@IntegrationTest({ "server.port: 0"})
|
||||
@DirtiesContext
|
||||
public class SimpleZuulServerApplicationTests {
|
||||
|
||||
@Value("${local.server.port}")
|
||||
private int port;
|
||||
|
||||
@Autowired
|
||||
private RouteLocator routes;
|
||||
|
||||
@Test
|
||||
public void bindRoute() {
|
||||
assertTrue(routes.getRoutePaths().contains("/testing123/**"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getOnSelf() {
|
||||
ResponseEntity<String> result = new TestRestTemplate().exchange(
|
||||
"http://localhost:" + port + "/", HttpMethod.GET,
|
||||
new HttpEntity<Void>((Void) null), String.class);
|
||||
assertEquals(HttpStatus.OK, result.getStatusCode());
|
||||
assertEquals("Hello world", result.getBody());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getOnSelfViaFilter() {
|
||||
ResponseEntity<String> result = new TestRestTemplate().exchange(
|
||||
"http://localhost:" + port + "/testing123/1", HttpMethod.GET,
|
||||
new HttpEntity<Void>((Void) null), String.class);
|
||||
assertEquals(HttpStatus.OK, result.getStatusCode());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
//Don't use @SpringBootApplication because we don't want to component scan
|
||||
@Configuration
|
||||
@EnableAutoConfiguration
|
||||
@RestController
|
||||
@EnableZuulServer
|
||||
class SimpleZuulServerApplication {
|
||||
|
||||
@RequestMapping("/local")
|
||||
public String local() {
|
||||
return "Hello local";
|
||||
}
|
||||
|
||||
@RequestMapping("/")
|
||||
public String home() {
|
||||
return "Hello world";
|
||||
}
|
||||
|
||||
@Bean
|
||||
public ZuulFilter sampleFilter() {
|
||||
return new ZuulFilter() {
|
||||
@Override
|
||||
public String filterType() {
|
||||
return "pre";
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean shouldFilter() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object run() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int filterOrder() {
|
||||
return 0;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public static void main(String[] args) {
|
||||
SpringApplication.run(SampleZuulProxyApplication.class, args);
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user