默认的GlobalFilter学习

本文详细分析了Spring Cloud Gateway的源码,特别是其核心组件GlobalFilter的实现,包括RemoveCachedBodyFilter、AdaptCachedBodyGlobalFilter、NettyWriteResponseFilter和NettyRoutingFilter等。这些过滤器在请求和响应处理中扮演关键角色,如缓存处理、路由转发和响应写回等操作。通过对源码的深入理解,有助于更好地掌握Spring Cloud Gateway的工作原理。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Spring-Cloud-Gateway源码系列学习

版本 v2.2.6.RELEASE

默认的GlobalFilter都有哪些

在这里插入图片描述

根据debug可以得出以下order顺序表格,一个请求都会经过这些GlobalFilter,这些 GlobalFilter 会在 FilteringWebHandler 通过 GatewayFilterAdapter 适配成GatewayFilter

GlobalFilter类order
RemoveCachedBodyFilterInteger.MIN_VALUE
AdaptCachedBodyGlobalFilterInteger.MIN_VALUE + 1000
NettyWriteResponseFilter-1
ForwardPathFilter0
GatewayMetricsFilter0
RouteToRequestUrlFilter10000
LoadBalancerClientFilter10100
WebsocketRoutingFilterInteger.MAX_VALUE
NettyRoutingFilterInteger.MAX_VALUE
ForwardRoutingFilterInteger.MAX_VALUE

tip:其中最应该关注的分别是NettyWriteResponseFilter和NettyRoutingFilter,NettyRoutingFilter负责发送请求到route目标网址(需要合并),而NettyWriteResponseFilter负责把响应结果发回客户端(根据是否是流数据,分别处理)

RemoveCachedBodyFilter源码分析

在流结束之前移除exchange上下文里的CACHED_REQUEST_BODY_ATTR

public class RemoveCachedBodyFilter implements GlobalFilter, Ordered {

	private static final Log log = LogFactory.getLog(RemoveCachedBodyFilter.class);

	@Override
	public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        //注意doFinally,相当于try finally,在流结束之前做的,也就是最后才移除CACHED_REQUEST_BODY_ATTR
		return chain.filter(exchange).doFinally(s -> {
            //倒数第一执行
            //移除对request body的缓存
			Object attribute = exchange.getAttributes().remove(CACHED_REQUEST_BODY_ATTR);
			if (attribute != null && attribute instanceof PooledDataBuffer) {
				PooledDataBuffer dataBuffer = (PooledDataBuffer) attribute;
				//释放堆外内存
				if (dataBuffer.isAllocated()) {
					if (log.isTraceEnabled()) {
						log.trace("releasing cached body in exchange attribute");
					}
					dataBuffer.release();
				}
			}
		});
	}

    //在Ordered接口里面 int HIGHEST_PRECEDENCE = Integer.MIN_VALUE;最高优先级
	@Override
	public int getOrder() {
		return HIGHEST_PRECEDENCE;
	}

}

AdaptCachedBodyGlobalFilter源码分析

这一步主要是判断需不需要进行RequestBody的缓存,如果有缓存则对exchange进行一些调整(替换成缓存的)

