1. 程式人生 > >拓展spring cloud gateway 動態路由解析及302問題

拓展spring cloud gateway 動態路由解析及302問題

開發十年,就只剩下這套架構體系了! >>>   

1.動態路由 需求:根據指定內網服務的ip和埠資訊提供反向代理。

原內網服務是釋出到外網上,通過業務邏輯在前端進行整合。後需要將這些內網服務隱藏到主服務之後。

第一版是通過自寫的netty進行實現。後改用spring cloud gateway進行實現。以方便後續維護。

實現效果:

  • innerhost1.innerport1.proxy.domain.com 訪問 內網服務1、innerhost2.innerport2.proxy.domain.com 訪問 內網服務2;
  • innerhost:內網服務地址 innerport 內網服務埠。

eg:127.0.0.1.8080.dev.localhost TO 127.0.0.1:8080

思路:參照gateway lb :RouteToRequestUrlFilter實現。

/**
 * route根據predicates配置進行匹配。因此只要制定符合需求的predicates便可獲得對應route及其uri。
 */
RoutePredicateHandlerMapping.lookupRoute(ServerWebExchange exchange):Mono<Route>

/**
 * 在filter中 RouteToRequestUrlFilter將route.uri解析並存儲到 ServerWebExchange.getAttributes():GATEWAY_REQUEST_URL_ATTR 中
 因此只要proxyfilter在RouteToRequestUrlFilter之後執行即可直接使用該uri
 */
RouteToRequestUrlFilter.filter(ServerWebExchange exchange, GatewayFilterChain chain):Mono<Void>

實現:

package.component:
@Component
public class ProxyFilter implements GlobalFilter, Ordered {
	@Override
	public int getOrder() {
	// 定義在RouteToRequestUrlFilter之後執行
		return RouteToRequestUrlFilter.ROUTE_TO_URL_FILTER_ORDER + 1000;
	}
	/**
	* 參照RouteToRequestUrlFilter實現
	*/
	@Override
	public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
		URI url = exchange.getAttribute(GATEWAY_REQUEST_URL_ATTR);
		String schemePrefix = exchange.getAttribute(GATEWAY_SCHEME_PREFIX_ATTR);
		if (url == null || (!"proxy".equals(url.getScheme()) && !"proxy".equals(schemePrefix))) {
			return chain.filter(exchange);
		}
		addOriginalRequestUrl(exchange, url);
		// TODO 根據配置中的beanName對url進行處理
		String beanName = url.getHost();
		IProxy proxy = null;
		try {
			proxy = SpringContextUtils.getBean(beanName);
		} catch (Exception e) {
			log.error("異常{}",     e.getLocalizedMessage());
		}
		if (proxy == null) {
			log.error("異常URI{} 解析:N{}", url, beanName);
			return chain.filter(exchange);
		}

		URI requestUrl = proxy.getProxyUri(exchange);
		if (requestUrl == null) {
			log.error("異常URI{} 解析:N{}", url, beanName);
			requestUrl = url;
		}

		exchange.getAttributes().put(GATEWAY_REQUEST_URL_ATTR, requestUrl);
		Mono<Void> mono = chain.filter(exchange);
		proxy.beforeSend(exchange);

		return mono;
	}
}
package.component:
public interface IProxy {

	public URI getProxyUri(ServerWebExchange exchange);

	public default void beforeSend(ServerWebExchange exchange) {
	}
}
package.domain:
@Configuration
public class ProxyRemote {
	private static URI error404URI;
	static {
		try {
			error404URI = new URI("http://www.localhost/common/404/404.html");
		} catch (URISyntaxException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}
	@Bean
	public IProxy dev() {
		return exchange -> {
			URI uri = exchange.getAttribute(GATEWAY_REQUEST_URL_ATTR);
			if (uri == null)
				return error404URI;
			String host = exchange.getRequest().getURI().toString();
			if (StringUtils.isEmpty(host) && host.contains("://") && host.contains(".dev")) {
				log.error("異常URI{} .", host);
				return error404URI;
			}
			int i;
			host = host.substring((i = host.indexOf("://") + 3), host.indexOf(".dev", i));

			if (!host.contains(".")) {
				return error404URI;
			}

			String port = host.substring((i = host.lastIndexOf(".")) + 1);
			host = host.substring(0, i);
			if (!NetUtils.isIp(host)) {
				return error404URI;
			}
			URI newUri;
			try {
				newUri = new URI("http://" + host + ":" + port + uri.getPath().toString()
						+ (Objs.isEmpty(uri.getQuery()) ? "" : "?" + uri.getQuery()));
				log.info("ASK:{},R:{}", exchange.getRequest().getURI().toString(), newUri);
				return newUri;
			} catch (URISyntaxException e) {
				e.printStackTrace();
			}
			return error404URI;
		};
	}
}

application.yml中配置如下:

proxyBeanName:
  dev: dev
spring:
  cloud:
    gateway:
      routes:
      - id: proxy-dev
        uri: proxy://${proxyBeanName.dev:dev}
        predicates:
        - Host=**.${proxyBeanName.dev:dev}.**

github: https://github.com/yuyizyk/note/tree/master/notes/spring-cloud-gateway-enhance

2.302問題 在gateway代理子服務中,一但發生302重定向:response.sendRedirect("/~");

那麼在放回的resp的header.location 中可以發現該重定向路徑是其內網服務地址(根據gateway代理訪問地址)自動補全後的路徑。而並非是gateway所代表的公網服務路徑。

參照:https://blog.csdn.net/tianyaleixiaowu/article/details/83618037

將其響應的header.location進行修改。即可完成處理302問題。

/**
 * 修改 302 返回host
 * 
 * 
 *
 * @author yuyi
 */
@Slf4j
@Component
public class WrapperResponseGlobalFilter implements GlobalFilter, Ordered {
	@Override
	public int getOrder() {
		return NettyWriteResponseFilter.WRITE_RESPONSE_FILTER_ORDER - 1;
	}
	@Override
	public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
		ServerHttpResponse originalResponse = exchange.getResponse();
		URI uri = exchange.getRequest().getURI();
//		DataBufferFactory bufferFactory = originalResponse.bufferFactory();
		ServerHttpResponseDecorator decoratedResponse = new ServerHttpResponseDecorator(originalResponse) {
			@SuppressWarnings("serial")
			@Override
			public Mono<Void> writeWith(Publisher<? extends DataBuffer> body) {
				switch (getDelegate().getStatusCode().value()) {
				case 302:
				case 301:
					HttpHeaders headers = getDelegate().getHeaders();
					String location = headers.getFirst(HttpHeaders.LOCATION);
					int i = -1;
					if (StringUtils.isEmpty(location) || (i = location.indexOf("/", 8)) == -1) {
						log.error("異常URI{} .", location);
						break;
					}

					String newLocation = uri.getScheme() + "://" + uri.getHost() + ":"
							+ (uri.getPort() > 0 ? uri.getPort() : 80) + location.substring(i);
					log.debug("ASK {}  for RELP URL :{}  TO {} ", uri.toString(), location, newLocation);
					headers.put(HttpHeaders.LOCATION, new ArrayList<String>() {
						{
							add(newLocation);
						}
					});
					break;
				default:
					break;
				}
				return super.writeWith(body);
			}
		};
		// replace response with decorator
		return chain.filter(exchange.mutate().response(decoratedResponse).bui