spring cloud gateway在GatewayFilter中获取并修改请求参数

参考:https://blog.csdn.net/tianyaleixiaowu/article/details/83375246

网上查询很多资料,但是都存在一些问题。后来仔细查看官方spring cloud gateway官方文档,发现它有提供相应拦截器

官网api:https://cloud.spring.io/spring-cloud-static/spring-cloud-gateway/2.1.0.RC3/multi/multi_spring-cloud-gateway.html

spring cloud gateway在GatewayFilter中获取并修改请求参数_第1张图片

spring cloud gateway在GatewayFilter中获取并修改请求参数_第2张图片

果断查看源码找到相关的拦截器,分别如下,参考源码就能解决问题

1、涉及到的源码拦截器

AddRequestParameterGatewayFilterFactory.java
/*
 * Copyright 2013-2017 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 */

package org.springframework.cloud.gateway.filter.factory;

import java.net.URI;

import org.springframework.cloud.gateway.filter.GatewayFilter;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.util.StringUtils;
import org.springframework.web.util.UriComponentsBuilder;

/**
 * @author Spencer Gibb
 */
public class AddRequestParameterGatewayFilterFactory extends AbstractNameValueGatewayFilterFactory {

	@Override
	public GatewayFilter apply(NameValueConfig config) {
		return (exchange, chain) -> {
			URI uri = exchange.getRequest().getURI();
			StringBuilder query = new StringBuilder();
			String originalQuery = uri.getRawQuery();

			if (StringUtils.hasText(originalQuery)) {
				query.append(originalQuery);
				if (originalQuery.charAt(originalQuery.length() - 1) != '&') {
					query.append('&');
				}
			}

			//TODO urlencode?
			query.append(config.getName());
			query.append('=');
			query.append(config.getValue());

			try {
				URI newUri = UriComponentsBuilder.fromUri(uri)
						.replaceQuery(query.toString())
						.build(true)
						.toUri();

				ServerHttpRequest request = exchange.getRequest().mutate().uri(newUri).build();

				return chain.filter(exchange.mutate().request(request).build());
			} catch (RuntimeException ex) {
				throw new IllegalStateException("Invalid URI query: \"" + query.toString() + "\"");
			}
		};
	}

}
ModifyRequestBodyGatewayFilterFactory.java
/*
 * Copyright 2013-2018 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 */

package org.springframework.cloud.gateway.filter.factory.rewrite;

import java.util.Map;

import org.springframework.cloud.gateway.support.BodyInserterContext;
import org.springframework.cloud.gateway.support.CachedBodyOutputMessage;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;

import org.springframework.cloud.gateway.filter.GatewayFilter;
import org.springframework.cloud.gateway.filter.factory.AbstractGatewayFilterFactory;
import org.springframework.cloud.gateway.support.DefaultServerRequest;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.http.HttpHeaders;
import org.springframework.http.codec.ServerCodecConfigurer;
import org.springframework.http.server.reactive.ServerHttpRequestDecorator;
import org.springframework.web.reactive.function.BodyInserter;
import org.springframework.web.reactive.function.BodyInserters;
import org.springframework.web.reactive.function.server.ServerRequest;

/**
 * This filter is BETA and may be subject to change in a future release.
 */