public class AdaptCachedBodyGlobalFilter
		implements GlobalFilter, Ordered, ApplicationListener<EnableBodyCachingEvent> {

    //记录需要缓存的 routeId
	private ConcurrentMap<String, Boolean> routesToCache = new ConcurrentHashMap<>();

    //通过事件驱动 更新需要缓存的 routeId
	@Override
	public void onApplicationEvent(EnableBodyCachingEvent event) {
		this.routesToCache.putIfAbsent(event.getRouteId(), true);
	}

	@Override
	public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
		// the cached ServerHttpRequest is used when the ServerWebExchange can not be
		// mutated, for example, during a predicate where the body is read, but still
		// needs to be cached.
        //如果RequestBody和Request都缓存的话,将会获得到,@see ServerWebExchangeUtils#cacheRequestBodyAndRequest return执行方法的第二个参数
        /**
		 * @see RetryGatewayFilterFactory#apply(java.lang.String, reactor.retry.Repeat, reactor.retry.Retry)
		 * @param event
		 */
		ServerHttpRequest cachedRequest = exchange
				.getAttributeOrDefault(CACHED_SERVER_HTTP_REQUEST_DECORATOR_ATTR, null);
		if (cachedRequest != null) {
            //移除
			exchange.getAttributes().remove(CACHED_SERVER_HTTP_REQUEST_DECORATOR_ATTR);
            //替换exchange 的 request 为 缓存的 cachedRequest
			return chain.filter(exchange.mutate().request(cachedRequest).build());
		}

		//获取 CACHED_REQUEST_BODY_ATTR
		DataBuffer body = exchange.getAttributeOrDefault(CACHED_REQUEST_BODY_ATTR, null);
        //获取 Route
		Route route = exchange.getAttribute(GATEWAY_ROUTE_ATTR);

        //如果 body 不为空 或者 Route不需要缓存 直接开始下一个filter
		if (body != null || !this.routesToCache.containsKey(route.getId())) {
			return chain.filter(exchange);
		}
		
        //ServerWebExchangeUtils.cacheRequestBody就是缓存RequestBody,但不会缓存整个Request,也就是不会保存CACHED_SERVER_HTTP_REQUEST_DECORATOR_ATTR
        //进行缓存
		return ServerWebExchangeUtils.cacheRequestBody(exchange, (serverHttpRequest) -> {
			// don't mutate and build if same request object
            // 如果相等则不需要修改exchange的request
			if (serverHttpRequest == exchange.getRequest()) {
				return chain.filter(exchange);
			}
			return chain.filter(exchange.mutate().request(serverHttpRequest).build());
		});
	}

    //Integer.MIN_VALUE + 1000
	@Override
	public int getOrder() {
		return Ordered.HIGHEST_PRECEDENCE + 1000;
	}

}

NettyWriteResponseFilter源码分析

跟NettyRoutingFilter一对的,NettyRoutingFilter负责请求目标地址返回响应结果,NettyWriteResponseFilter负责把响应结果发回给客户端

public class NettyWriteResponseFilter implements GlobalFilter, Ordered {

	/**
	 * Order for write response filter.
	 */
	public static final int WRITE_RESPONSE_FILTER_ORDER = -1;

	private static final Log log = LogFactory.getLog(NettyWriteResponseFilter.class);

	private final List<MediaType> streamingMediaTypes;

	public NettyWriteResponseFilter(List<MediaType> streamingMediaTypes) {
		this.streamingMediaTypes = streamingMediaTypes;
	}

    // -1
	@Override
	public int getOrder() {
		return WRITE_RESPONSE_FILTER_ORDER;
	}

	@Override
	public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
		// NOTICE: nothing in "pre" filter stage as CLIENT_RESPONSE_CONN_ATTR is not added
		// until the NettyRoutingFilter is run
		// @formatter:off
		return chain.filter(exchange)
				.doOnError(throwable -> cleanup(exchange))
				.then(Mono.defer(() -> {
                    //倒数第二执行这里,注意.then
                    //获得NettyRoutingFilter返回的Response
					Connection connection = exchange.getAttribute(CLIENT_RESPONSE_CONN_ATTR);
					if (connection == null) {
                        //如果NettyRoutingFilter 返回的 connection为null,直接返回空
						return Mono.empty();
					}
					if (log.isTraceEnabled()) {
						log.trace("NettyWriteResponseFilter start inbound: "
								+ connection.channel().id().asShortText() + ", outbound: "
								+ exchange.getLogPrefix());
					}
                    // 
					ServerHttpResponse response = exchange.getResponse();

					// TODO: needed?
                    //获得connection的inbound数据流,并转成DataBuffer类型元素
					final Flux<DataBuffer> body = connection
							.inbound()
							.receive()
							.retain()
							.map(byteBuf -> wrap(byteBuf, response));

                    //获取响应内容类型
					MediaType contentType = null;
					try {
						contentType = response.getHeaders().getContentType();
					}
					catch (Exception e) {
						if (log.isTraceEnabled()) {
							log.trace("invalid media type", e);
						}
					}
                    //针对流类型按流和普通的分表处理
					return (isStreamingMediaType(contentType)
							? response.writeAndFlushWith(body.map(Flux::just))
							: response.writeWith(body));
				})).doOnCancel(() -> cleanup(exchange));
		// @formatter:on
	}

    //把netty请求的ByteBuf转成DataBuffer
	protected DataBuffer wrap(ByteBuf byteBuf, ServerHttpResponse response) {
		DataBufferFactory bufferFactory = response.bufferFactory();
		if (bufferFactory instanceof NettyDataBufferFactory) {
			NettyDataBufferFactory factory = (NettyDataBufferFactory) bufferFactory;
			return factory.wrap(byteBuf);
		}
		// MockServerHttpResponse creates these
		else if (bufferFactory instanceof DefaultDataBufferFactory) {
			DataBuffer buffer = ((DefaultDataBufferFactory) bufferFactory)
					.allocateBuffer(byteBuf.readableBytes());
			buffer.write(byteBuf.nioBuffer());
			byteBuf.release();
			return buffer;
		}
		throw new IllegalArgumentException(
				"Unkown DataBufferFactory type " + bufferFactory.getClass());
	}

	private void cleanup(ServerWebExchange exchange) {
		Connection connection = exchange.getAttribute(CLIENT_RESPONSE_CONN_ATTR);
		if (connection != null) {
			connection.dispose();
		}
	}

	// TODO: use framework if possible
	// TODO: port to WebClientWriteResponseFilter
	private boolean isStreamingMediaType(@Nullable MediaType contentType) {
		return (contentType != null && this.streamingMediaTypes.stream()
				.anyMatch(contentType::isCompatibleWith));
	}

}

