Zuul默認使用的HTTP客戶端是Apache HTTPClient,主要功能是路由轉發和過濾器。zuul的核心是一系列的filters, 類似於java Servlet框架的Filter,或者AOP。zuul把請求路由到用戶處理邏輯的過程中,這些filter參與一些過濾處理,比如Authentication;外圍系統或者用戶通過網關訪問服務,網關通過註冊中心找到對應提供服務的客戶端(Ribbon獲取服務),網關也需要到註冊中心進行註冊。
Zuul可以爲同一註冊中心中的其他服務的訪問做反向代理(配置serviceId屬性),也可以爲外部服務訪問做代理(配置URL屬性),還可以對zuul服務本身的接口做代理(配置URL屬性的值爲forward關鍵字加路徑)
搭建Zuul網關服務實現路由和過濾
1.引入Zuul依賴
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-zuul</artifactId>
</dependency>
2.在啓動類上添加@EnableZuulProxy註解
@SpringBootApplication
@EnableZuulProxy
@EnableEurekaClient
public class ZuulApplication {
public static void main(String[] args) {
SpringApplication.run(ZuulApplication.class, args);
}
}
3.自定義配置zuul服務的路由 (zuul默認以服務ID作爲路由配置)
zuul默認情況下會以註冊中心中的服務id作爲路由設置,即如果請求是註冊中心中的某個serviceId組成的URL就路由給對應的服務;zuul中的服務列表使用的是Ribbon實現的;
瞭解下zuul節點下的配置屬性
zuul.prefix:api 配置在zuul節點下的屬性,值爲api 表示爲所有URL加一個前綴
zuul.ignoredServices:"*" 表示禁用默認的路由配置,值爲*表示禁用所有默認的路由,出去下面routes節點下配置的 路由外其他都無效。此屬性也可以禁用特定的某個或多個默認的服務ID路由,多個服務ID用逗號隔開。
zuul.routes: 這個節點是路由配置開始的節點,其下可以定義多個配置組
zuul.routes.path: 指的是用戶訪問的URL
zuul.routes.serviceId / zuul.routes.url: 路由目標服務在Eureka中的服務實例ID或者服務的url地址
在目標服務集成Eureka的情況下自定義路由(目標服務使用serviceId配置)
routes節點後配置路由,,。
當然,目標服務也可以使用URL代替serviceId,只不過這種情況下要實現負載訪問就要禁用Eureka的Ribbon,然以單獨配置Ribbon並執行負載的多個服務ID,這種方式感覺並沒有什麼用。。。。
如下示例配置了兩組路由,一組是customerURL,第二組是userURL。配置裏有prefix前綴屬性爲api,所以第一組配置就是把用戶訪問地址以api/customer/*的請求路由到customer-service這個服務中;第二組配置就是把api/user/*的請求路由到user-service這個服務中。
server:
port: 8040
spring:
application:
name: zuul
# 配置Eureka地址
eureka:
client:
service-url:
defaultZone: http://localhost:8761/eureka/
# 構建路由地址
zuul:
#所有URL都要加api這個前綴
prefix:api
# 禁用默認的路由設置,使用此配置以後只有下面配置的兩組路由纔會生效
ignoredServices:"*"
routes:
# 這裏可以自定義,只是一個唯一的標識
customerURL:
# 匹配的路由規則
path: /customer/**
# 路由的目標服務名
serviceId: customer-service
userURL:
# 匹配的路由規則
path: /user/**
# 路由的目標服務名
serviceId: user-service
上面這種配置中,如果某個服務有多個實例,以Eureka中集成Ribbon負載方式進行訪問;如果使用URL配置目標服務的路由,就要單獨配置Ribbon來實現負載:
server:
port: 8080
spring:
application:
name: zuul
# 配置eureka地址
eureka:
client:
service-url:
defaultZone: http://localhost:8761/eureka/
# 構建路由地址
zuul:
routes:
# 這裏可以自定義
demo2:
# 匹配的路由規則
path: /demo/**
# 使用URL配置,不適用服務ID
url: http://127.0.0.1:8070
# 關閉使用eureka負載路由
ribbon:
eureka:
enabled: false
# 如果不使用eureka的話,需要自己定義路由的那個服務的其他負載服務
demo:
ribbon:
# 這裏寫你要路由的demo服務的所有負載服務請求地址,本項目只啓動一個,因此只寫一個
listOfServers: http://localhost:8090/
對於path屬性的配置,要符合ant樣式:
user/* 這個配置只對user/id這種/後一個值得url生效
user/** 這個配置可以以user/開頭,任意個值得url生效。
不使用Eureka的情況下自定義配置路由(配置URL屬性代替serviceId,無負載均衡)
由於沒有Eureka註冊中心,也就沒有服務ID,只能使用URL地址配置服務的路由地址。
server:
port: 8080
spring:
application:
name: zuul
# 構建路由地址
zuul:
routes:
# 這裏可以自定義
demo2:
# 匹配的路由規則
path: /demo/**
# 路由的目標地址
url: http://localhost:8090/
4.Zuul過濾器配置
默認情況下,Spring Cloud Zuul在請求路由時,會過濾掉請求頭信息中的 一些敏感信息,防止它們被傳遞到下游的外部服務器。
zuul定義了四種標準過濾器類型,這四種過濾器分別對應着請求的生命週期
-
pre:路由請求前過濾。
-
post:路由請求後(此時已經走完目標服務程序了)過濾。
-
route:路由請求時過濾。
-
error:當上述三種過濾器拋出異常時,會走error過濾。
自定義並使用Zuul Filter
自定義過濾器很簡單,只要繼承ZuulFilter類並重寫相應的方法,用@Component註解作爲spring的bean使用即可。
ZuulFilter類提供瞭如下方法方便我們自定義和實現一個Zuul過濾器:
public String filterType():設置這個過濾器的類型是
public int filterOrder() :設置過濾器的優先級,多個同類型(filterType)ZuulFilter中哪個filter的此方法返回值最小優先級最高
public boolean shouldFilter() :決定此過濾器是否生效,返回true表示生效
public Object run() :過濾器的邏輯執行方法,在這個方法內調用doFiltrate方法
private boolean doFiltrate (HttpServletRequest request) :過濾邏輯,返回是否通過過濾(true或false)
下面是自定義一個pre類型的ZuulFilter的示例代碼:
import com.netflix.zuul.ZuulFilter;
import com.netflix.zuul.context.RequestContext;
import com.util.RequestBodyUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.PropertySource;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Component;
import javax.servlet.http.HttpServletRequest;
import java.util.Arrays;
import java.util.Random;
@Component
public class PreFilter extends ZuulFilter {
/**
* 定義過濾類型
* 【pre】路由請求前被調用過濾、
* 【post】後置過濾、
* 【error】錯誤過濾、
* 【route】路由請求時被調用
*/
@Override
public String filterType() {
// 設置這個過濾器的類型是 前置過濾器
return "pre";
}
/**
* 設置過濾器的優先級,如果服務中有多個同類型(filterType)的ZuulFilter
*
* 這個方法的值越小代表越先過濾
*/
@Override
public int filterOrder() {
return 0;
}
/**
* 決定此過濾器是否生效,返回true表示生效
*/
@Override
public boolean shouldFilter() {
return true;
}
/**
* 過濾器的邏輯執行方法,在這個方法內調用doFiltrate方法
*
* 注:當RequestContext.setSendZuulResponse(false);時表示過濾失敗,zuul不對其進行路由
*/
@Override
public Object run() {
// 獲取請求上下文
RequestContext requestContext = RequestContext.getCurrentContext();
// 獲取請求
HttpServletRequest request = requestContext.getRequest();
if (doFiltrate(request)) {
// 驗證通過
return null;
}
// 如果驗證不通過,那麼過濾該請求,不往下級服務去轉發請求,到此結束
requestContext.setSendZuulResponse(false);
requestContext.setResponseStatusCode(HttpStatus.FORBIDDEN.value());
requestContext.setResponseBody(HttpStatus.FORBIDDEN.getReasonPhrase());
requestContext.getResponse().setContentType("text/html;charset=UTF-8");
return null;
}
/**
* 執行過濾邏輯,返回是否通過過濾結果
*/
private boolean doFiltrate (HttpServletRequest request) {
try {
/// 獲取請求頭
String who = request.getHeader("Authorization");
log.info(" requestHeader param 【Authorization】 is -> {} !", who);
/// 向請求頭中添加信息
// requestContext.addZuulRequestHeader("");
// 獲取請求體
RequestBodyUtil requestBodyUtil = new RequestBodyUtil(request);
String requestBody = requestBodyUtil.getBody();
log.info(" got requestBady -> {}", requestBody);
// TODO 由於是測試代碼,這裏隨機返回 成功、失敗
return new Random().nextBoolean();
} catch (Exception e) {
log.error(" zull authe occur error !", e);
return false;
}
}
}
禁用Zuul中默認啓用的過濾器
上面提到了我們自己定義的filter如果不需要使用可以通過shouldFilter方法返回false實現對這個過濾器是禁用。但是Zuul默認啓用了一些filter的bean,如果我們不想使用顯然沒辦法通過修改方法來實現,這個時候我們可以通過配置yml來實現:
直接在配置文件中:zuul.<filtername>.<filtertype>.disable=true
例如:
zuul:
DebugFilter:
pre:
disable: true