public class ModifyRequestBodyGatewayFilterFactory
		extends AbstractGatewayFilterFactory {

	public ModifyRequestBodyGatewayFilterFactory() {
		super(Config.class);
	}

	@Deprecated
	public ModifyRequestBodyGatewayFilterFactory(ServerCodecConfigurer codecConfigurer) {
		this();
	}

	@Override
	@SuppressWarnings("unchecked")
	public GatewayFilter apply(Config config) {
		return (exchange, chain) -> {
			Class inClass = config.getInClass();

			ServerRequest serverRequest = new DefaultServerRequest(exchange);
			//TODO: flux or mono
			Mono modifiedBody = serverRequest.bodyToMono(inClass)
					// .log("modify_request_mono", Level.INFO)
					.flatMap(o -> config.rewriteFunction.apply(exchange, o));

			BodyInserter bodyInserter = BodyInserters.fromPublisher(modifiedBody, config.getOutClass());
			HttpHeaders headers = new HttpHeaders();
			headers.putAll(exchange.getRequest().getHeaders());

			// the new content type will be computed by bodyInserter
			// and then set in the request decorator
			headers.remove(HttpHeaders.CONTENT_LENGTH);

			// if the body is changing content types, set it here, to the bodyInserter will know about it
			if (config.getContentType() != null) {
				headers.set(HttpHeaders.CONTENT_TYPE, config.getContentType());
			}
			CachedBodyOutputMessage outputMessage = new CachedBodyOutputMessage(exchange, headers);
			return bodyInserter.insert(outputMessage,  new BodyInserterContext())
					// .log("modify_request", Level.INFO)
					.then(Mono.defer(() -> {
						ServerHttpRequestDecorator decorator = new ServerHttpRequestDecorator(
								exchange.getRequest()) {
							@Override
							public HttpHeaders getHeaders() {
								long contentLength = headers.getContentLength();
								HttpHeaders httpHeaders = new HttpHeaders();
								httpHeaders.putAll(super.getHeaders());
								if (contentLength > 0) {
									httpHeaders.setContentLength(contentLength);
								} else {
									// TODO: this causes a 'HTTP/1.1 411 Length Required' on httpbin.org
									httpHeaders.set(HttpHeaders.TRANSFER_ENCODING, "chunked");
								}
								return httpHeaders;
							}

							@Override
							public Flux getBody() {
								return outputMessage.getBody();
							}
						};
						return chain.filter(exchange.mutate().request(decorator).build());
					}));

		};
	}

	public static class Config {
		private Class inClass;
		private Class outClass;

		private String contentType;

		@Deprecated
		private Map inHints;
		@Deprecated
		private Map outHints;

		private RewriteFunction rewriteFunction;

		public Class getInClass() {
			return inClass;
		}

		public Config setInClass(Class inClass) {
			this.inClass = inClass;
			return this;
		}

		public Class getOutClass() {
			return outClass;
		}

		public Config setOutClass(Class outClass) {
			this.outClass = outClass;
			return this;
		}

		@Deprecated
		public Map getInHints() {
			return inHints;
		}

		@Deprecated
		public Config setInHints(Map inHints) {
			this.inHints = inHints;
			return this;
		}

		@Deprecated
		public Map getOutHints() {
			return outHints;
		}

		@Deprecated
		public Config setOutHints(Map outHints) {
			this.outHints = outHints;
			return this;
		}

		public RewriteFunction getRewriteFunction() {
			return rewriteFunction;
		}

		public  Config setRewriteFunction(Class inClass, Class outClass,
												RewriteFunction rewriteFunction) {
			setInClass(inClass);
			setOutClass(outClass);
			setRewriteFunction(rewriteFunction);
			return this;
		}

		public Config setRewriteFunction(RewriteFunction rewriteFunction) {
			this.rewriteFunction = rewriteFunction;
			return this;
		}

		public String getContentType() {
			return contentType;
		}

		public Config setContentType(String contentType) {
			this.contentType = contentType;
			return this;
		}
	}
}

2、根据上面源码修改我自己的拦截器

因为webflux中,get请求的参数修改是比较简单,并且多次订阅后再request到下层微服务也正常

post请求的body修改比较麻烦,而且post的body只能被订阅一次,所以request要重新封装再次发送出去

注意:post请求时,如果修改过参数,content-length的值也要重新设置,否则下层微服务获取参数时,会缺失一部分

package com.dream.web.config.filter;

import com.dream.common.constants.ServiceConstants;
import com.dream.common.model.vo.permission.SessionAuthenticationVO;
import com.dream.common.utils.GsonUtils;
import com.dream.user.api.feign.IUserServiceFeign;
import com.google.gson.Gson;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
import io.netty.buffer.ByteBufAllocator;
import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.gateway.filter.GatewayFilter;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.factory.AbstractGatewayFilterFactory;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.core.io.buffer.DataBufferUtils;
import org.springframework.core.io.buffer.NettyDataBufferFactory;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpRequestDecorator;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import org.springframework.web.util.UriComponentsBuilder;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;

import java.net.URI;
import java.nio.CharBuffer;
import java.nio.charset.StandardCharsets;
import java.util.concurrent.atomic.AtomicReference;

/**
 * 修改请求参数GatewayFilterFactory拦截器(类名格式必须为filterName + GatewayFilterFactory)
 * 注:在GlobalFilter里面处理参数不成功,必须在GatewayFilter处理才行。很神奇。。。。
 */
@Component
public class InjectParameterGatewayFilterFactory extends AbstractGatewayFilterFactory {

    private static final Logger logger = LoggerFactory.getLogger(InjectParameterGatewayFilterFactory.class);

    @Autowired
    private IUserServiceFeign iUserServiceFeign;