ForwardPathFilter源码分析

判断是不是forward,是则替换request的path

public class ForwardPathFilter implements GlobalFilter, Ordered {

	@Override
	public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        //获取Route
		Route route = exchange.getAttribute(GATEWAY_ROUTE_ATTR);
        //获取Uri
		URI routeUri = route.getUri();
        //获取协议,如https
		String scheme = routeUri.getScheme();
        //如果已经处理了(可以看看NettyRoutingFilter#filter)或者不是转发的
		if (isAlreadyRouted(exchange) || !"forward".equals(scheme)) {
            //下个filter
			return chain.filter(exchange);
		}
        //如果forward则替换path
		exchange = exchange.mutate()
				.request(exchange.getRequest().mutate().path(routeUri.getPath()).build())
				.build();
		return chain.filter(exchange);
	}

	@Override
	public int getOrder() {
		return 0;
	}

}

RouteToRequestUrlFilter源码分析

该类主要是拼接目标uri,例如 request = http://localhost:8080/111, route.uri = https://www.baidu.com,则mergeuri = https://www.baidu.com/111

public class RouteToRequestUrlFilter implements GlobalFilter, Ordered {

	/**
	 * Order of Route to URL.
	 */
	public static final int ROUTE_TO_URL_FILTER_ORDER = 10000;

	private static final Log log = LogFactory.getLog(RouteToRequestUrlFilter.class);

	private static final String SCHEME_REGEX = "[a-zA-Z]([a-zA-Z]|\\d|\\+|\\.|-)*:.*";
	static final Pattern schemePattern = Pattern.compile(SCHEME_REGEX);

	/* for testing */
	static boolean hasAnotherScheme(URI uri) {
		return schemePattern.matcher(uri.getSchemeSpecificPart()).matches()
				&& uri.getHost() == null && uri.getRawPath() == null;
	}

	@Override
	public int getOrder() {
		return ROUTE_TO_URL_FILTER_ORDER;
	}

	@Override
	public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        //获取Route
		Route route = exchange.getAttribute(GATEWAY_ROUTE_ATTR);
		if (route == null) {
			return chain.filter(exchange);
		}
		log.trace("RouteToRequestUrlFilter start");
        //获取请求uri
		URI uri = exchange.getRequest().getURI();
		boolean encoded = containsEncodedParts(uri);
        //获取目标uri
		URI routeUri = route.getUri();

        //如果是其他协议
		if (hasAnotherScheme(routeUri)) {
			// this is a special url, save scheme to special attribute
			// replace routeUri with schemeSpecificPart
			exchange.getAttributes().put(GATEWAY_SCHEME_PREFIX_ATTR,
					routeUri.getScheme());
			routeUri = URI.create(routeUri.getSchemeSpecificPart());
		}
		
        
		if ("lb".equalsIgnoreCase(routeUri.getScheme()) && routeUri.getHost() == null) {
			// Load balanced URIs should always have a host. If the host is null it is
			// most
			// likely because the host name was invalid (for example included an
			// underscore)
			throw new IllegalStateException("Invalid host: " + routeUri.toString());
		}

