Route类型过滤器
Route类型的过滤器在zuul中负责对请求进行转发,zuul作为网关的最核心的功能就体现在route类型过滤器中。Netflix提供了两个route类型的过滤器:ZuulHostRequest和ZuulNFRequest,但任意请求只会满足其中一个过滤器的执行条件。
Route类型过滤器执行顺序为:ZuulNFRequest(10) -> ZuulHostRequest(100)
ZuulHostRequest
ZuulHostRequest简单地根据host来转发请求,host的值根据上下文变量routeHost取得。
// ZuulHostRequest.groovy
boolean shouldFilter() {
// 如果routeHost不为空,则说明此次请求是转发到指定host,由此过滤器进行处理,否则由ZuulNFRequest进行处理
return RequestContext.currentContext.getRouteHost() != null && RequestContext.currentContext.sendZuulResponse()
}
Object run() {
HttpServletRequest request = RequestContext.currentContext.getRequest();
// 构建请求headers,会包括原请求请求头和zuul添加的请求头
Header[] headers = buildZuulRequestHeaders(request)
// 获取HTTP动词,即GET/POST之类
String verb = getVerb(request);
InputStream requestEntity = getRequestBody(request)
HttpClient httpclient = CLIENT.get()
String uri = request.getRequestURI()
if (RequestContext.currentContext.requestURI != null) {
// 如果在之前的过滤器中为上下文添加了requestURI,则覆盖掉原uri
uri = RequestContext.currentContext.requestURI
}
try {
// 转发请求并将响应保存到上下文
HttpResponse response = forward(httpclient, verb, uri, request, headers, requestEntity)
setResponse(response)
} catch (Exception e) {
throw e;
}
return null
}
ZuulHostRequest的run方法会根据原请求和上下文构建一个新的HTTP请求,然后进行转发,接下来看看forward方法
private static final AtomicReference<HttpClient> CLIENT = new AtomicReference<HttpClient>(newClient());
HttpResponse forward(HttpClient httpclient, String verb, String uri, HttpServletRequest request, Header[] headers, InputStream requestEntity) {
// 如果开启了debugRequest的话,这里会返回一个wrap过的requestEntity(debugRequestEntity)用于debug
requestEntity = debug(httpclient, verb, uri, request, headers, requestEntity)
org.apache.http.HttpHost httpHost
httpHost = getHttpHost()
org.apache.http.HttpRequest httpRequest;
switch (verb) {
case 'POST':
httpRequest = new HttpPost(uri + getQueryString())
InputStreamEntity entity = new InputStreamEntity(requestEntity, request.getContentLength())
httpRequest.setEntity(entity)
break
case 'PUT':
httpRequest = new HttpPut(uri + getQueryString())
InputStreamEntity entity = new InputStreamEntity(requestEntity, request.getContentLength())
httpRequest.setEntity(entity)
break;
default:
httpRequest = new BasicHttpRequest(verb, uri + getQueryString())
}
try {
httpRequest.setHeaders(headers)
HttpResponse zuulResponse = executeHttpRequest(httpclient, httpHost, httpRequest)
return zuulResponse
} finally {
// When HttpClient instance is no longer needed,
// shut down the connection manager to ensure
// immediate deallocation of all system resources
// 这行被注释掉了,可能因为之前用的client不是静态变量,现在换成静态变量后就不需要在这里释放连接了
// httpclient.getConnectionManager().shutdown();
}
}
HttpResponse executeHttpRequest(HttpClient httpclient, HttpHost httpHost, HttpRequest httpRequest) {
// 封装成hystrix command执行,有关hystrix的内容这里不做讨论
HostCommand command = new HostCommand(httpclient, httpHost, httpRequest)
command.execute();
}
ZuulNFRequest
ZuulNFRequest可以将请求路由到注册到eureka server的服务中。它使用Ribbon客户端,可以将请求就当前可用实例进行负载均衡。
// ZuulNFRequest.groovy
boolean shouldFilter() {
return NFRequestContext.currentContext.getRouteHost() == null && RequestContext.currentContext.sendZuulResponse()
}
Object run() {
NFRequestContext context = NFRequestContext.currentContext
HttpServletRequest request = context.getRequest();
// 构建请求headers,会包括原请求请求头和zuul添加的请求头
MultivaluedMap<String, String> headers = buildZuulRequestHeaders(request)
// 构建请求参数
MultivaluedMap<String, String> params = buildZuulRequestQueryParams(request)
Verb verb = getVerb(request);
Object requestEntity = getRequestBody(request)
// 通过routeVIP获取到相应的ribbon客户端
IClient restClient = ClientFactory.getNamedClient(context.getRouteVIP());
String uri = request.getRequestURI()
if (context.requestURI != null) {
uri = context.requestURI
}
//remove double slashes
uri = uri.replace("//", "/")
HttpResponse response = forward(restClient, verb, uri, headers, params, requestEntity)
setResponse(response)
return response
}
逻辑基本上与ZuulHostRequest一致,唯一不同的是使用的HTTP客户端不一样。ZuulNFRequest使用的客户端是从ClientFactory中获取的命名客户端(named client),每个命名客户端会有一个相应的命名配置(named config),这意味者每个客户端都可以有不同的配置。来看看相关代码
tip: 这个ClientFactory其实是属于ribbon的类,如果对ribbon有了解可以选择跳过下面的内容不看。
// ClientFactory.java
public static synchronized IClient getNamedClient(String name, Class<? extends IClientConfig> configClass) {
if (simpleClientMap.get(name) != null) {
return simpleClientMap.get(name);
}
try {
// client不存在,尝试创建一个
return createNamedClient(name, configClass);
} catch (ClientException e) {
throw new RuntimeException("Unable to create client", e);
}
}
public static synchronized IClient createNamedClient(String name, Class<? extends IClientConfig> configClass) throws ClientException {
// 获取命名配置
IClientConfig config = getNamedConfig(name, configClass);
return registerClientFromProperties(name, config);
}
需要注意到的是在createNamedClient()中,传入的client config是一个类型,通过getNamedConfig(name, configClass)获取到当前客户端对应的命名配置。
命名配置
所谓命名配置即是为每份配置起一个名字,然后在运行时就可以根据不同的名字来获取不同的配置,例如我们可以这样配置
# zuul.properties
origin.zuul.client.DeploymentContextBasedVipAddresses=ORIGIN
origin.zuul.client.Port=8080
foo.zuul.client.DeploymentContextBasedVipAddresses=FOO
foo.zuul.client.Port=8081
bar.zuul.client.DeploymentContextBasedVipAddresses=BAR
bar.zuul.client.Port=8082
然后构造出来的origin, foo, bar这三个客户端对应的配置分别是vip: ORIGIN, FOO, BAR; port: 8080, 8081, 8082
获取命名配置的代码如下
// ClientFactory.java
public static IClientConfig getNamedConfig(String name, Class<? extends IClientConfig> clientConfigClass) {
IClientConfig config = namedConfig.get(name);
if (config != null) {
return config;
} else {
try {
config = (IClientConfig) clientConfigClass.newInstance();
// 以名称前缀加载相应的配置
config.loadProperties(name);
} catch (Throwable e) {
logger.error("Unable to create client config instance", e);
return null;
}
config.loadProperties(name);
IClientConfig old = namedConfig.putIfAbsent(name, config);
if (old != null) {
config = old;
}
return config;
}
}