通過Zuul上傳文件

如果您@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ù)雜查詢字符串的編碼來挑選。

要強制查詢字符串的原始編碼,可以將特殊標(biāo)志傳遞給ZuulProperties,以便查詢字符串與HttpServletRequest::getQueryString方法相同:

application.yml

zuul:

? forceOriginalQueryStringEncoding: true

注意:此特殊標(biāo)志僅適用于SimpleHostRoutingFilter,您可以使用RequestContext.getCurrentContext().setRequestQueryParams(someOverriddenParameters)輕松覆蓋查詢參數(shù),因為查詢字符串現(xiàn)在直接在原始的HttpServletRequest上獲取。

普通嵌入Zuul

如果您使用@EnableZuulServer(而不是@EnableZuulProxy),您也可以運行不帶代理的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實現(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被實現(xiàn)為Servlet。對于一般情況,Zuul嵌入到Spring調(diào)度機制中。這允許Spring MVC控制路由。在這種情況下,Zuul被配置為緩沖請求。如果需要通過Zuul不緩沖請求(例如大文件上傳),Servlet也將安裝在Spring調(diào)度程序之外。默認(rèn)情況下,它位于/zuul??梢允褂脄uul.servlet-path屬性更改此路徑。

Zuul RequestContext

要在過濾器之間傳遞信息,Zuul使用a RequestContext。其數(shù)據(jù)按照每個請求的ThreadLocal進(jìn)行。關(guān)于路由請求,錯誤以及實際HttpServletRequest和HttpServletResponse的路由信息??。RequestContext擴展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)用程序中的端點很有用。

過濾器:

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過濾器項目。還有一些操作該存儲庫中的請求或響應(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。實際上,做這種直接映射并不是一個好主意,而是從foo的值來查看服務(wù)ID。

現(xiàn)在填寫SERVICE_ID_KEY,PreDecorationFilter將不會運行,RibbonRoutingFilter將會。如果您想要路由到完整的網(wǎng)址,請改用ctx.setRouteHost(url)。

要修改路由過濾器將轉(zhuǎn)發(fā)的路徑,請設(shè)置REQUEST_URI_KEY。

如何編寫路由過濾器

路由過濾器在預(yù)過濾器之后運行,并用于向其他服務(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)。在下面的過濾器中,我們添加一個隨機UUID作為X-Foo頭。其他操作,如轉(zhuǎn)換響應(yīng)體,要復(fù)雜得多,計算密集。

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才會運行。然后在請求中設(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

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容