        //拼接 目标 Uri
		URI mergedUrl = UriComponentsBuilder.fromUri(uri)
				// .uri(routeUri)
				.scheme(routeUri.getScheme()).host(routeUri.getHost())
				.port(routeUri.getPort()).build(encoded).toUri();
        //存进GATEWAY_REQUEST_URL_ATTR
		exchange.getAttributes().put(GATEWAY_REQUEST_URL_ATTR, mergedUrl);
        //下一个filter
		return chain.filter(exchange);
	}

}

NettyRoutingFilter

真正发送请求去目标地址,拿回响应结果

public class NettyRoutingFilter implements GlobalFilter, Ordered {

	private static final Log log = LogFactory.getLog(NettyRoutingFilter.class);

	private final HttpClient httpClient;

	private final ObjectProvider<List<HttpHeadersFilter>> headersFiltersProvider;

	private final HttpClientProperties properties;

	// do not use this headersFilters directly, use getHeadersFilters() instead.
	private volatile List<HttpHeadersFilter> headersFilters;

	public NettyRoutingFilter(HttpClient httpClient,
			ObjectProvider<List<HttpHeadersFilter>> headersFiltersProvider,
			HttpClientProperties properties) {
		this.httpClient = httpClient;
		this.headersFiltersProvider = headersFiltersProvider;
		this.properties = properties;
	}

	public List<HttpHeadersFilter> getHeadersFilters() {
		if (headersFilters == null) {
			headersFilters = headersFiltersProvider.getIfAvailable();
		}
		return headersFilters;
	}

	@Override
	public int getOrder() {
		return Ordered.LOWEST_PRECEDENCE;
	}

	@Override
	@SuppressWarnings("Duplicates")
	public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        //获取RouteToRequestUrlFilter合并好的uri
		URI requestUrl = exchange.getRequiredAttribute(GATEWAY_REQUEST_URL_ATTR);

        //获取协议
		String scheme = requestUrl.getScheme();
        //只能处理http/https协议的,如果有其他filter处理了这个请求(isAlreadyRouted)则不做处理
		if (isAlreadyRouted(exchange)
				|| (!"http".equals(scheme) && !"https".equals(scheme))) {
			return chain.filter(exchange);
		}
        //设置这个请求为已处理
		setAlreadyRouted(exchange);

        
		ServerHttpRequest request = exchange.getRequest();

        //获取请求 method (GET/POST...)
		final HttpMethod method = HttpMethod.valueOf(request.getMethodValue());
        
		final String url = requestUrl.toASCIIString();

		HttpHeaders filtered = filterRequest(getHeadersFilters(), exchange);
		
        //Request Header
		final DefaultHttpHeaders httpHeaders = new DefaultHttpHeaders();
		filtered.forEach(httpHeaders::set);

		boolean preserveHost = exchange
				.getAttributeOrDefault(PRESERVE_HOST_HEADER_ATTRIBUTE, false);
		Route route = exchange.getAttribute(GATEWAY_ROUTE_ATTR);