    public InjectParameterGatewayFilterFactory() {
        super(Config.class);
    }

    @Override
    public GatewayFilter apply(Config config) {
        // grab configuration from Config object
        return (exchange, chain) -> {
            logger.info("自定义注入参数网关拦截器生效");
            //获取会话管理全局过滤(SessionGlobalFilter)中设置的用户属性
            String info = exchange.getAttribute(ServiceConstants.SHIRO_SESSION_PRINCIPALS);
            if(StringUtils.isBlank(info)){
                // 无用户属性,不用处理参数注入
                return chain.filter(exchange);
            }
            SessionAuthenticationVO authenticationVO = (SessionAuthenticationVO)GsonUtils.fromJson(info,SessionAuthenticationVO.class);
            return handleParameter(authenticationVO,exchange,chain);
        };
    }

    public static class Config {
        //Put the configuration properties for your filter here
    }

    /**
     * 处理请求(添加全局类参数)
     * @param authenticationVO
     * @param exchange
     * @param chain
     * @return
     */
    private Mono handleParameter(SessionAuthenticationVO authenticationVO, ServerWebExchange exchange, GatewayFilterChain chain) {
        ServerHttpRequest serverHttpRequest = exchange.getRequest();
        HttpMethod method = serverHttpRequest.getMethod();
        String contentType = serverHttpRequest.getHeaders().getFirst(HttpHeaders.CONTENT_TYPE);
        //post请求时,如果是文件上传之类的请求,不修改请求消息体
        if (method == HttpMethod.POST && (MediaType.APPLICATION_FORM_URLENCODED_VALUE.equalsIgnoreCase(contentType)
                || MediaType.APPLICATION_JSON_VALUE.equalsIgnoreCase(contentType))) {
            // TODO: 2018/12/21 参考api文档中GatewapFilter中“修改请求消息体拦截器”:ModifyRequestBodyGatewayFilterFactory.java
            //从请求里获取Post请求体
            String bodyStr = resolveBodyFromRequest(serverHttpRequest);

            // 这种处理方式,必须保证post请求时,原始post表单必须有数据过来,不然会报错
            if (StringUtils.isBlank(bodyStr)) {
                logger.error("请求异常:{} POST请求必须传递参数", serverHttpRequest.getURI().getRawPath());
                ServerHttpResponse response = exchange.getResponse();
                response.setStatusCode(HttpStatus.BAD_REQUEST);
                return response.setComplete();
            }

            //application/x-www-form-urlencoded和application/json才添加参数
            //其他上传文件之类的,不做参数处理,因为文件流添加参数,文件原格式就会出问题了
            if (MediaType.APPLICATION_FORM_URLENCODED_VALUE.equalsIgnoreCase(contentType)) {
                // 普通键值对,增加参数
                bodyStr = String.format(bodyStr+"&%s=%s&%s=%s",ServiceConstants.COMMON_PARAMETER_ENTERPRISEID,authenticationVO.getEnterpriseId()
                        ,ServiceConstants.COMMON_PARAMETER_USERID,authenticationVO.getUserId());
            }
            if (MediaType.APPLICATION_JSON_VALUE.equalsIgnoreCase(contentType)) {
                // json,增加参数
                JsonParser parse = new JsonParser();
                JsonObject json = null;
                if (StringUtils.isBlank(bodyStr)) {
                    json = new JsonObject();
                } else {
                    json = parse.parse(bodyStr).getAsJsonObject();//创建jsonObject对象
                }
                json.addProperty(ServiceConstants.COMMON_PARAMETER_ENTERPRISEID,authenticationVO.getEnterpriseId());
                json.addProperty(ServiceConstants.COMMON_PARAMETER_USERID,authenticationVO.getUserId());
                // 转换回字符串
                Gson gson = new Gson();
                bodyStr = gson.toJson(json);
            }
            //记录日志
            logger.info("全局参数处理: {} url:{} 参数:{}", method.toString(), serverHttpRequest.getURI().getRawPath(), bodyStr);

            //下面的将请求体再次封装写回到request里,传到下一级,否则,由于请求体已被消费,后续的服务将取不到值
            URI uri = serverHttpRequest.getURI();
            URI newUri = UriComponentsBuilder.fromUri(uri).build(true).toUri();
            ServerHttpRequest request = exchange.getRequest().mutate().uri(newUri).build();
            DataBuffer bodyDataBuffer = stringBuffer(bodyStr);
            Flux bodyFlux = Flux.just(bodyDataBuffer);

            // 定义新的消息头
            HttpHeaders headers = new HttpHeaders();
            headers.putAll(exchange.getRequest().getHeaders());

            // 添加消息头
            headers.set(ServiceConstants.SHIRO_SESSION_PRINCIPALS,GsonUtils.toJson(authenticationVO));

            // 由于修改了传递参数,需要重新设置CONTENT_LENGTH,长度是字节长度,不是字符串长度
            int length = bodyStr.getBytes().length;
            headers.remove(HttpHeaders.CONTENT_LENGTH);
            headers.setContentLength(length);

            // 设置CONTENT_TYPE
            if (StringUtils.isNotBlank(contentType)) {
                headers.set(HttpHeaders.CONTENT_TYPE, contentType);
            }

            // 由于post的body只能订阅一次,由于上面代码中已经订阅过一次body。所以要再次封装请求到request才行,不然会报错请求已经订阅过
            request = new ServerHttpRequestDecorator(request) {
                @Override
                public HttpHeaders getHeaders() {
                    long contentLength = headers.getContentLength();
                    HttpHeaders httpHeaders = new HttpHeaders();
                    httpHeaders.putAll(super.getHeaders());
                    if (contentLength > 0) {
                        httpHeaders.setContentLength(contentLength);
                    } else {
                        // TODO: this causes a 'HTTP/1.1 411 Length Required' on httpbin.org
                        httpHeaders.set(HttpHeaders.TRANSFER_ENCODING, "chunked");
                    }
                    return httpHeaders;
                }

                @Override
                public Flux getBody() {
                    return bodyFlux;
                }
            };

            //封装request,传给下一级
            request.mutate().header(HttpHeaders.CONTENT_LENGTH, Integer.toString(bodyStr.length()));
            return chain.filter(exchange.mutate().request(request).build());
        } else if (method == HttpMethod.GET) {
            // TODO: 2018/12/21 参考api文档中GatewapFilter中“添加请求参数拦截器”:AddRequestParameterGatewayFilterFactory.java

            //记录日志
            //logger.info("全局参数处理: {} url:{} 参数:{}",method.toString(),serverHttpRequest.getURI().getRawPath(),newRequestQueryParams.toString());
            // 获取原参数
            URI uri = serverHttpRequest.getURI();
            StringBuilder query = new StringBuilder();
            String originalQuery = uri.getRawQuery();
            if (org.springframework.util.StringUtils.hasText(originalQuery)) {
                query.append(originalQuery);
                if (originalQuery.charAt(originalQuery.length() - 1) != '&') {
                    query.append('&');
                }
            }
            // 添加查询参数
            query.append(ServiceConstants.COMMON_PARAMETER_ENTERPRISEID+"="+authenticationVO.getEnterpriseId()
                    +"&"+ServiceConstants.COMMON_PARAMETER_USERID+"="+authenticationVO.getUserId());

            // 替换查询参数
            URI newUri = UriComponentsBuilder.fromUri(uri)
                    .replaceQuery(query.toString())
                    .build(true)
                    .toUri();

            ServerHttpRequest request = exchange.getRequest().mutate().uri(newUri).build();
            return chain.filter(exchange.mutate().request(request).build());
        }

        return chain.filter(exchange);
    }


