1.EnableZuulProxy的初始化
1.1 EnableZuulProxy源码分析
跟EnableZuulServer类似,引入了ZuulProxyMarkerConfiguration类
@EnableCircuitBreaker
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Import(ZuulProxyMarkerConfiguration.class)
public @interface EnableZuulProxy {
}
1.2 ZuulProxyMarkerConfiguration源码分析
同样的生成了了内部类Maker对象。link ZuulProxyAutoConfiguration
/**
* Responsible for adding in a marker bean to trigger activation of
* {@link ZuulProxyAutoConfiguration}
*
* @author Biju Kunjummen
*/
@Configuration
public class ZuulProxyMarkerConfiguration {
@Bean
public Marker zuulProxyMarkerBean() {
return new Marker();
}
class Marker {
}
}
1.3 ZuulProxyAutoConfiguration源码分析
从名字上看是ZuulAutoConfiguration的代理增强类,继承了ZuulAutoConfiguration,通过@ConditionalOnBean(ZuulProxyMarkerConfiguration.Marker.class)引入,额外进行了扩展。
1.3.1 DiscoveryClientRouteLocator
@ConditionalOnMissingBean标注,主要用于eureka服务列表刷新,详细分析见后续章节。
@Bean
@ConditionalOnMissingBean(DiscoveryClientRouteLocator.class)
public DiscoveryClientRouteLocator discoveryRouteLocator() {
return new DiscoveryClientRouteLocator(this.server.getServletPrefix(),
this.discovery, this.zuulProperties, this.serviceRouteMapper, this.registration);
}
1.3.2 相关过滤器
1)preDecorationFilter
过滤器 | 类型 | order | 执行条件 | 描述 |
---|---|---|---|---|
PreDetectionFilter | pre | 5 | 非forward重定向及sereviceId(微服务)相关路由,则会执行 | 主要对当前请求做预处理,如路由规则匹配等。对head头请求处ctx.setRouteHost(getUrl(location))这段代码需要注意,是simpleHostRoutingFilter执行的前提条件。http跟https且不是微服务的请求会交给simpleHostRoutingFilter处理 |
@Override
public boolean shouldFilter() {
RequestContext ctx = RequestContext.getCurrentContext();
return !ctx.containsKey(FORWARD_TO_KEY) // a filter has already forwarded
&& !ctx.containsKey(SERVICE_ID_KEY); // a filter has already determined serviceId
}
@Override
public Object run() {
RequestContext ctx = RequestContext.getCurrentContext();
final String requestURI = this.urlPathHelper.getPathWithinApplication(ctx.getRequest());
Route route = this.routeLocator.getMatchingRoute(requestURI);
if (route != null) {
String location = route.getLocation();
if (location != null) {
ctx.put(REQUEST_URI_KEY, route.getPath());
ctx.put(PROXY_KEY, route.getId());
if (!route.isCustomSensitiveHeaders()) {
this.proxyRequestHelper
.addIgnoredHeaders(this.properties.getSensitiveHeaders().toArray(new String[0]));
}
else {
this.proxyRequestHelper.addIgnoredHeaders(route.getSensitiveHeaders().toArray(new String[0]));
}
if (route.getRetryable() != null) {
ctx.put(RETRYABLE_KEY, route.getRetryable());
}
if (location.startsWith(HTTP_SCHEME+":") || location.startsWith(HTTPS_SCHEME+":")) {
ctx.setRouteHost(getUrl(location));
ctx.addOriginResponseHeader(SERVICE_HEADER, location);
}
else if (location.startsWith(FORWARD_LOCATION_PREFIX)) {
ctx.set(FORWARD_TO_KEY,
StringUtils.cleanPath(location.substring(FORWARD_LOCATION_PREFIX.length()) + route.getPath()));
ctx.setRouteHost(null);
return null;
}
else {
// set serviceId for use in filters.route.RibbonRequest
ctx.set(SERVICE_ID_KEY, location);
ctx.setRouteHost(null);
ctx.addOriginResponseHeader(SERVICE_ID_HEADER, location);
}
if (this.properties.isAddProxyHeaders()) {
addProxyHeaders(ctx, route);
String xforwardedfor = ctx.getRequest().getHeader(X_FORWARDED_FOR_HEADER);
String remoteAddr = ctx.getRequest().getRemoteAddr();
if (xforwardedfor == null) {
xforwardedfor = remoteAddr;
}
else if (!xforwardedfor.contains(remoteAddr)) { // Prevent duplicates
xforwardedfor += ", " + remoteAddr;
}
ctx.addZuulRequestHeader(X_FORWARDED_FOR_HEADER, xforwardedfor);
}
if (this.properties.isAddHostHeader()) {
ctx.addZuulRequestHeader(HttpHeaders.HOST, toHostHeader(ctx.getRequest()));
}
}
}
else {
log.warn("No route found for uri: " + requestURI);
String fallBackUri = requestURI;
String fallbackPrefix = this.dispatcherServletPath; // default fallback
// servlet is
// DispatcherServlet
if (RequestUtils.isZuulServletRequest()) {
// remove the Zuul servletPath from the requestUri
log.debug("zuulServletPath=" + this.properties.getServletPath());
fallBackUri = fallBackUri.replaceFirst(this.properties.getServletPath(), "");
log.debug("Replaced Zuul servlet path:" + fallBackUri);
}
else {
// remove the DispatcherServlet servletPath from the requestUri
log.debug("dispatcherServletPath=" + this.dispatcherServletPath);
fallBackUri = fallBackUri.replaceFirst(this.dispatcherServletPath, "");
log.debug("Replaced DispatcherServlet servlet path:" + fallBackUri);
}
if (!fallBackUri.startsWith("/")) {
fallBackUri = "/" + fallBackUri;
}
String forwardURI = fallbackPrefix + fallBackUri;
forwardURI = forwardURI.replaceAll("//", "/");
ctx.set(FORWARD_TO_KEY, forwardURI);
}
return null;
}
2)RibbonRoutingFilter
过滤器 | 类型 | order | 执行条件 | 描述 |
---|---|---|---|---|
RibbonRoutingFilter | route | 10 | 上下文包含serviceId,routeHost为空,且zuulResponse(决绝条件)为真执行 | 通过ribbon做负载均衡 |
@Override
public boolean shouldFilter() {
RequestContext ctx = RequestContext.getCurrentContext();
return (ctx.getRouteHost() == null && ctx.get(SERVICE_ID_KEY) != null
&& ctx.sendZuulResponse());
}
@Override
public Object run() {
RequestContext context = RequestContext.getCurrentContext();
this.helper.addIgnoredHeaders();
try {
RibbonCommandContext commandContext = buildCommandContext(context);
ClientHttpResponse response = forward(commandContext);
setResponse(response);
return response;
}
catch (ZuulException ex) {
throw new ZuulRuntimeException(ex);
}
catch (Exception ex) {
throw new ZuulRuntimeException(ex);
}
}
3)SimpleHostRoutingFilter
过滤器 | 类型 | order | 执行条件 | 描述 |
---|---|---|---|---|
SimpleHostRoutingFilter | route | 100 | routeHost(PreDecorationFilter设置)不为空,且zuulResponse(决绝条件)为真执行,可以看到route过滤器都有zuulResponse的相关条件,若在前置过程中判断无需通过路由请求,则可以设置决绝条件为false | 该调用是通过httpClient请求,所以默认是无负载均衡跟熔断能力,若采用该策略,一般需要下游服务有负载均衡能力,且需自行实现熔断机制 |
@Override
public boolean shouldFilter() {
return RequestContext.getCurrentContext().getRouteHost() != null
&& RequestContext.getCurrentContext().sendZuulResponse();
}
@Override
public Object run() {
RequestContext context = RequestContext.getCurrentContext();
HttpServletRequest request = context.getRequest();
MultiValueMap<String, String> headers = this.helper
.buildZuulRequestHeaders(request);
MultiValueMap<String, String> params = this.helper
.buildZuulRequestQueryParams(request);
String verb = getVerb(request);
InputStream requestEntity = getRequestBody(request);
if (request.getContentLength() < 0) {
context.setChunkedRequestBody();
}
String uri = this.helper.buildZuulRequestURI(request);
this.helper.addIgnoredHeaders();
try {
CloseableHttpResponse response = forward(this.httpClient, verb, uri, request,
headers, params, requestEntity);
setResponse(response);
}
catch (Exception ex) {
throw new ZuulRuntimeException(ex);
}
return null;
}
另在初始化该过滤器时,提供了用户自定义扩展httpClient的方式。用户只需自行实现CloseableHttpClient,并注入到spring ioc容器。
@Bean
@ConditionalOnMissingBean({SimpleHostRoutingFilter.class, CloseableHttpClient.class})
public SimpleHostRoutingFilter simpleHostRoutingFilter(ProxyRequestHelper helper,
ZuulProperties zuulProperties,
ApacheHttpClientConnectionManagerFactory connectionManagerFactory,
ApacheHttpClientFactory httpClientFactory) {
return new SimpleHostRoutingFilter(helper, zuulProperties,
connectionManagerFactory, httpClientFactory);
}
@Bean
@ConditionalOnMissingBean({SimpleHostRoutingFilter.class})
public SimpleHostRoutingFilter simpleHostRoutingFilter2(ProxyRequestHelper helper,
ZuulProperties zuulProperties,
CloseableHttpClient httpClient) {
return new SimpleHostRoutingFilter(helper, zuulProperties,
httpClient);
}
3)ZuulDiscoveryRefreshListener
与ZuulRefreshListener类似,监听事件中也有HearbeatEvent,所以eureka发送心跳后,如果网关中使用的proxy注解,则都会更新路由缓存,重复刷新,但无并发问题。
private static class ZuulDiscoveryRefreshListener
implements ApplicationListener<ApplicationEvent> {
private HeartbeatMonitor monitor = new HeartbeatMonitor();
@Autowired
private ZuulHandlerMapping zuulHandlerMapping;
@Override
public void onApplicationEvent(ApplicationEvent event) {
if (event instanceof InstanceRegisteredEvent) {
reset();
}
else if (event instanceof ParentHeartbeatEvent) {
ParentHeartbeatEvent e = (ParentHeartbeatEvent) event;
resetIfNeeded(e.getValue());
}
else if (event instanceof HeartbeatEvent) {
HeartbeatEvent e = (HeartbeatEvent) event;
resetIfNeeded(e.getValue());
}
}
private void resetIfNeeded(Object value) {
if (this.monitor.update(value)) {
reset();
}
}
private void reset() {
this.zuulHandlerMapping.setDirty(true);
}
}