SpringCloud Gateway 网关请求中body、query、header参数的获取和修改

这篇具有很好参考价值的文章主要介绍了SpringCloud Gateway 网关请求中body、query、header参数的获取和修改。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

前言

      最近在开发中要改造一个普通SpringBoot接口服务为SpringCloud Gateway网关服务,并且需要在网关做验签,由于我们这个服务需要对外几个第三方平台提供接口,每家请求的传参形式都不同,有将签名信息放请求头、也有将签名信息放query参数、还有直接放body中的,请求头和query参数的获取和修改都比较简单,body参数的获取和修改稍微复杂一点,本文会对SpringCloud Gateway常见几种传参数据的获取和修改做讲解。

一、请求头参数获取和修改

@Slf4j
@Component
public class HeaderGlobalFilter implements GlobalFilter , Ordered {
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        ServerHttpRequest request = exchange.getRequest();
        // 获取请求头中的sign,因为请求头可以有多个一样的key,可能会获取到多个值所以这里返回的是List
        List<String> signList = request.getHeaders().get("sign");
        log.info("请求头中的signList:{}", signList);
        // 获取请求头中的sign,取第一个
        String signFirst = request.getHeaders().getFirst("sign");
        log.info("请求头中的signFirst:{}", signFirst);
        // 请求转发处理,添加新的请求头
        // 因为是使用的原始的请求对象添加了新的请求头,所以下游服务能获取到请求原有请求头和新添加的请求头
        ServerHttpRequest mutableReq = request.mutate()
                .header("new-header", "自定义请求头")
                .build();
        ServerWebExchange mutableExchange = exchange.mutate().request(mutableReq).build();
        return chain.filter(mutableExchange);
    }
    @Override
    public int getOrder() {
        return -2000;
    }
}

二、Query参数获取和修改

@Slf4j
@Component
public class QueryParamGlobalFilter implements GlobalFilter , Ordered {
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {

        ServerHttpRequest request = exchange.getRequest();
        // 获取uri对象
        URI uri = request.getURI();
        log.info("请求的uri = {}",uri);
        String path = request.getURI().getPath();
        log.info("请求的path = {}",path);
        String query = request.getURI().getQuery();
        log.info("请求的query = {}",query);

        // 获取请求参数转换成键值对,值是一个数组
        MultiValueMap<String, String> queryParams = request.getQueryParams();
        log.info("获取请求参数转换成键值对 queryParams = {}",queryParams);
        // 将query参数转换成键值对,值是一个字符串,hutool包的HttpUtil
        Map<String, String> queryParams1 = HttpUtil.decodeParamMap(query, StandardCharsets.UTF_8);
        log.info("将query参数转换成键值对 queryParams = {}",queryParams1);

        // 处理uri,通过?分割,下标第0位是请求地址,第1位置是请求参数
        String url = uri.toString();
        String[] urlSplit = url.split("\\?");
        // 拼接新的url 这里自己处理即可,我这里直接替换请求参数
        String newUrl = urlSplit[0] + "?" + "name=kerwinNew&age=17";
        URI newUri = null;
        try {
            //将编码后的 %23 替换为 ,重新用这个字符串生成 URI
            newUri = new URI(newUrl.replace("%23", ""));
        } catch (URISyntaxException e) {
            throw new RuntimeException(e);
        }
        ServerHttpRequest mutableReq = request.mutate()
                .uri(newUri)
                .build();
        ServerWebExchange mutableExchange = exchange.mutate().request(mutableReq).build();
        return chain.filter(mutableExchange);
    }

    @Override
    public int getOrder() {
        return -1000;
    }
}

三、Body参数获取和修改

在Gateway中通常会有一个过滤器链,而 request body 只能读取一次,也就是说,如果在过滤器A中已经读取一次,在后面的过滤器B是无法读取成功的,会抛出如下的报错