    /**
     * 从Flux中获取字符串的方法
     * @return 请求体
     */
    private String resolveBodyFromRequest(ServerHttpRequest serverHttpRequest) {
        //获取请求体
        Flux body = serverHttpRequest.getBody();

        AtomicReference bodyRef = new AtomicReference<>();
        body.subscribe(buffer -> {
            CharBuffer charBuffer = StandardCharsets.UTF_8.decode(buffer.asByteBuffer());
            DataBufferUtils.release(buffer);
            bodyRef.set(charBuffer.toString());
        });
        //获取request body
        return bodyRef.get();
    }

    /**
     * 字符串转DataBuffer
     * @param value
     * @return
     */
    private DataBuffer stringBuffer(String value) {
        byte[] bytes = value.getBytes(StandardCharsets.UTF_8);
        NettyDataBufferFactory nettyDataBufferFactory = new NettyDataBufferFactory(ByteBufAllocator.DEFAULT);
        DataBuffer buffer = nettyDataBufferFactory.allocateBuffer(bytes.length);
        buffer.write(bytes);
        return buffer;
    }
}

代码中有一些与我设计相关的逻辑,但是主心骨是获取post、get请求的参数,在原参数基础上,注入2个参数到post,get中,然后再转发到下层

你可能感兴趣的:(spring,cloud)