如果您@EnableZuulProxy您可以使用代理路徑上傳文件接谨,只要文件很小荆残,它就應(yīng)該工作。對于大文件橄妆,有一個替代路徑繞過“/ zuul / *”中的SpringDispatcherServlet(以避免多部分處理)。也就是說祈坠,如果zuul.routes.customers=/customers/**則可以將大文件發(fā)送到“/ zuul / customers / *”害碾。servlet路徑通過zuul.servletPath進(jìn)行外部化。如果代理路由引導(dǎo)您通過Ribbon負(fù)載均衡器赦拘,例如慌随,超大文件也將需要提升超時設(shè)置
application.yml
hystrix.command.default.execution.isolation.thread.timeoutInMilliseconds: 60000
ribbon:
? ConnectTimeout: 3000
? ReadTimeout: 60000
請注意,要使用大型文件進(jìn)行流式傳輸躺同,您需要在請求中使用分塊編碼(某些瀏覽器默認(rèn)情況下不會執(zhí)行)阁猜。例如在命令行:
$ curl -v -H "Transfer-Encoding: chunked" \
? ? -F "file=@mylarge.iso" localhost:9999/zuul/simple/file
查詢字符串編碼
處理傳入的請求時,查詢參數(shù)被解碼蹋艺,因此可以在Zuul過濾器中進(jìn)行修改剃袍。然后在路由過濾器中構(gòu)建后端請求時重新編碼它們。如果使用Javascript的encodeURIComponent()方法編碼捎谨,結(jié)果可能與原始輸入不同民效。雖然這在大多數(shù)情況下不會出現(xiàn)任何問題,但一些Web服務(wù)器可以用復(fù)雜查詢字符串的編碼來挑選涛救。
要強(qiáng)制查詢字符串的原始編碼畏邢,可以將特殊標(biāo)志傳遞給ZuulProperties,以便查詢字符串與HttpServletRequest::getQueryString方法相同:
application.yml
zuul:
? forceOriginalQueryStringEncoding: true
注意:此特殊標(biāo)志僅適用于SimpleHostRoutingFilter州叠,您可以使用RequestContext.getCurrentContext().setRequestQueryParams(someOverriddenParameters)輕松覆蓋查詢參數(shù)棵红,因?yàn)椴樵冏址F(xiàn)在直接在原始的HttpServletRequest上獲取。
普通嵌入Zuul
如果您使用@EnableZuulServer(而不是@EnableZuulProxy)咧栗,您也可以運(yùn)行不帶代理的Zuul服務(wù)器逆甜,或者有選擇地切換代理平臺的部分虱肄。您添加到ZuulFilter類型的應(yīng)用程序的任何bean都將自動安裝,與@EnableZuulProxy一樣交煞,但不會自動添加任何代理過濾器咏窿。
在這種情況下,仍然通過配置“zuul.routes素征。*”來指定進(jìn)入Zuul服務(wù)器的路由集嵌,但沒有服務(wù)發(fā)現(xiàn)和代理,所以“serviceId”和“url”設(shè)置將被忽略御毅。例如:
application.yml
zuul:
? routes:
? ? api: /api/**
將“/ api / **”中的所有路徑映射到Zuul過濾器鏈根欧。
禁用Zuul過濾器
Spring Cloud的Zuul在代理和服務(wù)器模式下默認(rèn)啟用了多個ZuulFilterbean。有關(guān)啟用的可能過濾器端蛆,請參閱zuul過濾器包凤粗。如果要禁用它,只需設(shè)置zuul.<SimpleClassName>.<filterType>.disable=true今豆。按照慣例嫌拣,filters之后的包是Zuul過濾器類型。例如呆躲,禁用org.springframework.cloud.netflix.zuul.filters.post.SendResponseFilter設(shè)置zuul.SendResponseFilter.post.disable=true异逐。
為路線提供Hystrix回退
當(dāng)Zuul中給定路由的電路跳閘時,您可以通過創(chuàng)建類型為ZuulFallbackProvider的bean來提供回退響應(yīng)插掂。在這個bean中灰瞻,您需要指定回退的路由ID,并提供返回的ClientHttpResponse作為后備燥筷。這是一個非常簡單的ZuulFallbackProvider實(shí)現(xiàn)箩祥。
class MyFallbackProvider implements ZuulFallbackProvider {
? ? @Override
? ? public String getRoute() {
? ? ? ? return "customers";
? ? }
? ? @Override
? ? public ClientHttpResponse fallbackResponse() {
? ? ? ? return new ClientHttpResponse() {
? ? ? ? ? ? @Override
? ? ? ? ? ? public HttpStatus getStatusCode() throws IOException {
? ? ? ? ? ? ? ? return HttpStatus.OK;
? ? ? ? ? ? }
? ? ? ? ? ? @Override
? ? ? ? ? ? public int getRawStatusCode() throws IOException {
? ? ? ? ? ? ? ? return 200;
? ? ? ? ? ? }
? ? ? ? ? ? @Override
? ? ? ? ? ? public String getStatusText() throws IOException {
? ? ? ? ? ? ? ? return "OK";
? ? ? ? ? ? }
? ? ? ? ? ? @Override
? ? ? ? ? ? public void close() {
? ? ? ? ? ? }
? ? ? ? ? ? @Override
? ? ? ? ? ? public InputStream getBody() throws IOException {
? ? ? ? ? ? ? ? return new ByteArrayInputStream("fallback".getBytes());
? ? ? ? ? ? }
? ? ? ? ? ? @Override
? ? ? ? ? ? public HttpHeaders getHeaders() {
? ? ? ? ? ? ? ? HttpHeaders headers = new HttpHeaders();
? ? ? ? ? ? ? ? headers.setContentType(MediaType.APPLICATION_JSON);
? ? ? ? ? ? ? ? return headers;
? ? ? ? ? ? }
? ? ? ? };
? ? }
}
這里是路由配置的樣子。
zuul:
? routes:
? ? customers: /customers/**
如果您希望為所有路由提供默認(rèn)的回退肆氓,您可以創(chuàng)建一個類型為ZuulFallbackProvider的bean,并且getRoute方法返回*或null底瓣。
class MyFallbackProvider implements ZuulFallbackProvider {
? ? @Override
? ? public String getRoute() {
? ? ? ? return "*";
? ? }
? ? @Override
? ? public ClientHttpResponse fallbackResponse() {
? ? ? ? return new ClientHttpResponse() {
? ? ? ? ? ? @Override
? ? ? ? ? ? public HttpStatus getStatusCode() throws IOException {
? ? ? ? ? ? ? ? return HttpStatus.OK;
? ? ? ? ? ? }
? ? ? ? ? ? @Override
? ? ? ? ? ? public int getRawStatusCode() throws IOException {
? ? ? ? ? ? ? ? return 200;
? ? ? ? ? ? }
? ? ? ? ? ? @Override
? ? ? ? ? ? public String getStatusText() throws IOException {
? ? ? ? ? ? ? ? return "OK";
? ? ? ? ? ? }
? ? ? ? ? ? @Override
? ? ? ? ? ? public void close() {
? ? ? ? ? ? }
? ? ? ? ? ? @Override
? ? ? ? ? ? public InputStream getBody() throws IOException {
? ? ? ? ? ? ? ? return new ByteArrayInputStream("fallback".getBytes());
? ? ? ? ? ? }
? ? ? ? ? ? @Override
? ? ? ? ? ? public HttpHeaders getHeaders() {
? ? ? ? ? ? ? ? HttpHeaders headers = new HttpHeaders();
? ? ? ? ? ? ? ? headers.setContentType(MediaType.APPLICATION_JSON);
? ? ? ? ? ? ? ? return headers;
? ? ? ? ? ? }
? ? ? ? };
? ? }
}
Zuul開發(fā)人員指南
有關(guān)Zuul如何工作的一般概述谢揪,請參閱Zuul維基。
Zuul Servlet
Zuul被實(shí)現(xiàn)為Servlet捐凭。對于一般情況拨扶,Zuul嵌入到Spring調(diào)度機(jī)制中。這允許Spring MVC控制路由茁肠。在這種情況下患民,Zuul被配置為緩沖請求。如果需要通過Zuul不緩沖請求(例如大文件上傳)垦梆,Servlet也將安裝在Spring調(diào)度程序之外匹颤。默認(rèn)情況下仅孩,它位于/zuul∮”停可以使用zuul.servlet-path屬性更改此路徑辽慕。
Zuul RequestContext
要在過濾器之間傳遞信息,Zuul使用a RequestContext赦肃。其數(shù)據(jù)按照每個請求的ThreadLocal進(jìn)行溅蛉。關(guān)于路由請求,錯誤以及實(shí)際HttpServletRequest和HttpServletResponse的路由信息??他宛。RequestContext擴(kuò)展ConcurrentHashMap船侧,所以任何東西都可以存儲在上下文中。FilterConstants包含由Spring Cloud Netflix安裝的過濾器使用的密鑰(稍后再安裝)厅各。
@EnableZuulProxy與@EnableZuulServer
Spring Cloud Netflix根據(jù)使用何種注釋來啟用Zuul安裝多個過濾器勺爱。@EnableZuulProxy是@EnableZuulServer的超集。換句話說讯检,@EnableZuulProxy包含@EnableZuulServer安裝的所有過濾器琐鲁。“代理”中的其他過濾器啟用路由功能人灼。如果你想要一個“空白”Zuul围段,你應(yīng)該使用@EnableZuulServer。
@EnableZuulServer過濾器
創(chuàng)建從Spring Boot配置文件加載路由定義的SimpleRouteLocator投放。
安裝了以下過濾器(正常Spring豆類):
前置過濾器
ServletDetectionFilter:檢測請求是否通過Spring調(diào)度程序奈泪。使用鍵FilterConstants.IS_DISPATCHER_SERVLET_REQUEST_KEY設(shè)置布爾值。
FormBodyWrapperFilter:解析表單數(shù)據(jù)灸芳,并對下游請求進(jìn)行重新編碼涝桅。
DebugFilter:如果設(shè)置debug請求參數(shù),則此過濾器將RequestContext.setDebugRouting()和RequestContext.setDebugRequest()設(shè)置為true烙样。
路由過濾器
SendForwardFilter:此過濾器使用ServletRequestDispatcher轉(zhuǎn)發(fā)請求冯遂。轉(zhuǎn)發(fā)位置存儲在RequestContext屬性FilterConstants.FORWARD_TO_KEY中。這對于轉(zhuǎn)發(fā)到當(dāng)前應(yīng)用程序中的端點(diǎn)很有用谒获。
過濾器:
SendResponseFilter:將代理請求的響應(yīng)寫入當(dāng)前響應(yīng)蛤肌。
錯誤過濾器:
SendErrorFilter:如果RequestContext.getThrowable()不為null,則轉(zhuǎn)發(fā)到/錯誤(默認(rèn)情況下)批狱÷阕迹可以通過設(shè)置error.path屬性來更改默認(rèn)轉(zhuǎn)發(fā)路徑(/error)。
@EnableZuulProxy過濾器
創(chuàng)建從DiscoveryClient(如Eureka)以及屬性加載路由定義的DiscoveryClientRouteLocator赔硫。每個serviceId從DiscoveryClient創(chuàng)建路由炒俱。隨著新服務(wù)的添加,路由將被刷新。
除了上述過濾器之外权悟,還安裝了以下過濾器(正常Spring豆類):
前置過濾器
PreDecorationFilter:此過濾器根據(jù)提供的RouteLocator確定在哪里和如何路由砸王。它還為下游請求設(shè)置各種與代理相關(guān)的頭。
路由過濾器
RibbonRoutingFilter:此過濾器使用Ribbon僵芹,Hystrix和可插拔HTTP客戶端發(fā)送請求处硬。服務(wù)ID位于RequestContext屬性FilterConstants.SERVICE_ID_KEY中。此過濾器可以使用不同的HTTP客戶端拇派。他們是:
ApacheHttpClient荷辕。這是默認(rèn)的客戶端。
SquareupOkHttpClientv3件豌。通過在類路徑上設(shè)置com.squareup.okhttp3:okhttp庫并設(shè)置ribbon.okhttp.enabled=true來啟用此功能疮方。
Netflix Ribbon HTTP客戶端。這可以通過設(shè)置ribbon.restclient.enabled=true來啟用茧彤。這個客戶端有限制骡显,比如它不支持PATCH方法,還有內(nèi)置的重試曾掂。
SimpleHostRoutingFilter:此過濾器通過Apache HttpClient發(fā)送請求到預(yù)定的URL惫谤。URL位于RequestContext.getRouteHost()。
自定義Zuul過濾示例
以下大部分以下“如何撰寫”示例都包含示例Zuul過濾器項(xiàng)目珠洗。還有一些操作該存儲庫中的請求或響應(yīng)正文的例子溜歪。
如何編寫預(yù)過濾器
前置過濾器用于設(shè)置RequestContext中的數(shù)據(jù),用于下游的過濾器许蓖。主要用例是設(shè)置路由過濾器所需的信息蝴猪。
public class QueryParamPreFilter extends ZuulFilter {
@Override
public int filterOrder() {
return PRE_DECORATION_FILTER_ORDER - 1; // run before PreDecoration
}
@Override
public String filterType() {
return PRE_TYPE;
}
@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();
HttpServletRequest request = ctx.getRequest();
if (request.getParameter("foo") != null) {
? ? // put the serviceId in `RequestContext`
? ? ctx.put(SERVICE_ID_KEY, request.getParameter("foo"));
? ? }
? ? ? ? return null;
? ? }
}
上面的過濾器從foo請求參數(shù)填充SERVICE_ID_KEY。實(shí)際上膊爪,做這種直接映射并不是一個好主意自阱,而是從foo的值來查看服務(wù)ID。
現(xiàn)在填寫SERVICE_ID_KEY米酬,PreDecorationFilter將不會運(yùn)行沛豌,RibbonRoutingFilter將會。如果您想要路由到完整的網(wǎng)址淮逻,請改用ctx.setRouteHost(url)琼懊。
要修改路由過濾器將轉(zhuǎn)發(fā)的路徑,請?jiān)O(shè)置REQUEST_URI_KEY爬早。
如何編寫路由過濾器
路由過濾器在預(yù)過濾器之后運(yùn)行,并用于向其他服務(wù)發(fā)出請求启妹。這里的大部分工作是將請求和響應(yīng)數(shù)據(jù)轉(zhuǎn)換到客戶端所需的模型筛严。
public class OkHttpRoutingFilter extends ZuulFilter {
@Autowired
private ProxyRequestHelper helper;
@Override
public String filterType() {
return ROUTE_TYPE;
}
@Override
public int filterOrder() {
return SIMPLE_HOST_ROUTING_FILTER_ORDER - 1;
}
@Override
public boolean shouldFilter() {
return RequestContext.getCurrentContext().getRouteHost() != null
&& RequestContext.getCurrentContext().sendZuulResponse();
}
? ? @Override
? ? public Object run() {
OkHttpClient httpClient = new OkHttpClient.Builder()
// customize
.build();
RequestContext context = RequestContext.getCurrentContext();
HttpServletRequest request = context.getRequest();
String method = request.getMethod();
String uri = this.helper.buildZuulRequestURI(request);
Headers.Builder headers = new Headers.Builder();
Enumeration<String> headerNames = request.getHeaderNames();
while (headerNames.hasMoreElements()) {
String name = headerNames.nextElement();
Enumeration<String> values = request.getHeaders(name);
while (values.hasMoreElements()) {
String value = values.nextElement();
headers.add(name, value);
}
}
InputStream inputStream = request.getInputStream();
RequestBody requestBody = null;
if (inputStream != null && HttpMethod.permitsRequestBody(method)) {
MediaType mediaType = null;
if (headers.get("Content-Type") != null) {
mediaType = MediaType.parse(headers.get("Content-Type"));
}
requestBody = RequestBody.create(mediaType, StreamUtils.copyToByteArray(inputStream));
}
Request.Builder builder = new Request.Builder()
.headers(headers.build())
.url(uri)
.method(method, requestBody);
Response response = httpClient.newCall(builder.build()).execute();
LinkedMultiValueMap<String, String> responseHeaders = new LinkedMultiValueMap<>();
for (Map.Entry<String, List<String>> entry : response.headers().toMultimap().entrySet()) {
responseHeaders.put(entry.getKey(), entry.getValue());
}
this.helper.setResponse(response.code(), response.body().byteStream(),
responseHeaders);
context.setRouteHost(null); // prevent SimpleHostRoutingFilter from running
return null;
? ? }
}
上述過濾器將Servlet請求信息轉(zhuǎn)換為OkHttp3請求信息,執(zhí)行HTTP請求饶米,然后將OkHttp3響應(yīng)信息轉(zhuǎn)換為Servlet響應(yīng)桨啃。警告:此過濾器可能有錯誤车胡,但功能不正確。
如何編寫過濾器
后置過濾器通常操縱響應(yīng)照瘾。在下面的過濾器中匈棘,我們添加一個隨機(jī)UUID作為X-Foo頭。其他操作析命,如轉(zhuǎn)換響應(yīng)體主卫,要復(fù)雜得多,計(jì)算密集鹃愤。
public class AddResponseHeaderFilter extends ZuulFilter {
@Override
public String filterType() {
return POST_TYPE;
}
@Override
public int filterOrder() {
return SEND_RESPONSE_FILTER_ORDER - 1;
}
@Override
public boolean shouldFilter() {
return true;
}
@Override
public Object run() {
RequestContext context = RequestContext.getCurrentContext();
? ? HttpServletResponse servletResponse = context.getResponse();
servletResponse.addHeader("X-Foo", UUID.randomUUID().toString());
return null;
}
}
Zuul錯誤如何工作
如果在Zuul過濾器生命周期的任何部分拋出異常簇搅,則會執(zhí)行錯誤過濾器。SendErrorFilter只有RequestContext.getThrowable()不是null才會運(yùn)行软吐。然后在請求中設(shè)置特定的javax.servlet.error.*屬性瘩将,并將請求轉(zhuǎn)發(fā)到Spring Boot錯誤頁面。
Zuul渴望應(yīng)用程序上下文加載
Zuul內(nèi)部使用Ribbon調(diào)用遠(yuǎn)程URL凹耙,并且Ribbon客戶端默認(rèn)在第一次調(diào)用時由Spring Cloud加載依啰。可以使用以下配置更改Zuul的此行為霎终,并將導(dǎo)致在應(yīng)用程序啟動時谎势,子Ribbon相關(guān)的應(yīng)用程序上下文正在加載。
application.yml
zuul:
? ribbon:
? ? eager-load:
? ? ? enabled: true