前言
0.本次分享希望能给开发agent的同事一些帮助 1.我搭建的并不是SpringCloud 微服务环境,完全仿照agent(目前没有微服务化)来的 2.搭建的是聚合工程
父pom
services zuul
版本说明 SpringCloud Finchley.SR2 springboot 2.0.2.RELEASE 选择低版本是因为简单
启动类
首先看spring.factories文件 导入了ZuulServerAutoConfiguration 和 ZuulProxyAutoConfiguration 这两个类的区别是生效条件
@ConditionalOnBean(ZuulProxyMarkerConfiguration.Marker.class) 和 @ConditionalOnBean(ZuulServerMarkerConfiguration.Marker.class)
zuul网关在使用的时候一般在启动类上加@EnableZuulProxy注解
启动类只干了一件事注册了一个标记类org.springframework.cloud.netflix.zuul.ZuulProxyMarkerConfiguration.Marker 两者导入的maker不同
那么问题来了ZuulServerAutoConfiguration 和 ZuulProxyAutoConfiguration 的区别是啥 ZuulProxyAutoConfiguration extends ZuulServerAutoConfiguration 所以ZuulProxyAutoConfiguration 功能多一些(注入的filter多)
因此后续内容以ZuulServerAutoConfiguration 来讲
ZuulServerAutoConfiguration 介绍
1.ZuulFilterConfiguration介绍
zuul的filter要继承ZuulFilter并添加到bean,那么就会被下面的@Autowired注入进来
counterFactory
空实现
tracerFactory
空实现
FilterLoader(单例)和FilterFileManager(单例)
zuul支持动加载Filter类文件。实现原理是监控存放Filter文件的目录,定期扫描这些目录,如果发现有新Filter源码文件或者Filter源码文件有改动,则对文件进行编译加载。
在项目中调用FilterFileManager.init(int pollingIntervalSeconds, String… directories) 传入 pollingIntervalSeconds 文件检查时间见个,filter文件的目录 就会定时将目录下的filter通过FilterLoader动态写入到filterRegistry中
FilterFileManager源码 FilterLoader源码
FilterRegistry(单例)
就是一个ConcurrentHashMap 存 beanName 和 ZuulFilter
ZuulFilterInitializer
在@PostConstruct中 将所有ZuulFilter放入了ZuulFilterInitializer 的成员 filterRegistry 中 查看filterRegistry 源码 实际上存入的了currentHashMap中
请求拦截器的确定
接下来问题来了,当我用postman请求zuul,会被哪个类处理呢?
框架处理请求用的是springMvc,熟悉springMvc源码的都知道请求是由DispatcherServlet处理的,这里我在DispatcherServlet.doDispatch方法上打了一个断点 getHandler方法:根据request请求获取Handler 点进去 回过来再看:ZuulServerAutoConfiguration 向容器中注入了ZuulHandlerMapping,而ZuulHandlerMapping又需要RouteLocator(由primaryRouteLocator提供),而primaryRouteLocator又需要routeLocators(由simpleRouteLocator提供) 方法 simpleRouteLocator() 中的 this.zuulProperties 会获取 application.yml配置的映射
- 现在可知是ZuulController来处理请求
我在第一次看ZuulServerAutoConfiguration的时候自动注册了ZuulController 和 zuulServlet,这两个都能处理请求
ZuulController
ZuulController源码发现继承ServletWrappingController 于是搜索ServletWrappingController是做什么的
通过查阅资料发现 ServletWrappingController 会拦截请求,交给内部包装的的servlet进行处理
- 最终确定所有请求由ZuulController拦截,由内部包装的ZuulServlet进行处理
ZuulServlet
package com.netflix.zuul.http;
import com.netflix.zuul.FilterProcessor;
import com.netflix.zuul.ZuulRunner;
import com.netflix.zuul.context.RequestContext;
import com.netflix.zuul.exception.ZuulException;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.mockito.runners.MockitoJUnitRunner;
import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
import static org.junit.Assert.assertTrue;
import static org.mockito.Mockito.*;
public class ZuulServlet extends HttpServlet {
private static final long serialVersionUID = -3374242278843351500L;
private ZuulRunner zuulRunner;
@Override
public void init(ServletConfig config) throws ServletException {
super.init(config);
String bufferReqsStr = config.getInitParameter("buffer-requests");
boolean bufferReqs = bufferReqsStr != null && bufferReqsStr.equals("true") ? true : false;
zuulRunner = new ZuulRunner(bufferReqs);
}
@Override
public void service(javax.servlet.ServletRequest servletRequest, javax.servlet.ServletResponse servletResponse) throws ServletException, IOException {
try {
init((HttpServletRequest) servletRequest, (HttpServletResponse) servletResponse);
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();
}
}
void postRoute() throws ZuulException {
zuulRunner.postRoute();
}
void route() throws ZuulException {
zuulRunner.route();
}
void preRoute() throws ZuulException {
zuulRunner.preRoute();
}
void init(HttpServletRequest servletRequest, HttpServletResponse servletResponse) {
zuulRunner.init(servletRequest, servletResponse);
}
void error(ZuulException e) {
RequestContext.getCurrentContext().setThrowable(e);
zuulRunner.error();
}
@RunWith(MockitoJUnitRunner.class)
public static class UnitTest {
@Mock
HttpServletRequest servletRequest;
@Mock
HttpServletResponseWrapper servletResponse;
@Mock
FilterProcessor processor;
@Mock
PrintWriter writer;
@Before
public void before() {
MockitoAnnotations.initMocks(this);
}
@Test
public void testProcessZuulFilter() {
ZuulServlet zuulServlet = new ZuulServlet();
zuulServlet = spy(zuulServlet);
RequestContext context = spy(RequestContext.getCurrentContext());
try {
FilterProcessor.setProcessor(processor);
RequestContext.testSetCurrentContext(context);
when(servletResponse.getWriter()).thenReturn(writer);
zuulServlet.init(servletRequest, servletResponse);
verify(zuulServlet, times(1)).init(servletRequest, servletResponse);
assertTrue(RequestContext.getCurrentContext().getRequest() instanceof HttpServletRequestWrapper);
assertTrue(RequestContext.getCurrentContext().getResponse() instanceof HttpServletResponseWrapper);
zuulServlet.preRoute();
verify(processor, times(1)).preRoute();
zuulServlet.postRoute();
verify(processor, times(1)).postRoute();
zuulServlet.route();
verify(processor, times(1)).route();
RequestContext.testSetCurrentContext(null);
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
servlet init方法
ZuulServlet 初始化时会执行一次 servlet的init方法创建 ZuulRunner 对象赋值给成员变量
service方法
处理每次http请求
init 方法
RequestContext 实际上就是当前线程(threadLocal)里面存了一个map
- init 方法总结:当前线程TheadLocal中放了一个map,然后map中存入request 和 response
preRoute方法;
获取preFilter 逻辑 在filterRegistry获取所有filter然后调用filter的filterType方法,根据filterType方法返回值是否为pre进行过滤 循环执行preFilter
可以看到ServletDetectionFilter、Servlet30WrapperFilter、FormBodyWrapperFilter、DebugFilter几个内置的Filter 下面来看看这几个filter的作用
ServletDetectionFilter
package org.springframework.cloud.netflix.zuul.filters.pre;
import javax.servlet.http.HttpServletRequest;
import org.springframework.web.servlet.DispatcherServlet;
import com.netflix.zuul.ZuulFilter;
import com.netflix.zuul.context.RequestContext;
import com.netflix.zuul.http.HttpServletRequestWrapper;
import com.netflix.zuul.http.ZuulServlet;
import static org.springframework.cloud.netflix.zuul.filters.support.FilterConstants.IS_DISPATCHER_SERVLET_REQUEST_KEY;
import static org.springframework.cloud.netflix.zuul.filters.support.FilterConstants.PRE_TYPE;
import static org.springframework.cloud.netflix.zuul.filters.support.FilterConstants.SERVLET_DETECTION_FILTER_ORDER;
public class ServletDetectionFilter extends ZuulFilter {
public ServletDetectionFilter() {
}
@Override
public String filterType() {
return PRE_TYPE;
}
@Override
public int filterOrder() {
return SERVLET_DETECTION_FILTER_ORDER;
}
@Override
public boolean shouldFilter() {
return true;
}
@Override
public Object run() {
RequestContext ctx = RequestContext.getCurrentContext();
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;
}
private boolean isDispatcherServletRequest(HttpServletRequest request) {
return request.getAttribute(DispatcherServlet.WEB_APPLICATION_CONTEXT_ATTRIBUTE) != null;
}
}
Servlet30WrapperFilter
package org.springframework.cloud.netflix.zuul.filters.pre;
import java.lang.reflect.Field;
import javax.servlet.http.HttpServletRequest;
import org.springframework.cloud.netflix.zuul.util.RequestUtils;
import org.springframework.util.Assert;
import org.springframework.util.ReflectionUtils;
import com.netflix.zuul.ZuulFilter;
import com.netflix.zuul.context.RequestContext;
import com.netflix.zuul.http.HttpServletRequestWrapper;
import static org.springframework.cloud.netflix.zuul.filters.support.FilterConstants.PRE_TYPE;
import static org.springframework.cloud.netflix.zuul.filters.support.FilterConstants.SERVLET_30_WRAPPER_FILTER_ORDER;
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 boolean shouldFilter() {
return true;
}
@Override
public Object run() {
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()) {
ctx.setRequest(new Servlet30RequestWrapper(request));
}
return null;
}
}
Servlet30RequestWrapper 及其父类 com.netflix.zuul.http.HttpServletRequestWrapper
- 讲到这里本次主要将透传相关,因此Servlet30RequestWrapper这个类将重点介绍
package org.springframework.cloud.netflix.zuul.filters.pre;
import com.netflix.zuul.http.HttpServletRequestWrapper;
import javax.servlet.http.HttpServletRequest;
class Servlet30RequestWrapper extends HttpServletRequestWrapper {
private HttpServletRequest request;
Servlet30RequestWrapper(HttpServletRequest request) {
super(request);
this.request = request;
}
@Override
public HttpServletRequest getRequest() {
return this.request;
}
}
package com.netflix.zuul.http;
import com.netflix.zuul.constants.ZuulHeaders;
import com.netflix.zuul.context.RequestContext;
import com.netflix.zuul.util.HTTPRequestUtils;
import org.apache.commons.io.IOUtils;
import org.junit.Before;
import org.junit.Test;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.servlet.ServletInputStream;
import javax.servlet.http.HttpServletRequest;
import java.io.*;
import java.net.SocketTimeoutException;
import java.net.URLDecoder;
import java.util.*;
import java.util.zip.*;
import static org.junit.Assert.*;
import static org.mockito.Mockito.when;
public class HttpServletRequestWrapper extends javax.servlet.http.HttpServletRequestWrapper {
private final static HashMap<String, String[]> EMPTY_MAP = new HashMap<String, String[]>();
protected static final Logger LOG = LoggerFactory.getLogger(HttpServletRequestWrapper.class);
private HttpServletRequest req;
private byte[] contentData = null;
private HashMap<String, String[]> parameters = null;
private long bodyBufferingTimeNs = 0;
public HttpServletRequestWrapper() {
super(groovyTrick());
}
private static HttpServletRequest groovyTrick() {
throw new IllegalArgumentException("Please use HttpServletRequestWrapper(HttpServletRequest request) constructor!");
}
private HttpServletRequestWrapper(HttpServletRequest request, byte[] contentData, HashMap<String, String[]> parameters) {
super(request);
req = request;
this.contentData = contentData;
this.parameters = parameters;
}
public HttpServletRequestWrapper(HttpServletRequest request) {
super(request);
req = request;
}
@Override
public HttpServletRequest getRequest() {
try {
parseRequest();
} catch (IOException e) {
throw new IllegalStateException("Cannot parse the request!", e);
}
return req;
}
public byte[] getContentData() {
return contentData;
}
public HashMap<String, String[]> getParameters() {
if (parameters == null) return EMPTY_MAP;
HashMap<String, String[]> map = new HashMap<String, String[]>(parameters.size() * 2);
for (String key : parameters.keySet()) {
map.put(key, parameters.get(key).clone());
}
return map;
}
private void parseRequest() throws IOException {
if (parameters != null)
return;
HashMap<String, List<String>> mapA = new HashMap<String, List<String>>();
List<String> list;
Map<String, List<String>> query = HTTPRequestUtils.getInstance().getQueryParams();
if (query != null) {
for (String key : query.keySet()) {
list = query.get(key);
mapA.put(key, list);
}
}
if (shouldBufferBody()) {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
try {
long bufferStartTime = System.nanoTime();
IOUtils.copy(req.getInputStream(), baos);
bodyBufferingTimeNs = System.nanoTime() - bufferStartTime;
contentData = baos.toByteArray();
} catch (SocketTimeoutException e) {
LOG.error("SocketTimeoutException reading request body from inputstream. error=" + String.valueOf(e.getMessage()));
if (contentData == null) {
contentData = new byte[0];
}
}
try {
LOG.debug("Length of contentData byte array = " + contentData.length);
if (req.getContentLength() != contentData.length) {
LOG.warn("Content-length different from byte array length! cl=" + req.getContentLength() + ", array=" + contentData.length);
}
} catch(Exception e) {
LOG.error("Error checking if request body gzipped!", e);
}
final boolean isPost = req.getMethod().equals("POST");
String contentType = req.getContentType();
final boolean isFormBody = contentType != null && contentType.contains("application/x-www-form-urlencoded");
if (isPost && isFormBody) {
String enc = req.getCharacterEncoding();
if (enc == null) enc = "UTF-8";
String s = new String(contentData, enc), name, value;
StringTokenizer st = new StringTokenizer(s, "&");
int i;
boolean decode = req.getContentType() != null;
while (st.hasMoreTokens()) {
s = st.nextToken();
i = s.indexOf("=");
if (i > 0 && s.length() > i + 1) {
name = s.substring(0, i);
value = s.substring(i + 1);
if (decode) {
try {
name = URLDecoder.decode(name, "UTF-8");
} catch (Exception e) {
}
try {
value = URLDecoder.decode(value, "UTF-8");
} catch (Exception e) {
}
}
list = mapA.get(name);
if (list == null) {
list = new LinkedList<String>();
mapA.put(name, list);
}
list.add(value);
}
}
}
}
HashMap<String, String[]> map = new HashMap<String, String[]>(mapA.size() * 2);
for (String key : mapA.keySet()) {
list = mapA.get(key);
map.put(key, list.toArray(new String[list.size()]));
}
parameters = map;
}
private boolean shouldBufferBody() {
if (LOG.isDebugEnabled()) {
LOG.debug("Path = " + req.getPathInfo());
LOG.debug("Transfer-Encoding = " + String.valueOf(req.getHeader(ZuulHeaders.TRANSFER_ENCODING)));
LOG.debug("Content-Encoding = " + String.valueOf(req.getHeader(ZuulHeaders.CONTENT_ENCODING)));
LOG.debug("Content-Length header = " + req.getContentLength());
}
boolean should = false;
if (req.getContentLength() > 0) {
should = true;
}
else if (req.getContentLength() == -1) {
final String transferEncoding = req.getHeader(ZuulHeaders.TRANSFER_ENCODING);
if (transferEncoding != null && transferEncoding.equals(ZuulHeaders.CHUNKED)) {
RequestContext.getCurrentContext().setChunkedRequestBody();
should = true;
}
}
return should;
}
public long getBodyBufferingTimeNs()
{
return bodyBufferingTimeNs;
}
@Override
public ServletInputStream getInputStream() throws IOException {
parseRequest();
return new ServletInputStreamWrapper(contentData);
}
@Override
public BufferedReader getReader() throws IOException {
parseRequest();
String enc = req.getCharacterEncoding();
if (enc == null)
enc = "UTF-8";
byte[] data = contentData;
if (data == null)
data = new byte[0];
return new BufferedReader(new InputStreamReader(new ByteArrayInputStream(data), enc));
}
@Override
public String getParameter(String name) {
try {
parseRequest();
} catch (IOException e) {
throw new IllegalStateException("Cannot parse the request!", e);
}
if (parameters == null) return null;
String[] values = parameters.get(name);
if (values == null || values.length == 0)
return null;
return values[0];
}
@SuppressWarnings("unchecked")
@Override
public Map getParameterMap() {
try {
parseRequest();
} catch (IOException e) {
throw new IllegalStateException("Cannot parse the request!", e);
}
return getParameters();
}
@SuppressWarnings("unchecked")
@Override
public Enumeration getParameterNames() {
try {
parseRequest();
} catch (IOException e) {
throw new IllegalStateException("Cannot parse the request!", e);
}
return new Enumeration<String>() {
private String[] arr = getParameters().keySet().toArray(new String[0]);
private int idx = 0;
@Override
public boolean hasMoreElements() {
return idx < arr.length;
}
@Override
public String nextElement() {
return arr[idx++];
}
};
}
@Override
public String[] getParameterValues(String name) {
try {
parseRequest();
} catch (IOException e) {
throw new IllegalStateException("Cannot parse the request!", e);
}
if (parameters == null) return null;
String[] arr = parameters.get(name);
if (arr == null)
return null;
return arr.clone();
}
}
HttpServletRequestWrapper 注释介绍:
This class implements the Wrapper or Decorator pattern. Methods default to calling through to the wrapped request object, except the ones that read the request's content (parameters, stream or reader).
This class provides a buffered content reading that allows the methods getReader(), getInputStream() and any of the getParameterXXX to be called safely and repeatedly with the same results.
This class is intended to wrap relatively small HttpServletRequest instances.
FormBodyWrapperFilter
|