        //创建请求流
		Flux<HttpClientResponse> responseFlux = getHttpClient(route, exchange)
				.headers(headers -> {
					headers.add(httpHeaders);
					// Will either be set below, or later by Netty
					headers.remove(HttpHeaders.HOST);
					if (preserveHost) {
						String host = request.getHeaders().getFirst(HttpHeaders.HOST);
						headers.add(HttpHeaders.HOST, host);
					}
				}).request(method).uri(url).send((req, nettyOutbound) -> {
					if (log.isTraceEnabled()) {
						nettyOutbound
								.withConnection(connection -> log.trace("outbound route: "
										+ connection.channel().id().asShortText()
										+ ", inbound: " + exchange.getLogPrefix()));
					}
					return nettyOutbound.send(request.getBody().map(this::getByteBuf));
				}).responseConnection((res, connection) -> {

					// Defer committing the response until all route filters have run
					// Put client response as ServerWebExchange attribute and write
					// response later NettyWriteResponseFilter
            		//保存响应信息到exchange上下文,流到NettyWriteResponseFilter的.then把响应结果发回客户端
            		//把响应结果放到CLIENT_RESPONSE_ATTR
					exchange.getAttributes().put(CLIENT_RESPONSE_ATTR, res);
            		//把connection放到CLIENT_RESPONSE_CONN_ATTR
					exchange.getAttributes().put(CLIENT_RESPONSE_CONN_ATTR, connection);

					ServerHttpResponse response = exchange.getResponse();
					// put headers and status so filters can modify the response
					HttpHeaders headers = new HttpHeaders();

					res.responseHeaders().forEach(
							entry -> headers.add(entry.getKey(), entry.getValue()));

            		//获取数据类型,并保存到ORIGINAL_RESPONSE_CONTENT_TYPE_ATTR
					String contentTypeValue = headers.getFirst(HttpHeaders.CONTENT_TYPE);
					if (StringUtils.hasLength(contentTypeValue)) {
						exchange.getAttributes().put(ORIGINAL_RESPONSE_CONTENT_TYPE_ATTR,
								contentTypeValue);
					}

            		//把请求目标地址返回的status设置到exchange.getResponse
					setResponseStatus(res, response);

					//确保Response的HttpHeadersFilter在请求响应后执行
					HttpHeaders filteredResponseHeaders = HttpHeadersFilter.filter(
							getHeadersFilters(), headers, exchange, Type.RESPONSE);

					if (!filteredResponseHeaders
							.containsKey(HttpHeaders.TRANSFER_ENCODING)
							&& filteredResponseHeaders
									.containsKey(HttpHeaders.CONTENT_LENGTH)) {
						// It is not valid to have both the transfer-encoding header and
						// the content-length header.
						// Remove the transfer-encoding header in the response if the
						// content-length header is present.
						response.getHeaders().remove(HttpHeaders.TRANSFER_ENCODING);
					}

					exchange.getAttributes().put(CLIENT_RESPONSE_HEADER_NAMES,
							filteredResponseHeaders.keySet());

					response.getHeaders().putAll(filteredResponseHeaders);

					return Mono.just(res);
				});

		Duration responseTimeout = getResponseTimeout(route);
		if (responseTimeout != null) {
            //发起请求
			responseFlux = responseFlux
                	//设置响应超时时间
					.timeout(responseTimeout, Mono.error(new TimeoutException(
							"Response took longer than timeout: " + responseTimeout)))
                	//传播一个超时异常
					.onErrorMap(TimeoutException.class,
							th -> new ResponseStatusException(HttpStatus.GATEWAY_TIMEOUT,
									th.getMessage(), th));
		}

		return responseFlux.then(chain.filter(exchange));
	}
	
    //把DataBuffer转成ByteBuf,用于netty请求目标地址
	protected ByteBuf getByteBuf(DataBuffer dataBuffer) {
		if (dataBuffer instanceof NettyDataBuffer) {
			NettyDataBuffer buffer = (NettyDataBuffer) dataBuffer;
			return buffer.getNativeBuffer();
		}
		// MockServerHttpResponse creates these
		else if (dataBuffer instanceof DefaultDataBuffer) {
			DefaultDataBuffer buffer = (DefaultDataBuffer) dataBuffer;
			return Unpooled.wrappedBuffer(buffer.getNativeBuffer());
		}
		throw new IllegalArgumentException(
				"Unable to handle DataBuffer of type " + dataBuffer.getClass());
	}

    //把请求目标地址返回的status设置到exchange.getResponse
	private void setResponseStatus(HttpClientResponse clientResponse,
			ServerHttpResponse response) {
		HttpStatus status = HttpStatus.resolve(clientResponse.status().code());
		if (status != null) {
			response.setStatusCode(status);
		}
		else {
			while (response instanceof ServerHttpResponseDecorator) {
				response = ((ServerHttpResponseDecorator) response).getDelegate();
			}
			if (response instanceof AbstractServerHttpResponse) {
				((AbstractServerHttpResponse) response)
						.setStatusCodeValue(clientResponse.status().code());
			}
			else {
				// TODO: log warning here, not throw error?
				throw new IllegalStateException("Unable to set status code "
						+ clientResponse.status().code() + " on response of type "
						+ response.getClass().getName());
			}
		}
	}

	/**
	 * Creates a new HttpClient with per route timeout configuration. Sub-classes that
	 * override, should call super.getHttpClient() if they want to honor the per route
	 * timeout configuration.
	 * @param route the current route.
	 * @param exchange the current ServerWebExchange.
	 * @param chain the current GatewayFilterChain.
	 * @return
	 */
    //创建一个基于Netty的HttpClient
	protected HttpClient getHttpClient(Route route, ServerWebExchange exchange) {
		Object connectTimeoutAttr = route.getMetadata().get(CONNECT_TIMEOUT_ATTR);
		if (connectTimeoutAttr != null) {
			Integer connectTimeout = getInteger(connectTimeoutAttr);
			return this.httpClient.tcpConfiguration((tcpClient) -> tcpClient
					.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, connectTimeout));
		}
		return httpClient;
	}

    //根据route的Metadata的CONNECT_TIMEOUT_ATTR获取连接超时时间
	static Integer getInteger(Object connectTimeoutAttr) {
		Integer connectTimeout;
		if (connectTimeoutAttr instanceof Integer) {
			connectTimeout = (Integer) connectTimeoutAttr;
		}
		else {
			connectTimeout = Integer.parseInt(connectTimeoutAttr.toString());
		}
		return connectTimeout;
	}

    //根据route获取响应超时时间
	private Duration getResponseTimeout(Route route) {
		Object responseTimeoutAttr = route.getMetadata().get(RESPONSE_TIMEOUT_ATTR);
		Long responseTimeout = null;
		if (responseTimeoutAttr != null) {
			if (responseTimeoutAttr instanceof Number) {
				responseTimeout = ((Number) responseTimeoutAttr).longValue();
			}
			else {
				responseTimeout = Long.valueOf(responseTimeoutAttr.toString());
			}
		}
		return responseTimeout != null ? Duration.ofMillis(responseTimeout)
				: properties.getResponseTimeout();
	}

}

