SpringCloud-原始碼分析 zuul (一)
本文作者:陳剛,叩丁狼高階講師。原創文章,轉載請註明出處。
zuul捨命週期
zuul,在SpringCloud中充當服務閘道器的角色,它包含了請求路由,過濾,安全等功能,可以說是我們web應用的“安保人員”,保證了我們“微服務園區”的安全,那麼zuul是如何實現路由和過濾等功能的呢?我這裡有一張摘抄於SpringCloud官網的zuul的生命週期圖片
叩丁狼教育.png
這張圖的大致流程為:
1.當客戶端請求過來首先會到 "pre" filters 這樣的一個前置過濾器做一些處理,然後呼叫自定義的過濾器
2.前置過濾器執行完了之後會呼叫 “routing”filter 過濾器 ,看名字都知道這是做路由分發的過濾器
3.在路由的過程中出現了異常,那麼會走 “error”filters過濾器,然後再走 "post"filters 過濾器 ,或者正常路由完成也會走到“post”filters
4."post"filters過濾器負責處理響應 ,最後把結果響應給客戶端
這裡是zuul大致的生命週期流程,我們看到它這裡大量用到了filter進行處理,並且Zuul允許我們自定義Filter ,他提供了抽象的 ZuulFilter 過濾器,裡面有四個基本方法,我們要自定義Filter就需要繼承ZuulFilter,然後複寫四個方法
/** 服務過濾 */ @Component public class MyFilter extends ZuulFilter { /** 返回過濾器的型別,過濾器型別如下: pre:請求路由之前呼叫過濾 routing:請求routing之時呼叫過濾 post: 請求路由之後呼叫過濾 error:傳送錯誤時呼叫過濾 */ @Override public String filterType() { return "pre"; } //filterOrder:過濾的順序 @Override public int filterOrder() { return 0; } //shouldFilter:是否要過濾,true表示永遠過濾。我們可以在這裡做一寫過濾處理 @Override public boolean shouldFilter() { return true; } //當前過濾器的執行方法 //我們可以在該方法中處理一些自己的判斷 @Override public Object run() { //獲取請求物件 RequestContext ctx = RequestContext.getCurrentContext(); Object pass = ctx.getRequest().getParameter("pass"); if(pass == null) { ctx.setSendZuulResponse(false); ctx.setResponseStatusCode(401); try { ctx.getResponse().getWriter().write("pass is empty"); }catch (Exception e){} } return null; } }
那麼我們接下來就分析他的原始碼就是去看這些內建的filter做了什麼事情。
zuul的啟動/配置
簡單回顧一下zuul的使用 ,除了引入zuul相關依賴而外,我們要使用zull還需要在配置類上開啟zuul功能
//@EnableZuulProxy :開啟路由閘道器功能 @SpringBootApplication @EnableZuulProxy public class ServiceZuulApplication { public static void main(String[] args) { SpringApplication.run(ServiceZuulApplication.class, args); } }
EnableZuulProxy的註釋告訴我們,這裡設設定Zuul伺服器端點,和安裝了一些過濾器,通過這些過濾器它可以轉發請求到後端伺服器
/**
* 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 DiscoveryClient.
*
* @see EnableZuulServer for how to get a Zuul server without any proxying
*
* @author Spencer Gibb
* @author Dave Syer
* @author Biju Kunjummen
*/
@EnableCircuitBreaker
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Import(ZuulProxyMarkerConfiguration.class)
public @interface EnableZuulProxy {
}
不過這裡引入了 ZuulProxyMarkerConfiguration 配置類,這個配置在幹嘛呢?
/**
* 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 {
}
}
翻譯:“Responsible for adding in a marker bean to trigger activation of
- {@link ZuulProxyAutoConfiguration}”
它在負責新增標記bean以觸發啟用 ZuulProxyAutoConfiguration 這個類,研究過springboot自動配置的同學就會知道 ,SpringBoot 中會有大量的 xxxAutoConfiguration 自動配置的類會在應用啟動的過程中被啟用實現自動裝配,從而節省了我們很多的配置。
而這個類在配置些什麼東西?
/**
* @author Spencer Gibb
* @author Dave Syer
* @author Biju Kunjummen
*/
@Configuration
//這裡引入了幾種客戶端配置
@Import({ RibbonCommandFactoryConfiguration.RestClientRibbonConfiguration.class,
RibbonCommandFactoryConfiguration.OkHttpRibbonConfiguration.class,
RibbonCommandFactoryConfiguration.HttpClientRibbonConfiguration.class,
HttpClientConfiguration.class })
//如果存在了 ZuulProxyMarkerConfiguration.Marker的例項,該配置生效,這裡是滿足條件的
@ConditionalOnBean(ZuulProxyMarkerConfiguration.Marker.class)
public class ZuulProxyAutoConfiguration extends ZuulServerAutoConfiguration {
...省略程式碼...
ZuulProxyAutoConfiguration繼承了 ZuulServerAutoConfiguration ,我們先看下這個配置類
/**
* @author Spencer Gibb
* @author Dave Syer
* @author Biju Kunjummen
*/
@Configuration
@EnableConfigurationProperties({ ZuulProperties.class })
@ConditionalOnClass(ZuulServlet.class)
@ConditionalOnBean(ZuulServerMarkerConfiguration.Marker.class)
// Make sure to get the ServerProperties from the same place as a normal web app would
// FIXME @Import(ServerPropertiesAutoConfiguration.class)
public class ZuulServerAutoConfiguration {
//繫結zuul的配置資訊
@Autowired
protected ZuulProperties zuulProperties;
@Autowired
protected ServerProperties server;
//注入請求錯誤控制器
@Autowired(required = false)
private ErrorController errorController;
@Bean
public HasFeatures zuulFeature() {
return HasFeatures.namedFeature("Zuul (Simple)", ZuulServerAutoConfiguration.class);
}
//RouteLocator that composes multiple RouteLocators. :
//多路由組合定位器
@Bean
@Primary
public CompositeRouteLocator primaryRouteLocator(
Collection<RouteLocator> routeLocators) {
return new CompositeRouteLocator(routeLocators);
}
//簡單的路由定位器
@Bean
@ConditionalOnMissingBean(SimpleRouteLocator.class)
public SimpleRouteLocator simpleRouteLocator() {
return new SimpleRouteLocator(this.server.getServlet().getServletPrefix(),
this.zuulProperties);
}
@Bean
public ZuulController zuulController() {
return new ZuulController();
}
// MVC HandlerMapping that maps incoming request paths to remote services.
//看名字也知道,他是做請求路徑和遠端服務的對映的,是 HandlerMapping的實現
@Bean
public ZuulHandlerMapping zuulHandlerMapping(RouteLocator routes) {
ZuulHandlerMapping mapping = new ZuulHandlerMapping(routes, zuulController());
mapping.setErrorController(this.errorController);
return mapping;
}
//定義ZuulRefreshListener zuul重新整理的監聽器
@Bean
public ApplicationListener<ApplicationEvent> zuulRefreshRoutesListener() {
return new ZuulRefreshListener();
}
// Core Zuul servlet which intializes and orchestrates zuulFilter execution
//這裡在註冊ZuulServlet 這樣的一個servlet, 這個東西了不得了,
//他是負責核心Zuul servlet初始化和呼叫zuulFilter執行,跟DispatcherServlet差不過
@Bean
@ConditionalOnMissingBean(name = "zuulServlet")
public ServletRegistrationBean zuulServlet() {
ServletRegistrationBean<ZuulServlet> servlet = new ServletRegistrationBean<>(new ZuulServlet(),
this.zuulProperties.getServletPattern());
// The whole point of exposing this servlet is to provide a route that doesn't
// buffer requests.
servlet.addInitParameter("buffer-requests", "false");
return servlet;
}
// pre filters :
//前置過濾器,看名字是用來做檢測的
@Bean
public ServletDetectionFilter servletDetectionFilter() {
return new ServletDetectionFilter();
}
//前置過濾器,是對請求資料做一些增強處理
@Bean
public FormBodyWrapperFilter formBodyWrapperFilter() {
return new FormBodyWrapperFilter();
}
@Bean
public DebugFilter debugFilter() {
return new DebugFilter();
}
@Bean
public Servlet30WrapperFilter servlet30WrapperFilter() {
return new Servlet30WrapperFilter();
}
// post filters
//下面是定義一系列的後置過濾器
@Bean
public SendResponseFilter sendResponseFilter(ZuulProperties properties) {
return new SendResponseFilter(zuulProperties);
}
@Bean
public SendErrorFilter sendErrorFilter() {
return new SendErrorFilter();
}
@Bean
public SendForwardFilter sendForwardFilter() {
return new SendForwardFilter();
}
整理一下這裡配置類裡面做了哪些事情呢?
1.註冊了多路由組合定位器 CompositeRouteLocator
2.註冊了簡單的路由定位器SimpleRouteLocator
3.註冊了ZuulController ,zuulServlet會通過呼叫它再,實現對請求的呼叫他的原始碼如下
**
* @author Spencer Gibb
*/
public class ZuulController extends ServletWrappingController {
public ZuulController() {
setServletClass(ZuulServlet.class);
setServletName("zuul");
setSupportedMethods((String[]) null); // Allow all
}
@Override
public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception {
try {
// We don't care about the other features of the base class, just want to
// handle the request
//處理請求
return super.handleRequestInternal(request, response);
}
finally {
// @see com.netflix.zuul.context.ContextLifecycleFilter.doFilter
RequestContext.getCurrentContext().unset();
}
}
}
4.註冊了ZuulHandlerMapping 是對path和遠端服務的對映
- 註冊了zuulServlet : 請求的分發器類似於DispatcherServlet
6.定義了一系列的前置過濾器和後置過濾器,作用分別如下:
ServletDetectionFilter : 標記處理servlet的型別,前置通知 ,執行順序 -3
/**
* Detects whether a request is ran through the {@link DispatcherServlet} or {@link ZuulServlet}.
* The purpose was to detect this up-front at the very beginning of Zuul filter processing
* and rely on this information in all filters.
* RequestContext is used such that the information is accessible to classes
* which do not have a request reference.
* @author Adrian Ivan
*/
public class ServletDetectionFilter extends ZuulFilter {
public ServletDetectionFilter() {
}
//前置通知
@Override
public String filterType() {
return PRE_TYPE;
}
/**
* Must run before other filters that rely on the difference between
* DispatcherServlet and ZuulServlet.
*/
//filterOrder 決定了這個過濾器的執行順序 這裡是 :-3 見
//public static final int SERVLET_DETECTION_FILTER_ORDER = -3;
@Override
public int filterOrder() {
return SERVLET_DETECTION_FILTER_ORDER;
}
@Override
public boolean shouldFilter() {
return true;
}
@Override
public Object run() {
RequestContext ctx = RequestContext.getCurrentContext();
//判斷結果儲存到 HttpServletRequest中
HttpServletRequest request = ctx.getRequest();
if (!(request instanceof HttpServletRequestWrapper)
&& isDispatcherServletRequest(request)) {
ctx.set(IS_DISPATCHER_SERVLET_REQUEST_KEY, true);
} else {
ctx.set(IS_DISPATCHER_SERVLET_REQUEST_KEY, false);
}
return null;
}
//判斷當前請求是否是DispatcherServletRequest
private boolean isDispatcherServletRequest(HttpServletRequest request) {
return request.getAttribute(DispatcherServlet.WEB_APPLICATION_CONTEXT_ATTRIBUTE) != null;
}
翻譯過濾器上面的註釋"Detects whether a request is ran through the {@link DispatcherServlet} or {@link ZuulServlet}"大致功能為:用來標記該請求是通過 DispatcherServlet處理還是通過 ZuulServlet處理
,run()把判斷結果以boolean值的方式儲存到HttpServletRequest中,後續的處理中就可以通過它獲取到這個標記做不同的處理,而這個filter執行的順序是 -3(filterOrder() 方法) ,越小越先執行
FormBodyWrapperFilter :解析表單資料併為後續處理重新編碼,將符合要求的請求體包裝成FormBodyRequestWrapper物件。前置通知,執行順序 -1
/**
* Pre {@link ZuulFilter} that parses form data and reencodes it for downstream services
*
* @author Dave Syer
*/
public class FormBodyWrapperFilter extends ZuulFilter {
...省略...
@Override
public String filterType() {
//前置通知
return PRE_TYPE;
}
@Override
public int filterOrder() {
//執行順序 -1
return FORM_BODY_WRAPPER_FILTER_ORDER;
}
@Override
public Object run() {
RequestContext ctx = RequestContext.getCurrentContext();
//處理請求
HttpServletRequest request = ctx.getRequest();
FormBodyRequestWrapper wrapper = null;
if (request instanceof HttpServletRequestWrapper) {
HttpServletRequest wrapped = (HttpServletRequest) ReflectionUtils
.getField(this.requestField, request);
wrapper = new FormBodyRequestWrapper(wrapped);
ReflectionUtils.setField(this.requestField, request, wrapper);
if (request instanceof ServletRequestWrapper) {
ReflectionUtils.setField(this.servletRequestField, request, wrapper);
}
}
else {
//包裝成 FormBodyRequestWrapper
wrapper = new FormBodyRequestWrapper(request);
ctx.setRequest(wrapper);
}
if (wrapper != null) {
ctx.getZuulRequestHeaders().put("content-type", wrapper.getContentType());
}
return null;
}
...省略...
DebugFilter :開啟除錯標記 ,前置通知 ,執行順序 1
/**
* Pre {@link ZuulFilter} that sets {@link RequestContext} debug attributes to true if
* the "debug" request parameter is set.
*
* @author Spencer Gibb
*/
public class DebugFilter extends ZuulFilter {
private static final DynamicBooleanProperty ROUTING_DEBUG = DynamicPropertyFactory
.getInstance().getBooleanProperty(ZuulConstants.ZUUL_DEBUG_REQUEST, false);
private static final DynamicStringProperty DEBUG_PARAMETER = DynamicPropertyFactory
.getInstance().getStringProperty(ZuulConstants.ZUUL_DEBUG_PARAMETER, "debug");
@Override
public String filterType() {
return PRE_TYPE;
}
@Override
public int filterOrder() {
return DEBUG_FILTER_ORDER;
}
@Override
public boolean shouldFilter() {
HttpServletRequest request = RequestContext.getCurrentContext().getRequest();
if ("true".equals(request.getParameter(DEBUG_PARAMETER.get()))) {
return true;
}
return ROUTING_DEBUG.get();
}
翻譯註釋“Pre {@link ZuulFilter} that sets {@link RequestContext} debug attributes to true if the "debug" request parameter is set.”
如果請求中設定了“debug”請求引數, RequestContext除錯屬性設定為true。說白了就是通過 reques中的debug引數來啟用除錯資訊,這樣當線上環境出現問題的時候,可以通過請求引數的方式來啟用這些debug資訊以幫助分析問題
Servlet30WrapperFilter :包裝http請求 ,前置通知 ,執行順序 -2
/**
* Pre {@link ZuulFilter} that wraps requests in a Servlet 3.0 compliant wrapper.
* Zuul's default wrapper is only Servlet 2.5 compliant.
* @author Spencer Gibb
*/
public class Servlet30WrapperFilter extends ZuulFilter {
private Field requestField = null;
public Servlet30WrapperFilter() {
this.requestField = ReflectionUtils.findField(HttpServletRequestWrapper.class,
"req", HttpServletRequest.class);
Assert.notNull(this.requestField,
"HttpServletRequestWrapper.req field not found");
this.requestField.setAccessible(true);
}
protected Field getRequestField() {
return this.requestField;
}
@Override
public String filterType() {
return PRE_TYPE;
}
@Override
public int filterOrder() {
return SERVLET_30_WRAPPER_FILTER_ORDER;
}
@Override
public Object run() {
//把請求包裝成 Servlet30RequestWrapper
RequestContext ctx = RequestContext.getCurrentContext();
HttpServletRequest request = ctx.getRequest();
if (request instanceof HttpServletRequestWrapper) {
request = (HttpServletRequest) ReflectionUtils.getField(this.requestField,
request);
ctx.setRequest(new Servlet30RequestWrapper(request));
}
else if (RequestUtils.isDispatcherServletRequest()) {
// If it's going through the dispatcher we need to buffer the body
ctx.setRequest(new Servlet30RequestWrapper(request));
}
return null;
}
翻譯註釋:“that wraps requests in a Servlet 3.0 compliant wrapper.
Zuul's default wrapper is only Servlet 2.5 compliant.”
這裡是對原始的HttpServletRequest請求包裝成Servlet30RequestWrapper物件即要相容3.0。zuul預設只是相容2.5,你現在知道為什麼他叫 Servlet30WrapperFilter 了嗎?
SendResponseFilter :後置通知 ,處理請求響應,執行順序 1000
/**
* Post {@link ZuulFilter} that writes responses from proxied requests to the current response.
*
* @author Spencer Gibb
* @author Dave Syer
* @author Ryan Baxter
*/
public class SendResponseFilter extends ZuulFilter {
...省略...
@Override
public Object run() {
try {
addResponseHeaders();
writeResponse();
}
catch (Exception ex) {
ReflectionUtils.rethrowRuntimeException(ex);
}
return null;
}
private void writeResponse() throws Exception {
RequestContext context = RequestContext.getCurrentContext();
// there is no body to send
if (context.getResponseBody() == null
&& context.getResponseDataStream() == null) {
return;
}
HttpServletResponse servletResponse = context.getResponse();
if (servletResponse.getCharacterEncoding() == null) { // only set if not set
servletResponse.setCharacterEncoding("UTF-8");
}
OutputStream outStream = servletResponse.getOutputStream();
InputStream is = null;
try {
if (RequestContext.getCurrentContext().getResponseBody() != null) {
String body = RequestContext.getCurrentContext().getResponseBody();
writeResponse(
new ByteArrayInputStream(
body.getBytes(servletResponse.getCharacterEncoding())),
outStream);
return;
}
...省略...
//寫響應結果
private void writeResponse(InputStream zin, OutputStream out) throws Exception {
byte[] bytes = buffers.get();
int bytesRead = -1;
while ((bytesRead = zin.read(bytes)) != -1) {
out.write(bytes, 0, bytesRead);
}
}
翻譯:that writes responses from proxied requests to the current response.
翻譯大致意思為把代理請求的響應寫入到當前響應,
String body = RequestContext.getCurrentContext().getResponseBody(); 獲取到響應內容 ,通過 servletResponse.getOutputStream(); 寫出去 ,
我們從原始碼中可以看到該過濾器會檢查請求上下文中是否包含請求響應相關的頭資訊、響應資料流或是響應體,然後利用請求上下文的響應資訊來組織需要傳送回客戶端的響應內容。
SendErrorFilter :錯誤處理過濾器 ,把錯誤重定向到/error路徑上,執行順序 0
/**
* Error {@link ZuulFilter} that forwards to /error (by default) if {@link RequestContext#getThrowable()} is not null.
*
* @author Spencer Gibb
*/
//TODO: move to error package in Edgware
public class SendErrorFilter extends ZuulFilter {
private static final Log log = LogFactory.getLog(SendErrorFilter.class);
protected static final String SEND_ERROR_FILTER_RAN = "sendErrorFilter.ran";
//異常重定向路徑
@Value("${error.path:/error}")
private String errorPath;
@Override
public String filterType() {
return ERROR_TYPE;
}
@Override
public int filterOrder() {
return SEND_ERROR_FILTER_ORDER;
}
@Override
public boolean shouldFilter() {
RequestContext ctx = RequestContext.getCurrentContext();
// only forward to errorPath if it hasn't been forwarded to already
return ctx.getThrowable() != null
&& !ctx.getBoolean(SEND_ERROR_FILTER_RAN, false);
}
@Override
public Object run() {
try {
RequestContext ctx = RequestContext.getCurrentContext();
//找到異常
ZuulException exception = findZuulException(ctx.getThrowable());
HttpServletRequest request = ctx.getRequest();
//處理異常錯誤碼等
request.setAttribute("javax.servlet.error.status_code", exception.nStatusCode);
log.warn("Error during filtering", exception);
request.setAttribute("javax.servlet.error.exception", exception);
if (StringUtils.hasText(exception.errorCause)) {
request.setAttribute("javax.servlet.error.message", exception.errorCause);
}
RequestDispatcher dispatcher = request.getRequestDispatcher(
this.errorPath);
if (dispatcher != null) {
ctx.set(SEND_ERROR_FILTER_RAN, true);
if (!ctx.getResponse().isCommitted()) {
ctx.setResponseStatusCode(exception.nStatusCode);
dispatcher.forward(request, ctx.getResponse());
}
}
}
catch (Exception ex) {
ReflectionUtils.rethrowRuntimeException(ex);
}
return null;
}
SendForwardFilter:用來處理路由規則中的forward本地跳轉配置 ,執行順序 5000
/**
* Route {@link ZuulFilter} that forwards requests using the {@link RequestDispatcher}.
* Forwarding location is located in the {@link RequestContext} attribute {@link org.springframework.cloud.netflix.zuul.filters.support.FilterConstants#FORWARD_TO_KEY}.
* Useful for forwarding to endpoints in the current application.
使用者RequestDispatcher 進行本地應用端點的Forwarding
* @author Dave Syer
*/
public class SendForwardFilter extends ZuulFilter {
...省略...
@Override
public Object run() {
try {
RequestContext ctx = RequestContext.getCurrentContext();
String path = (String) ctx.get(FORWARD_TO_KEY);
RequestDispatcher dispatcher = ctx.getRequest().getRequestDispatcher(path);
if (dispatcher != null) {
ctx.set(SEND_FORWARD_FILTER_RAN, true);
if (!ctx.getResponse().isCommitted()) {
//請求跳轉
dispatcher.forward(ctx.getRequest(), ctx.getResponse());
ctx.getResponse().flushBuffer();
}
}
}
catch (Exception ex) {
ReflectionUtils.rethrowRuntimeException(ex);
}
return null;
}
你以為到這裡就完了嗎,並沒有,我們剛才看的是ZuulServerAutoConfiguration中定義的過濾器,在ZuulProxyAutoConfiguration中還定義了一些過濾器
/**
* @author Spencer Gibb
* @author Dave Syer
* @author Biju Kunjummen
*/
@Configuration
@Import({ RibbonCommandFactoryConfiguration.RestClientRibbonConfiguration.class,
RibbonCommandFactoryConfiguration.OkHttpRibbonConfiguration.class,
RibbonCommandFactoryConfiguration.HttpClientRibbonConfiguration.class,
HttpClientConfiguration.class })
@ConditionalOnBean(ZuulProxyMarkerConfiguration.Marker.class)
public class ZuulProxyAutoConfiguration extends ZuulServerAutoConfiguration {
...省略程式碼...
// pre filters : 前置過濾
@Bean
public PreDecorationFilter preDecorationFilter(RouteLocator routeLocator, ProxyRequestHelper proxyRequestHelper) {
return new PreDecorationFilter(routeLocator, this.server.getServlet().getServletPrefix(), this.zuulProperties,
proxyRequestHelper);
}
// route filters :路由過濾
@Bean
public RibbonRoutingFilter ribbonRoutingFilter(ProxyRequestHelper helper,
RibbonCommandFactory<?> ribbonCommandFactory) {
RibbonRoutingFilter filter = new RibbonRoutingFilter(helper, ribbonCommandFactory,
this.requestCustomizers);
return filter;
}
@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);
}
PreDecorationFilter :匹配路由過著和服務位置、在請求上下文中設定該請求的基本資訊 ,執行順序 5
/**
* Pre {@link ZuulFilter} that determines where and how to route based on the supplied {@link RouteLocator}.
* Also sets various proxy related headers for downstream requests.
*/
public class PreDecorationFilter extends ZuulFilter {
...省略...
@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) {
//從路由中獲取請求服務id
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 {
//設定服務id在RibbonReques中使用
// 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()));
}
...省略...
RibbonRoutingFilter:routing過濾器,使用Ribbon和Hystrix來向服務例項發起請求 ,有服務熔斷機制,執行順序 10
/**
* Route {@link ZuulFilter} that uses Ribbon, Hystrix and pluggable http clients to send requests.
* ServiceIds are found in the {@link RequestContext} attribute {@link org.springframework.cloud.netflix.zuul.filters.support.FilterConstants#SERVICE_ID_KEY}.
通過 Ribbon 和 Hystrix 向http客戶端傳送請求
通過 RequestContext找到 ServiceIds服務id ,
* @author Spencer Gibb
* @author Dave Syer
* @author Ryan Baxter
*/
public class RibbonRoutingFilter extends ZuulFilter {
...省略...
@Override
public Object run() {
//獲取請求上下文
RequestContext context = RequestContext.getCurrentContext();
this.helper.addIgnoredHeaders();
try {
//建立一個 RibbonCommandContext Ribbon命令上下文,用來發請求
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);
}
}
//根據RequestContext 請求上下文,獲取請求服務id,url等封裝成RibbonCommandContext
protected RibbonCommandContext buildCommandContext(RequestContext context) {
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 && !verb.equalsIgnoreCase("GET")) {
context.setChunkedRequestBody();
}
String serviceId = (String) context.get(SERVICE_ID_KEY);
Boolean retryable = (Boolean) context.get(RETRYABLE_KEY);
Object loadBalancerKey = context.get(LOAD_BALANCER_KEY);
String uri = this.helper.buildZuulRequestURI(request);
// remove double slashes
uri = uri.replace("//", "/");
long contentLength = useServlet31 ? request.getContentLengthLong(): request.getContentLength();
return new RibbonCommandContext(serviceId, verb, uri, retryable, headers, params,
requestEntity, this.requestCustomizers, contentLength, loadBalancerKey);
}
protected ClientHttpResponse forward(RibbonCommandContext context) throws Exception {
Map<String, Object> info = this.helper.debug(context.getMethod(),
context.getUri(), context.getHeaders(), context.getParams(),
context.getRequestEntity());
RibbonCommand command = this.ribbonCommandFactory.create(context);
try {
//執行請求
ClientHttpResponse response = command.execute();
this.helper.appendDebug(info, response.getRawStatusCode(), response.getHeaders());
return response;
}
catch (HystrixRuntimeException ex) {
//處理異常
return handleException(info, ex);
}
}
//處理異常
protected ClientHttpResponse handleException(Map<String, Object> info,
HystrixRuntimeException ex) throws ZuulException {
int statusCode = HttpStatus.INTERNAL_SERVER_ERROR.value();
Throwable cause = ex;
String message = ex.getFailureType().toString();
ClientException clientException = findClientException(ex);
if (clientException == null) {
clientException = findClientException(ex.getFallbackException());
}
if (clientException != null) {
if (clientException
.getErrorType() == ClientException.ErrorType.SERVER_THROTTLED) {
statusCode = HttpStatus.SERVICE_UNAVAILABLE.value();
}
cause = clientException;
message = clientException.getErrorType().toString();
}
info.put("status", String.valueOf(statusCode));
throw new ZuulException(cause, "Forwarding error", statusCode, message);
}
SimpleHostRoutingFilter : 通過RequestContext#getRouteHost()找到呼叫的服務地址 ,使用http客戶端實現呼叫 ,他和RibbonRoutingFilter的區別是沒有使用Hystrix所以並沒有執行緒隔離和斷路器的保護。
執行順序 100
/**
* Route {@link ZuulFilter} that sends requests to predetermined URLs via apache
* {@link HttpClient}. URLs are found in {@link RequestContext#getRouteHost()}.
通過RequestContext#getRouteHost()找到呼叫的服務地址 ,使用apache的http客戶端實現呼叫
*
* @author Spencer Gibb
* @author Dave Syer
* @author Bilal Alp
* @author Gang Li
*/
public class SimpleHostRoutingFilter extends ZuulFilter {
...省略...
@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 傳送請求
private CloseableHttpResponse forward(CloseableHttpClient httpclient, String verb,
String uri, HttpServletRequest request, MultiValueMap<String, String> headers,
MultiValueMap<String, String> params, InputStream requestEntity)
throws Exception {
Map<String, Object> info = this.helper.debug(verb, uri, headers, params,
requestEntity);
//請求主機
URL host = RequestContext.getCurrentContext().getRouteHost();
HttpHost httpHost = getHttpHost(host);
//請求地址
uri = StringUtils.cleanPath((host.getPath() + uri).replaceAll("/{2,}", "/"));
int contentLength = request.getContentLength();
ContentType contentType = null;
if (request.getContentType() != null) {
contentType = ContentType.parse(request.getContentType());
}
InputStreamEntity entity = new InputStreamEntity(requestEntity, contentLength,
contentType);
HttpRequest httpRequest = buildHttpRequest(verb, uri, entity, headers, params,
request);
try {
log.debug(httpHost.getHostName() + " " + httpHost.getPort() + " "
+ httpHost.getSchemeName());
//傳送請求
CloseableHttpResponse zuulResponse = forwardRequest(httpclient, httpHost,
httpRequest);
this.helper.appendDebug(info, zuulResponse.getStatusLine().getStatusCode(),
revertHeaders(zuulResponse.getAllHeaders()));
return zuulResponse;
}
finally {
// When HttpClient instance is no longer needed,
// shut down the connection manager to ensure
// immediate deallocation of all system resources
// httpclient.getConnectionManager().shutdown();
}
}
------------------------------------
protected HttpRequest buildHttpRequest(String verb, String uri,
InputStreamEntity entity, MultiValueMap<String, String> headers,
MultiValueMap<String, String> params, HttpServletRequest request) {
HttpRequest httpRequest;
String uriWithQueryString = uri + (this.forceOriginalQueryStringEncoding
? getEncodedQueryString(request) :
this.helper.getQueryString(params));
//處理各種請求方式
switch (verb.toUpperCase()) {
case "POST":
HttpPost httpPost = new HttpPost(uriWithQueryString);
httpRequest = httpPost;
httpPost.setEntity(entity);
break;
case "PUT":
HttpPut httpPut = new HttpPut(uriWithQueryString);
httpRequest = httpPut;
httpPut.setEntity(entity);
break;
case "PATCH":
HttpPatch httpPatch = new HttpPatch(uriWithQueryString);
httpRequest = httpPatch;
httpPatch.setEntity(entity);
break;
case "DELETE":
BasicHttpEntityEnclosingRequest entityRequest = new BasicHttpEntityEnclosingRequest(
verb, uriWithQueryString);
httpRequest = entityRequest;
entityRequest.setEntity(entity);
break;
default:
httpRequest = new BasicHttpRequest(verb, uriWithQueryString);
log.debug(uriWithQueryString);
}
httpRequest.setHeaders(convertHeaders(headers));
return httpRequest;
}
------------------------------------
//最終執行請求
private CloseableHttpResponse forwardRequest(CloseableHttpClient httpclient,
HttpHost httpHost, HttpRequest httpRequest) throws IOException {
return httpclient.execute(httpHost, httpRequest);
}
到這裡我們把 ZuulProxyAutoConfiguration 自動配置類中定義的比較重要的一些過濾器都介紹完了 ,zuul在執行過程中就會按照這些filter的呼叫順序去執行,我們來用表格整理一下
用表格
那這一章我們分析到這裡 ,下一章我們跟蹤一下zuul的執行流程,看他是如果把這些filter串聯起來的 。
想獲取更多技術乾貨,請前往叩丁狼官網:http://www.wolfcode.cn/all_article.html