java.lang.IllegalStateException: Only one connection receive subscriber allowed.
	at reactor.ipc.netty.channel.FluxReceive.startReceiver(FluxReceive.java:279)
	at reactor.ipc.netty.channel.FluxReceive.lambda$subscribe$2(FluxReceive.java:129)
	at io.netty.util.concurrent.AbstractEventExecutor.safeExecute$$$capture(AbstractEventExecutor.java:163)
	at io.netty.util.concurrent.AbstractEventExecutor.safeExecute(AbstractEventExecutor.java)
	at io.netty.util.concurrent.SingleThreadEventExecutor.runAllTasks(SingleThreadEventExecutor.java:404)
	at io.netty.channel.nio.NioEventLoop.run(NioEventLoop.java:446)
	at io.netty.util.concurrent.SingleThreadEventExecutor$5.run(SingleThreadEventExecutor.java:884)
	at java.lang.Thread.run(Thread.java:745)

要解决这个问题需要将获取body的方法重写,进行缓存读即可,我们可以添加一个自定义CacheBodyGlobalFilter全局过滤器来进行处理,要注意的是这个过滤器优先级一定要是最高的,不然别的过滤器读取一次body数据后这里就读取不到了。文章来源地址https://www.toymoban.com/news/detail-817285.html

3.1、全局body缓存过滤器

import lombok.extern.slf4j.Slf4j;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.core.io.buffer.DataBufferUtils;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.http.codec.HttpMessageReader;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpRequestDecorator;
import org.springframework.stereotype.Component;
import org.springframework.web.reactive.function.server.HandlerStrategies;
import org.springframework.web.reactive.function.server.ServerRequest;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;

import java.util.List;

/**
 * body 缓存过滤器
 * @author Kerwin
 * @date 2024/1/11
 */
@Slf4j
@Component
public class CacheBodyGlobalFilter implements GlobalFilter, Ordered {

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {

        /**
         * save request path and serviceId into gateway context
         */
        ServerHttpRequest request = exchange.getRequest();
        HttpHeaders headers = request.getHeaders();

        // 处理参数
        MediaType contentType = headers.getContentType();
        long contentLength = headers.getContentLength();
        if (contentLength > 0 && !MediaType.MULTIPART_FORM_DATA_VALUE.startsWith(contentType.toString())) {
            if (MediaType.APPLICATION_JSON.equals(contentType) || MediaType.APPLICATION_JSON_UTF8.equals(contentType)) {
                return readBody(exchange, chain);
            }
        }

        return chain.filter(exchange);
    }


    /**
     * default HttpMessageReader
     */
    private static final List<HttpMessageReader<?>> messageReaders = HandlerStrategies.withDefaults().messageReaders();
    /**
     * ReadJsonBody
     *
     * @param exchange
     * @param chain
     * @return
     */
    private Mono<Void> readBody(ServerWebExchange exchange, GatewayFilterChain chain) {
        /**
         * join the body
         */
        return DataBufferUtils.join(exchange.getRequest().getBody()).flatMap(dataBuffer -> {
            byte[] bytes = new byte[dataBuffer.readableByteCount()];
            dataBuffer.read(bytes);
            DataBufferUtils.release(dataBuffer);
            Flux<DataBuffer> cachedFlux = Flux.defer(() -> {
                DataBuffer buffer = exchange.getResponse().bufferFactory().wrap(bytes);
                DataBufferUtils.retain(buffer);
                return Mono.just(buffer);
            });
            /**
             * repackage ServerHttpRequest
             */
            ServerHttpRequest mutatedRequest = new ServerHttpRequestDecorator(exchange.getRequest()) {
                @Override
                public Flux<DataBuffer> getBody() {
                    return cachedFlux;
                }
            };
            /**
             * mutate exchage with new ServerHttpRequest
             */
            ServerWebExchange mutatedExchange = exchange.mutate().request(mutatedRequest).build();
            /**
             * read body string with default messageReaders
             */
            return ServerRequest.create(mutatedExchange, messageReaders).bodyToMono(String.class)
                    .doOnNext(objectValue -> {
                        log.debug("[GatewayContext]Read JsonBody:{}", objectValue);
                    }).then(chain.filter(mutatedExchange));
        });
    }
    @Override
    public int getOrder() {
        return HIGHEST_PRECEDENCE;
    }
}

3.2、获取和修改body工具类

public class GatewayBodyUtils {