ForwardRoutingFilter源码分析

判断是不是forward,是则将请求转发给 DispatcherHandler,当作当前实例的url处理

public class ForwardRoutingFilter implements GlobalFilter, Ordered {

	private static final Log log = LogFactory.getLog(ForwardRoutingFilter.class);

	private final ObjectProvider<DispatcherHandler> dispatcherHandlerProvider;

	// do not use this dispatcherHandler directly, use getDispatcherHandler() instead.
	private volatile DispatcherHandler dispatcherHandler;

	public ForwardRoutingFilter(
			ObjectProvider<DispatcherHandler> dispatcherHandlerProvider) {
		this.dispatcherHandlerProvider = dispatcherHandlerProvider;
	}

	private DispatcherHandler getDispatcherHandler() {
		if (dispatcherHandler == null) {
			dispatcherHandler = dispatcherHandlerProvider.getIfAvailable();
		}

		return dispatcherHandler;
	}

    //Integer.MIN_VALUE
	@Override
	public int getOrder() {
		return Ordered.LOWEST_PRECEDENCE;
	}

	@Override
	public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        // 获得 request uri
		URI requestUrl = exchange.getRequiredAttribute(GATEWAY_REQUEST_URL_ATTR);

        // 判断是否能够处理
        //加入请求url为http://127.0.0.1:8080/globalfilters,route的目标url为forward:///globalfilters
        //那么它会将请求转发给 DispatcherHandler,DispatcherHandler 匹配并转发到当前网关实例本地接口/globalfilters
		String scheme = requestUrl.getScheme();
		if (isAlreadyRouted(exchange) || !"forward".equals(scheme)) {
			return chain.filter(exchange);
		}

		// TODO: translate url?

		if (log.isTraceEnabled()) {
			log.trace("Forwarding to URI: " + requestUrl);
		}

