Zuul源碼解析
基於
Spring-cloud-netflix 2.0.4.RELEASE
進行分析的。
Zuul 架構圖
在zuul中, 整個請求的過程是這樣的,首先將請求給zuulservlet處理,zuulservlet中有一個zuulRunner對象,該對象中初始化了RequestContext:作爲存儲整個請求的一些數據,並被所有的zuulfilter共享。zuulRunner中還有 FilterProcessor,FilterProcessor作爲執行所有的zuulfilter的管理器。FilterProcessor從filterloader 中獲取zuulfilter,而zuulfilter是被filterFileManager所加載,並支持groovy熱加載,採用了輪詢的方式熱加載。
有了這些filter之後,zuulservelet首先執行的Pre類型的過濾器,再執行route類型的過濾器,最後執行的是post 類型的過濾器,如果在執行這些過濾器有錯誤的時候則會執行error類型的過濾器。執行完這些過濾器,最終將請求的結果返回給客戶端。
zuul工作原理源碼分析
在之前已經講過,如何使用zuul,其中不可缺少的一個步驟就是在程序的啓動類加上@EnableZuulProxy
,該EnableZuulProxy
類代碼如下:
@EnableCircuitBreaker
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Import(ZuulProxyMarkerConfiguration.class)
public @interface EnableZuulProxy {
}
其中,引用了ZuulProxyMarkerConfiguration
,ZuulProxyMarkerConfiguration
只做了一件事, 實例化了內部類Marker
.
// Responsible for adding in a marker bean to trigger activation of {@link ZuulProxyAutoConfiguration}
@Configuration
public class ZuulProxyMarkerConfiguration {
@Bean
public Marker zuulProxyMarkerBean() {
return new Marker();
}
class Marker {
}
}
從註釋中看到這個是用於激活ZuulProxyAutoConfiguration
,看看這個類
@Configuration
@Import({ RibbonCommandFactoryConfiguration.RestClientRibbonConfiguration.class,
RibbonCommandFactoryConfiguration.OkHttpRibbonConfiguration.class,
RibbonCommandFactoryConfiguration.HttpClientRibbonConfiguration.class,
HttpClientConfiguration.class })
@ConditionalOnBean(ZuulProxyMarkerConfiguration.Marker.class)
public class ZuulProxyAutoConfiguration extends ZuulServerAutoConfiguration {
//...
}
跟蹤ZuulProxyAutoConfiguration
,該類注入了DiscoveryClient、RibbonCommandFactoryConfiguration用作負載均衡相關的。注入了一些列的filters,比如PreDecorationFilter、RibbonRoutingFilter、SimpleHostRoutingFilter,代碼如如下:
@Bean
public PreDecorationFilter preDecorationFilter(RouteLocator routeLocator, ProxyRequestHelper proxyRequestHelper) {
return new PreDecorationFilter(routeLocator, this.server.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
public SimpleHostRoutingFilter simpleHostRoutingFilter(ProxyRequestHelper helper, ZuulProperties zuulProperties) {
return new SimpleHostRoutingFilter(helper, zuulProperties);
}
這個配置下面註冊了很多組件,不過先暫時不看,它同時繼承自ZuulServerAutoConfiguration
,看看這個類:
@Configuration
@EnableConfigurationProperties({ ZuulProperties.class })
@ConditionalOnClass({ZuulServlet.class, ZuulServletFilter.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 {
@Autowired
protected ZuulProperties zuulProperties;
@Autowired
protected ServerProperties server;
@Autowired(required = false)
private ErrorController errorController;
private Map<String, CorsConfiguration> corsConfigurations;
@Autowired(required = false)
private List<WebMvcConfigurer> configurers = emptyList();
@Bean
public HasFeatures zuulFeature() {
return HasFeatures.namedFeature("Zuul (Simple)", ZuulServerAutoConfiguration.class);
}
@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();
}
@Bean
public ZuulHandlerMapping zuulHandlerMapping(RouteLocator routes) {
ZuulHandlerMapping mapping = new ZuulHandlerMapping(routes, zuulController());
mapping.setErrorController(this.errorController);
mapping.setCorsConfigurations(getCorsConfigurations());
return mapping;
}
protected final Map<String, CorsConfiguration> getCorsConfigurations() {
if (this.corsConfigurations == null) {
ZuulCorsRegistry registry = new ZuulCorsRegistry();
this.configurers
.forEach(configurer -> configurer.addCorsMappings(registry));
this.corsConfigurations = registry.getCorsConfigurations();
}
return this.corsConfigurations;
}
@Bean
public ApplicationListener<ApplicationEvent> zuulRefreshRoutesListener() {
return new ZuulRefreshListener();
}
@Bean
@ConditionalOnMissingBean(name = "zuulServlet")
@ConditionalOnProperty(name = "zuul.use-filter", havingValue = "false", matchIfMissing = true)
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;
}
@Bean
@ConditionalOnMissingBean(name = "zuulServletFilter")
@ConditionalOnProperty(name = "zuul.use-filter", havingValue = "true", matchIfMissing = false)
public FilterRegistrationBean zuulServletFilter(){
final FilterRegistrationBean<ZuulServletFilter> filterRegistration = new FilterRegistrationBean<>();
filterRegistration.setUrlPatterns(Collections.singleton(this.zuulProperties.getServletPattern()));
filterRegistration.setFilter(new ZuulServletFilter());
filterRegistration.setOrder(Ordered.LOWEST_PRECEDENCE);
// The whole point of exposing this servlet is to provide a route that doesn't
// buffer requests.
filterRegistration.addInitParameter("buffer-requests", "false");
return filterRegistration;
}
// 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();
}
@Bean
@ConditionalOnProperty(value = "zuul.ribbon.eager-load.enabled")
public ZuulRouteApplicationContextInitializer zuulRoutesApplicationContextInitiazer(
SpringClientFactory springClientFactory) {
return new ZuulRouteApplicationContextInitializer(springClientFactory,
zuulProperties);
}
@Configuration
protected static class ZuulFilterConfiguration {
@Autowired
private Map<String, ZuulFilter> filters;
@Bean
public ZuulFilterInitializer zuulFilterInitializer(
CounterFactory counterFactory, TracerFactory tracerFactory) {
FilterLoader filterLoader = FilterLoader.getInstance();
FilterRegistry filterRegistry = FilterRegistry.instance();
return new ZuulFilterInitializer(this.filters, counterFactory, tracerFactory, filterLoader, filterRegistry);
}
}
@Configuration
@ConditionalOnClass(MeterRegistry.class)
protected static class ZuulCounterFactoryConfiguration {
@Bean
@ConditionalOnBean(MeterRegistry.class)
public CounterFactory counterFactory(MeterRegistry meterRegistry) {
return new DefaultCounterFactory(meterRegistry);
}
}
@Configuration
protected static class ZuulMetricsConfiguration {
@Bean
@ConditionalOnMissingBean(CounterFactory.class)
public CounterFactory counterFactory() {
return new EmptyCounterFactory();
}
@ConditionalOnMissingBean(TracerFactory.class)
@Bean
public TracerFactory tracerFactory() {
return new EmptyTracerFactory();
}
}
private static class ZuulRefreshListener
implements ApplicationListener<ApplicationEvent> {
@Autowired
private ZuulHandlerMapping zuulHandlerMapping;
private HeartbeatMonitor heartbeatMonitor = new HeartbeatMonitor();
@Override
public void onApplicationEvent(ApplicationEvent event) {
if (event instanceof ContextRefreshedEvent
|| event instanceof RefreshScopeRefreshedEvent
|| event instanceof RoutesRefreshedEvent
|| 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.heartbeatMonitor.update(value)) {
reset();
}
}
private void reset() {
this.zuulHandlerMapping.setDirty(true);
}
}
private static class ZuulCorsRegistry extends CorsRegistry {
@Override
protected Map<String, CorsConfiguration> getCorsConfigurations() {
return super.getCorsConfigurations();
}
}
}
這個配置類裏註冊了很多bean:
- SimpleRouteLocator:默認的路由定位器,主要負責維護配置文件中的路由配置。
- DiscoveryClientRouteLocator:繼承自SimpleRouteLocator,該類會將配置文件中的靜態路由配置以及服務發現(比如eureka)中的路由信息進行合併,主要是靠它路由到具體服務。
- CompositeRouteLocator:組合路由定位器,看入參就知道應該是會保存好多個RouteLocator,構造過程中其實僅包括一個DiscoveryClientRouteLocator。
- ZuulController:Zuul創建的一個Controller,用於將請求交由ZuulServlet處理。
- ZuulHandlerMapping:這個會添加到SpringMvc的HandlerMapping鏈中,只有選擇了ZuulHandlerMapping的請求才能出發到Zuul的後續流程。
它的父類ZuulServerAutoConfiguration
,引用了一些相關的配置。在缺失zuulServlet bean的情況下注入了ZuulServlet,該類是zuul的核心類。
@Bean
@ConditionalOnMissingBean(name = "zuulServlet")
public ServletRegistrationBean zuulServlet() {
ServletRegistrationBean 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;
}
同時也注入了其他的過濾器,比如ServletDetectionFilter、DebugFilter、Servlet30WrapperFilter,這些過濾器都是pre類型的。
@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類型的,比如 SendResponseFilter,error類型,比如 SendErrorFilter,route類型比如SendForwardFilter,代碼如下:
@Bean
public SendResponseFilter sendResponseFilter() {
return new SendResponseFilter();
}
@Bean
public SendErrorFilter sendErrorFilter() {
return new SendErrorFilter();
}
@Bean
public SendForwardFilter sendForwardFilter() {
return new SendForwardFilter();
}
初始化ZuulFilterInitializer類,將所有的filter 向FilterRegistry註冊
@Configuration
protected static class ZuulFilterConfiguration {
@Autowired
private Map<String, ZuulFilter> filters;
@Bean
public ZuulFilterInitializer zuulFilterInitializer(
CounterFactory counterFactory, TracerFactory tracerFactory) {
FilterLoader filterLoader = FilterLoader.getInstance();
FilterRegistry filterRegistry = FilterRegistry.instance();
return new ZuulFilterInitializer(this.filters, counterFactory, tracerFactory, filterLoader, filterRegistry);
}
}
而FilterRegistry管理了一個ConcurrentHashMap,用作存儲過濾器的,並有一些基本的CURD過濾器的方法,代碼如下:
public class FilterRegistry {
private static final FilterRegistry INSTANCE = new FilterRegistry();
public static final FilterRegistry instance() {
return INSTANCE;
}
private final ConcurrentHashMap<String, ZuulFilter> filters = new ConcurrentHashMap<String, ZuulFilter>();
private FilterRegistry() {
}
public ZuulFilter remove(String key) {
return this.filters.remove(key);
}
public ZuulFilter get(String key) {
return this.filters.get(key);
}
public void put(String key, ZuulFilter filter) {
this.filters.putIfAbsent(key, filter);
}
public int size() {
return this.filters.size();
}
public Collection<ZuulFilter> getAllFilters() {
return this.filters.values();
}
}
FilterLoader
類持有FilterRegistry,FilterFileManager類持有FilterLoader,所以最終是由FilterFileManager注入 filterFilterRegistry的ConcurrentHashMap的。FilterFileManager到開啓了輪詢機制,定時的去加載過濾器,代碼如下:
void startPoller() {
poller = new Thread("GroovyFilterFileManagerPoller") {
public void run() {
while (bRunning) {
try {
sleep(pollingIntervalSeconds * 1000);
manageFiles();
} catch (Exception e) {
e.printStackTrace();
}
}
}
};
poller.setDaemon(true);
poller.start();
}
Zuulservlet作爲類似於Spring MVC中的DispatchServlet,起到了前端控制器的作用,所有的請求都由它接管。它的核心代碼如下:
@Override
public void service(javax.servlet.ServletRequest servletRequest, javax.servlet.ServletResponse servletResponse) throws ServletException, IOException {
try {
init((HttpServletRequest) servletRequest, (HttpServletResponse) servletResponse);
// Marks this request as having passed through the "Zuul engine", as opposed to servlets
// explicitly bound in web.xml, for which requests will not have the same data attached
RequestContext context = RequestContext.getCurrentContext();
context.setZuulEngineRan();
try {
preRoute();
} catch (ZuulException e) {
error(e);
postRoute();
return;
}
try {
route();
} catch (ZuulException e) {
error(e);
postRoute();
return;
}
try {
postRoute();
} catch (ZuulException e) {
error(e);
return;
}
} catch (Throwable e) {
error(new ZuulException(e, 500, "UNHANDLED_EXCEPTION_" + e.getClass().getName()));
} finally {
RequestContext.getCurrentContext().unset();
}
}
跟蹤init(),可以發現這個方法爲每個請求生成了RequestContext,RequestContext繼承了ConcurrentHashMap<String, Object>,在請求結束時銷燬掉該RequestContext,RequestContext的生命週期爲請求到zuulServlet開始處理,直到請求結束返回結果。 RequestContext類在存儲了很多重要的信息,包括HttpServletRequest、HttpServletRespons、ResponseDataStream、ResponseStatusCode等。 RequestContext對象在處理請求的過程中,一直存在,所以這個對象爲所有Filter共享。
首先來看一看pre()的處理過程,它會進入到ZuulRunner,該類的作用是將請求的HttpServletRequest、HttpServletRespons放在RequestContext類中,幷包裝了一個FilterProcessor,代碼如下:
public void init(HttpServletRequest servletRequest, HttpServletResponse servletResponse) {
RequestContext ctx = RequestContext.getCurrentContext();
if (bufferRequests) {
ctx.setRequest(new HttpServletRequestWrapper(servletRequest));
} else {
ctx.setRequest(servletRequest);
}
ctx.setResponse(new HttpServletResponseWrapper(servletResponse));
}
public void preRoute() throws ZuulException {
FilterProcessor.getInstance().preRoute();
}
而FilterProcessor類爲調用filters的類,比如調用pre類型所有的過濾器:
public void preRoute() throws ZuulException {
try {
runFilters("pre");
} catch (ZuulException e) {
throw e;
} catch (Throwable e) {
throw new ZuulException(e, 500, "UNCAUGHT_EXCEPTION_IN_PRE_FILTER_" + e.getClass().getName());
}
}
跟蹤runFilters()方法,可以發現,它最終調用了FilterLoader的getFiltersByType(sType)方法來獲取同一類的過濾器,然後用for循環遍歷所有的ZuulFilter,執行了 processZuulFilter()方法,跟蹤該方法可以發現最終是執行了ZuulFilter的方法,最終返回了該方法返回的Object對象。
public Object runFilters(String sType) throws Throwable {
if (RequestContext.getCurrentContext().debugRouting()) {
Debug.addRoutingDebug("Invoking {" + sType + "} type filters");
}
boolean bResult = false;
List<ZuulFilter> list = FilterLoader.getInstance().getFiltersByType(sType);
if (list != null) {
for (int i = 0; i < list.size(); i++) {
ZuulFilter zuulFilter = list.get(i);
Object result = processZuulFilter(zuulFilter);
if (result != null && result instanceof Boolean) {
bResult |= ((Boolean) result);
}
}
}
return bResult;
}
route、post類型的過濾器的執行過程和pre執行過程類似。
Zuul默認過濾器
默認的核心過濾器一覽表
Zuul默認注入的過濾器,它們的執行順序在FilterConstants類,我們可以先定位在這個類,然後再看這個類的過濾器的執行順序以及相關的註釋,可以很輕鬆定位到相關的過濾器,也可以直接打開 spring-cloud-netflix-core.jar的 zuul.filters包,可以看到一些列的filter,現在我以表格的形式,列出默認注入的filter.
過濾器 | order | 描述 | 類型 |
---|---|---|---|
ServletDetectionFilter | -3 | 檢測請求是用 DispatcherServlet還是 ZuulServlet | pre |
Servlet30WrapperFilter | -2 | 在Servlet 3.0 下,包裝 requests | pre |
FormBodyWrapperFilter | -1 | 解析表單數據 | pre |
SendErrorFilter | 0 | 如果中途出現錯誤 | error |
DebugFilter | 1 | 設置請求過程是否開啓debug | pre |
PreDecorationFilter | 5 | 根據uri決定調用哪一個route過濾器 | pre |
RibbonRoutingFilter | 10 | 如果寫配置的時候用ServiceId則用這個route過濾器,該過濾器可以用Ribbon 做負載均衡,用hystrix做熔斷 | route |
SimpleHostRoutingFilter | 100 | 如果寫配置的時候用url則用這個route過濾 | route |
SendForwardFilter | 500 | 用RequestDispatcher請求轉發 | route |
SendResponseFilter | 1000 | 用RequestDispatcher請求轉發 | post |
過濾器的order值越小,就越先執行,並且在執行過濾器的過程中,它們共享了一個RequestContext對象,該對象的生命週期貫穿於請求,可以看出優先執行了pre類型的過濾器,並將執行後的結果放在RequestContext中,供後續的filter使用,比如在執行PreDecorationFilter的時候,決定使用哪一個route,它的結果的是放在RequestContext對象中,後續會執行所有的route的過濾器,如果不滿足條件就不執行該過濾器的run方法。最終達到了就執行一個route過濾器的run()方法。
而error類型的過濾器,是在程序發生異常的時候執行的。
post類型的過濾,在默認的情況下,只注入了SendResponseFilter,該類型的過濾器是將最終的請求結果以流的形式輸出給客戶單。
現在來看SimpleHostRoutingFilter是如何工作?
進入到SimpleHostRoutingFilter類的方法的run()方法,核心代碼如下:
@Override
public Object run() {
RequestContext context = RequestContext.getCurrentContext();
//省略代碼
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作爲請求類,並重構了url,請求到了具體的服務,得到的一個CloseableHttpResponse對象,並將CloseableHttpResponse對象的保存到RequestContext對象中。並調用了ProxyRequestHelper的setResponse方法,將請求狀態碼,流等信息保存在RequestContext對象中。
private void setResponse(HttpResponse response) throws IOException {
RequestContext.getCurrentContext().set("zuulResponse", response);
this.helper.setResponse(response.getStatusLine().getStatusCode(),
response.getEntity() == null ? null : response.getEntity().getContent(),
revertHeaders(response.getAllHeaders()));
}
現在來看SendResponseFilter是如何工作?
這個過濾器的order爲1000,在默認且正常的情況下,是最後一個執行的過濾器,該過濾器是最終將得到的數據返回給客戶端的請求。
在它的run()方法裏,有兩個方法:addResponseHeaders()和writeResponse(),即添加響應頭和寫入響應數據流。
public Object run() {
try {
addResponseHeaders();
writeResponse();
}
catch (Exception ex) {
ReflectionUtils.rethrowRuntimeException(ex);
}
return null;
}
其中writeResponse()方法是通過從RequestContext中獲取ResponseBody獲或者ResponseDataStream來寫入到HttpServletResponse中的,但是在默認的情況下ResponseBody爲null,而ResponseDataStream在route類型過濾器中已經設置進去了。具體代碼如下:
private void writeResponse() throws Exception {
RequestContext context = RequestContext.getCurrentContext();
HttpServletResponse servletResponse = context.getResponse();
//代碼省略
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;
}
//代碼省略
is = context.getResponseDataStream();
InputStream inputStream = is;
//代碼省略
writeResponse(inputStream, outStream);
//代碼省略
}
}
..//代碼省略
}
如何在zuul上做日誌處理
由於zuul作爲api網關,所有的請求都經過這裏,所以在網關上,可以做請求相關的日誌處理。 我的需求是這樣的,需要記錄請求的 url,ip地址,參數,請求發生的時間,整個請求的耗時,請求的響應狀態,甚至請求響應的結果等。 很顯然,需要實現這樣的一個功能,需要寫一個ZuulFliter,它應該是在請求發送給客戶端之前做處理,並且在route過濾器路由之後,在默認的情況下,這個過濾器的order應該爲500-1000之間。那麼如何獲取這些我需要的日誌信息呢?找RequestContext,在請求的生命週期裏這個對象裏,存儲了整個請求的所有信息。
現在編碼,在代碼的註釋中,做了詳細的說明,代碼如下:
@Component
public class LoggerFilter extends ZuulFilter {
@Override
public String filterType() {
return FilterConstants.POST_TYPE;
}
@Override
public int filterOrder() {
return FilterConstants.SEND_RESPONSE_FILTER_ORDER - 1;
}
@Override
public boolean shouldFilter() {
return true;
}
@Override
public Object run() {
RequestContext context = RequestContext.getCurrentContext();
HttpServletRequest request = context.getRequest();
String method = request.getMethod();//氫氣的類型,post get ..
Map<String, String> params = HttpUtils.getParams(request);
String paramsStr = params.toString();//請求的參數
long statrtTime = (long) context.get("startTime");//請求的開始時間
Throwable throwable = context.getThrowable();//請求的異常,如果有的話
request.getRequestURI();//請求的uri
HttpUtils.getIpAddress(request);//請求的iP地址
context.getResponseStatusCode();//請求的狀態
long duration=System.currentTimeMillis() - statrtTime);//請求耗時
return null;
}
}
現在讀者也許有疑問,如何得到的statrtTime,即請求開始的時間,其實這需要另外一個過濾器,在網絡請求route之前(大部分耗時都在route這一步),在過濾器中,在RequestContext存儲一個時間即可,另寫一個過濾器,代碼如下:
@Component
public class AccessFilter extends ZuulFilter {
@Override
public String filterType() {
return "pre";
}
@Override
public int filterOrder() {
return 0;
}
@Override
public boolean shouldFilter() {
return true;
}
@Override
public Object run() {
RequestContext ctx = RequestContext.getCurrentContext();
ctx.set("startTime",System.currentTimeMillis());
return null;
}
}
可能還有這樣的需求,我需要將響應結果,也要存儲在log中,在之前已經分析了,在route結束後,將從具體服務獲取的響應流存儲在RequestContext中,在SendResponseFilter過濾器寫入在HttpServletResponse中,最終返回給客戶端。那麼我只需要在SendResponseFilter寫入響應流之前把響應流寫入到 log日誌中即可,那麼會引發另外一個問題,因爲響應流寫入到 log後,RequestContext就沒有響應流了,在SendResponseFilter就沒有流輸入到HttpServletResponse中,導致客戶端沒有任何的返回數據,那麼解決的辦法是這樣的:
InputStream inputStream =RequestContext.getCurrentContext().getResponseDataStream();
InputStream newInputStream= copy(inputStream);
transerferTolog(inputStream);
RequestContext.getCurrentContext().setResponseDataStream(newInputStream);
從RequestContext獲取到流之後,首先將流 copy一份,將流轉化下字符串,存在日誌中,再set到RequestContext中, 這樣SendResponseFilter就可以將響應返回給客戶端。這樣的做法有點影響性能,如果不是字符流,可能需要做更多的處理工作。