    /**
     * 获取请求中的body
     * @param req
     * @return
     */
    public static String getBody(ServerHttpRequest req) {
        // 处理参数
        HttpHeaders headers = req.getHeaders();
        MediaType contentType = headers.getContentType();
        long contentLength = headers.getContentLength();
        if (contentLength > 0 && !MediaType.MULTIPART_FORM_DATA_VALUE.startsWith(contentType.toString())) {
            if (MediaType.APPLICATION_JSON.equals(contentType) || MediaType.APPLICATION_JSON_UTF8.equals(contentType)) {
                AtomicReference<String> requestBody = new AtomicReference<>("");
                RecorderServerHttpRequestDecorator requestDecorator = new RecorderServerHttpRequestDecorator(req);
                Flux<DataBuffer> body = requestDecorator.getBody();
                body.subscribe(buffer -> {
                    CharBuffer charBuffer = StandardCharsets.UTF_8.decode(buffer.asByteBuffer());
                    requestBody.set(charBuffer.toString());
                });
                return requestBody.get();
            }
        }
        return null;
    }

    /**
     * 修改设置body
     * @param body 不能为空
     * @param exchange
     * @param chain
     * @return
     */
    public static Mono<Void> setBody(String body,ServerWebExchange exchange, GatewayFilterChain chain){
        DataBuffer bodyDataBuffer = stringBuffer(body);
        Flux<DataBuffer> bodyFlux = Flux.just(bodyDataBuffer);
        MediaType contentType = exchange.getRequest().getHeaders().getContentType();
        ServerHttpRequest mutatedRequest = new ServerHttpRequestDecorator(
                exchange.getRequest()) {
            @Override
            public HttpHeaders getHeaders() {
                HttpHeaders httpHeaders = new HttpHeaders();
                int length = body.getBytes().length;
                httpHeaders.putAll(super.getHeaders());
                httpHeaders.remove(HttpHeaders.CONTENT_TYPE);
                httpHeaders.remove(HttpHeaders.CONTENT_LENGTH);
                httpHeaders.setContentLength(length);
                httpHeaders.set(HttpHeaders.CONTENT_TYPE, contentType.toString());
                // 设置CONTENT_TYPE
                return httpHeaders;
            }
            @Override
            public Flux<DataBuffer> getBody() {
                return bodyFlux;
            }
        };
        return chain.filter(exchange.mutate().request(mutatedRequest).build());
    }

    public static class RecorderServerHttpRequestDecorator extends ServerHttpRequestDecorator {
        private final List<DataBuffer> dataBuffers = new ArrayList<>();

        public RecorderServerHttpRequestDecorator(ServerHttpRequest delegate) {
            super(delegate);
            super.getBody().map(dataBuffer -> {
                dataBuffers.add(dataBuffer);
                return dataBuffer;
            }).subscribe();
        }

        @Override
        public Flux<DataBuffer> getBody() {
            return copy();
        }

        private Flux<DataBuffer> copy() {
            return Flux.fromIterable(dataBuffers)
                    .map(buf -> buf.factory().wrap(buf.asByteBuffer()));
        }
    }

    public static 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;
    }
}

3.3、获取和修改body测试

@Slf4j
@Component
public class BodyTestGlobalFilter implements GlobalFilter , Ordered {
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        ServerHttpRequest request = exchange.getRequest();
        // 获取body
        String body = GatewayBodyUtils.getBody(request);
        log.info("body = {}",body);
        
        // 修改body
        JSONObject bodyJson = JSON.parseObject(body);
        bodyJson.put("name","kerwin1");
        bodyJson.put("age","17");
        log.info("修改后body: {} ", bodyJson);

        // 将新的body数据放入请求中,获取新的Mono<Void>返回
        return GatewayBodyUtils.setBody(bodyJson.toJSONString(),exchange,chain);
    }

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