        //将请求转发给 DispatcherHandler,当作当前实例的链接处理
		return this.getDispatcherHandler().handle(exchange);
	}

}
<think>好的,用户现在的问题是如何在基于WebMVC的应用(比如Spring MVC)中实现代理服务器的逻辑,特别是处理CONNECT请求,以避免之前的501错误。用户之前遇到了使用HttpClient配置代理到8080端口,但该端口运行的是一个普通的WebMVC应用,无法处理代理请求,导致连接失败。 首先,我需要回顾之前的对话。用户最初的问题是关于代理配置出错,得到501状态码,并且发现请求没有进入8080服务的调试。之前的回复解释了原因,即WebMVC应用没有处理代理逻辑,特别是CONNECT方法,导致代理握手失败。现在用户进一步询问如何在WebMVC应用中实现代理服务器的逻辑,这也是他们最初的疑问。 接下来,我需要考虑用户的实际场景。用户可能是在开发过程中需要将某个现有的WebMVC应用作为代理服务器使用,或者希望调试经过该应用的流量。常见的情况可能是在本地开发环境中需要捕获或转发请求,但不想使用专门的代理工具,而是希望通过现有的Spring Boot应用来实现。 用户的真实需求可能包括: 1. 在现有的Spring MVC应用中添加代理功能,能够处理代理请求,特别是HTTPS使用的CONNECT方法。 2. 避免使用外部代理工具,简化开发环境配置。 3. 理解如何在应用层实现请求转发,处理代理相关的HTTP方法。 用户的深层需求可能是希望有一个集成的解决方案,将代理功能嵌入到现有的服务中,方便调试或特定业务逻辑的处理。例如,可能需要记录或修改经过代理的请求,或者在转发前进行身份验证等。 现在需要解决的问题是如何在WebMVC应用中实现代理逻辑。首先,需要明确普通的WebMVC应用(如Spring MVC)并不是设计用来作为代理服务器的,因此需要手动处理代理请求的转发,尤其是CONNECT方法。对于HTTP代理,客户端会发送CONNECT请求来建立隧道,代理服务器需要处理这个请求,然后转发后续的数据。 可能的解决方案步骤包括: 1. 捕获并处理CONNECT请求,建立与目标服务器的连接。 2. 对于普通HTTP请求,进行转发。 3. 处理HTTPS流量的隧道传输,这需要正确处理CONNECT方法,并在建立连接后转发数据。 但是,在Servlet容器(如Tomcat)中,处理底层的TCP连接或处理CONNECT方法可能比较困难,因为Servlet API主要面向HTTP请求处理,而不是底层的网络操作。因此,可能需要使用更底层的库或框架,比如Netty,或者借助Spring的WebClient或RestTemplate来转发请求。 用户可能希望尽量利用现有的Spring MVC结构,因此需要考虑如何在Controller中处理这些请求。不过,普通的Controller可能无法直接处理CONNECT方法,因为Servlet容器可能不支持。此时,可能需要配置一个独立的服务器来处理代理请求,或者在Spring Boot应用中嵌入一个Netty服务器来处理代理逻辑。 另外,实现一个完整的代理服务器需要考虑很多细节,比如连接管理、超时处理、错误处理、并发请求等,这可能超出了简单的WebMVC应用的范围。因此,可能需要权衡是自行实现代理逻辑,还是集成现有的代理库。 可能的回答方向是建议用户使用现有的库,比如Spring Cloud Gateway,或者编写一个简单的代理处理器,使用WebClient来转发请求。对于CONNECT方法,可能需要单独处理,建立隧道连接。 需要验证这些思路是否正确。例如,在Spring MVC中是否可以拦截CONNECT请求,或者是否需要自定义过滤器或控制器。同时,需要考虑如何处理HTTPS的隧道,这可能需要更底层的处理,比如使用Netty的管道来转发字节流。 总结来说,用户需要在WebMVC应用中实现代理服务器逻辑,特别是处理CONNECT请求,这可能需要结合Servlet层面的处理和使用HTTP客户端库进行转发,或者使用更底层的网络库来管理连接。考虑到复杂性和维护成本,可能推荐使用现有代理库或工具,但用户可能希望自行实现,因此需要提供具体的实现步骤和示例代码。</think>--- 要在基于 Spring WebMVC 的应用中实现代理服务器的逻辑(包括处理 `CONNECT` 请求),需要解决两个核心问题: 1. **拦截并处理 `CONNECT` 方法**(用于 HTTPS 代理隧道)。 2. **将客户端请求转发到目标服务器**,并将响应返回给客户端。 但需要注意:**标准的 Spring WebMVC 是基于 Servlet 的,默认不支持直接处理 `CONNECT` 方法**,因此需要结合一些特殊技巧或底层库。以下是具体实现方案: --- ### 方案一:纯 Spring WebMVC 实现(有限支持) 适用于简单的 HTTP 代理,**不支持 HTTPS**(无法处理 `CONNECT` 隧道)。 #### 1. 添加依赖(如 Spring WebClient) ```xml <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-webflux</artifactId> </dependency> ``` #### 2. 编写代理 Controller ```java @RestController public class ProxyController { private final WebClient webClient = WebClient.create(); // 拦截所有路径的 HTTP 请求 @RequestMapping("/**") public ResponseEntity<byte[]> proxyRequest( @RequestHeader HttpHeaders headers, @RequestBody(required = false) byte[] body, HttpMethod method, HttpServletRequest request, ServerHttpResponse response) { // 构建目标 URL(示例中将所有请求转发到 example.com) String targetUrl = "http://example.com" + request.getRequestURI(); // 使用 WebClient 转发请求 return webClient.method(method) .uri(targetUrl) .headers(h -> h.addAll(headers)) .body(BodyInserters.fromByteArray(body != null ? body : new byte[0])) .exchangeToMono(clientResponse -> { // 将目标服务器的响应返回给客户端 return clientResponse.toEntity(byte[].class); }) .block(); } } ``` #### 局限性: - **无法处理 `CONNECT` 方法**(Servlet 容器会直接返回 501)。 - **仅支持 HTTP 转发**,无法代理 HTTPS 流量。 --- ### 方案二:嵌入 Netty 实现完整代理(推荐) 通过 **Netty** 在 Spring Boot 应用中嵌入一个真正的代理服务器,支持 `CONNECT` 方法和 HTTPS。 #### 1. 添加 Netty 依赖 ```xml <dependency> <groupId>io.projectreactor.netty</groupId> <artifactId>reactor-netty-http</artifactId> </dependency> ``` #### 2. 编写 Netty 代理服务器 ```java import reactor.netty.DisposableServer; import reactor.netty.http.server.HttpServer; @SpringBootApplication public class ProxyApplication { public static void main(String[] args) { SpringApplication.run(ProxyApplication.class, args); startNettyProxyServer(); } private static void startNettyProxyServer() { DisposableServer server = HttpServer.create() .port(8080) // 代理服务端口 .handle((request, response) -> { String targetHost = request.requestHeaders().get("Host"); return response.sendRedirect("http://" + targetHost); }) .bindNow(); server.onDispose().block(); } } ``` #### 3. 扩展支持 `CONNECT` 方法 ```java import io.netty.handler.codec.http.HttpMethod; import reactor.netty.http.server.HttpServerRoutes; private static void startNettyProxyServer() { HttpServer.create() .port(8080) .route(routes -> routes.add(HttpMethod.CONNECT, "/**", (request, response) -> { // 处理 CONNECT 请求,建立隧道 return response.sendWebsocket((in, out) -> { // 实现隧道逻辑(此处需自行编写) return out.send(in.receive().retain()); }); }) .get("/**", (request, response) -> { // 处理普通 HTTP 请求 return response.sendRedirect("http://example.com"); })) .bindNow(); } ``` --- ### 方案三:集成 Spring Cloud Gateway(生产级方案) 如果目标是构建生产级代理服务,**Spring Cloud Gateway** 是更成熟的选择,支持动态路由、过滤器和 `CONNECT` 方法。 #### 1. 添加依赖 ```xml <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-gateway</artifactId> </dependency> ``` #### 2. 配置路由 ```yaml spring: cloud: gateway: routes: - id: proxy_route uri: http://example.com predicates: - Path=/** ``` #### 3. 自定义过滤器处理 `CONNECT` ```java @Bean public GlobalFilter customConnectFilter() { return (exchange, chain) -> { if (exchange.getRequest().getMethod() == HttpMethod.CONNECT) { // 实现 CONNECT 逻辑 return handleConnectRequest(exchange); } return chain.filter(exchange); }; } private Mono<Void> handleConnectRequest(ServerWebExchange exchange) { // 建立隧道并转发数据 // (此处需要底层 Netty 操作,代码较复杂) } ``` --- ### 关键问题总结 | 方案 | 优点 | 缺点 | |---------------------|--------------------------|-------------------------------| | 纯 Spring WebMVC | 简单快速 | 不支持 `CONNECT` 和 HTTPS | | 嵌入 Netty | 完整代理功能 | 需要深入 Netty 知识 | | Spring Cloud Gateway| 生产级、功能丰富 | 配置复杂,学习曲线高 | --- ### 最终建议 - **调试用途**:直接使用现成代理工具(如 Charles)。 - **简单 HTTP 代理**:使用方案一。 - **完整代理服务**:选择方案二或三,优先使用 Spring Cloud Gateway。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值