到了这里,关于SpringCloud Gateway 网关请求中body、query、header参数的获取和修改的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 使用 Postman 传递请求参数:Query、Path 和 Body 教程

    Postman  作为一个功能强大的工具,极大地简化了 API 测试和调试的过程,提供了发送请求和检查响应的直接方法。本文将着重介绍如何在 Postman 中高效地处理请求参数,以提高 API 测试和开发的便利性。 首先,我们需要明白什么是请求参数。简单来说,请求参数是传递给服务

    2024年04月23日
    浏览(40)
  • tp6框架中Http类 请求的header、body参数传参 及post、file格式

    引入Http类: 在需要使用的地方引入Http类: use thinkfacadeHttp; GET请求示例: $response = Http::get(\\\'https://example.com/api/resource\\\');  设置Header参数: $headers = [ \\\'Authorization\\\' = \\\'Bearer YourAccessToken\\\', \\\'Content-Type\\\' = \\\'application/json\\\', ]; $response = Http::header($headers)-get(\\\'https://example.com/api/resource\\\'); POST请

    2024年01月19日
    浏览(48)
  • 【Java】SpringCloud Gateway自定义过滤器中获取ServerHttpRequest的body中的数据为NULL的问题

    这个情况出现在,我需要进行验证码的校验,因此用户的请求首先需要被验证码过滤器校验,而验证码过滤器不需要设定为全局过滤器,因此我就单纯的把它设定为了一个局部过滤器,代码如下 然后我进行请求的时候,json参数如下 然后请求经过解析后会发现,字符串居然是

    2024年02月09日
    浏览(51)
  • springcloud gateway中打印请求参数,请求路径和返回数据

    在平时前后端联调过程中,需要查询日志看到前端请求的接口,上送的参数,返回数据这样有利于我们定位问题;话不多说直接上代码。 在gateway模块中,新建一个filter的包,然后创建改类,即可在控制台和日志文件里面打印出请求参数,只写了常用的 post 和 get 请求的方式;

    2024年02月15日
    浏览(37)
  • 【SpringCloud Gateway】SpringCloud各微服务之间用户登录信息共享的实现思路——gateway网关token校验以及向微服务发送请求携带token

            最近在学习SpringCloud项目时,想到了一些问题,各个微服务分别部署在不同的服务上,由naocs作为注册中心实现负载均衡,彼此之间通过Feign相互调用通信,信息同步并不像单体项目那样方便,传统单体项目的登录验证方式似乎在SpringCloud中不能满足项目的需求。那么

    2024年02月05日
    浏览(46)
  • Gateway网关参数进行验签POST 包含requestbody 请求体封装

    特别注意一点, 因为在网关层 拿出 request 流之后,必须重写getbody()方法把所有的参数放进去,否则后面转发的请求无法接收到任何数据, 坑,巨坑,因为版本问题网上很多都不能兼容, 我的springboot环境 依赖包 需求描述:前端发起请求的参数携带sign=xxxx,后台验证签名是够正确 sign签

    2024年02月06日
    浏览(37)
  • SpringCloud Gateway 整合RSA对请求参数解密、响应结果加密【SpringCloud系列10】

    本文章实现的是 网关中的 参数解密、响应数据体加密功能。 1 集成 commons-codec commons-codec 是Apache开源组织提供的用于摘要运算、编码解码的包。常见的编码解码工具Base64、MD5、Hex、SHA1、DES等。 本项目中集成RSA 非对称算法,RSAUtils 工具类 然后创建一个测试类,生成一组公钥

    2024年02月06日
    浏览(42)
  • Nginx 获取自定义请求header头和URL参数

    在 ngx_lua 中访问 Nginx 内置变量 ngx.var.http_HEADER 即可获得请求头HEADER的内容。 在 nginx配置中,通过$http_HEADER 即可获得请求头HEADER的内容。 案例: 在nginx的location配置中,在获取header配置时, 须要在header名称前面加上固定前缀“http_“,并将header名称中的“-”中划线变为下划线

    2024年02月04日
    浏览(36)
  • springcloud gateway 配置predicates 多个path;路由请求头新增参数

    一、 在一个微服务节点的predicates下配置多个path: - Path=/test/batis/test,/test/testJmeter,具体配置如下: 二、 在请求头中新增header参数,yml文件配置如下: 三、自定义filter中新增请求头参数:

    2024年02月16日
    浏览(43)
  • springcloud的gateway之GlobalFilter获取请求信息及requestBody

    《本文参考地址》 RequestGlobalFilter.java ResponseGlobalFilter.java RequestAndResponseGlobalFilter.java

    2024年02月16日
    浏览(31)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包