关于form-data和http请求body&网关处理

关于form-data和http请求body&网关处理

场景

  • 【在http过网关时,修改内容】

  • 在网关过滤器中,监听所有http请求,截获请求,并且修改body中内容。添加一组用户凭证,然后通过网关走向后台服务

  • 使用applicaon/json的请求时,能很好的解决

  • 使用form-data时,body采用下面的 'boundary—'限定边界,加入参数的形式进行传递

  • 后台我都是通过手动编译http协议body转string

  • 以前都不太清楚http具体内容,现在写出来记一下

  • 使用form-data传递文件时,并且添加参数,展示还未解决


form-data 只传参数—httpbody 对应字符串形式

http–请求协议内容:form-data————表单提交,只携带参数的情况

–form-data : key就是namae=“”的内容 ,value内容就是换行下面的那个参数

----------------------------402066259756119690380117
Content-Disposition: form-data; name="userId"

wuhong
----------------------------402066259756119690380117
Content-Disposition: form-data; name="123"

123123
----------------------------402066259756119690380117--

form-data 传文件 —httpbody 对应字符串形式

http–请求协议内容:form-data----携带文件的情况

  • 传文件的boundary边界中,添加了filename的标识
  • 【Content-Type: image/png】能看出文件的类型
    中间的内容是文件的内容
----------------------------360975841684466334428049
Content-Disposition: form-data; name="file"; filename="image-20210224172302222.png"
Content-Type: image/png

�PNG

IHDR  G     OEV>    IDATx��W��H����88:���U�ʪ������Y�,�\.�h���1�4^�xA3��v�8�;Þ���V�EjZG- �y� '"23";�*�
�x�P����u�'~p�	�A����M#��ԍF�M3�X�@���yד!lD�c't�3�V�A��I$� �۱�~�)#*��to�|!B�A���% �
��p`Z���]���U:�{8&
�	��:�\}
�� �(�F#�@����f�v%N7�aHL�
�)���,�K�)R�H�"E�)R���L+_z^"�CDh�,�DDd���PU��M��a���{v���+x�OXI���H�"E�CD��{!�hQ�;m����9�G�D
�#�~��H�h��粯
----------------------------360975841684466334428049--

温馨提示:如果要手动编辑http - form-data的body。如上面的格式,需要自行研究body的换行符号,我之前就是看着图上的格式进行\n换行,结果真实格式有一点点不一样,有点地方还要加斜杠。具体下面的filter代码有

吐槽&结果

  1. gateway处理非常恶心,加了webflux,响应式流的内容。这块方向我还是个彩笔
  2. 最后的代码方案其实能解决form-data传文件时,加入一组自定义的key-value信息。只是我的加入自定义信息的时候,使用了String打开和编辑。估计是这个地方,对文件的编码出现了影响。大致应该可以通过解析编码格式保存文件。

gateway fileter代码

package com.wuhong.filter;



import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;

import com.wuhong.entity.Accounts;
import com.wuhong.utils.JWTUtil;
import io.netty.buffer.ByteBufAllocator;

import lombok.extern.slf4j.Slf4j;



import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.cloud.gateway.filter.factory.rewrite.CachedBodyOutputMessage;
import org.springframework.cloud.gateway.support.BodyInserterContext;
import org.springframework.cloud.gateway.support.DefaultServerRequest;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.Ordered;

import org.springframework.core.io.buffer.*;

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.web.reactive.function.BodyInserter;
import org.springframework.web.reactive.function.BodyInserters;
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.io.*;

import java.util.List;

/**
 * @ClassName DiaryFilter
 * @Description TODO
 * @Author director吴
 * @Date 2022/1/16 21:14
 * @Version 1.0
 **/
@Slf4j
@Configuration
public class DiaryFilter implements GlobalFilter, Ordered {
    

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

        //从请求头获取Token
        String token = exchange.getRequest().getHeaders().get("token").get(0);

        if (token != null) {
            boolean status = JWTUtil.checkToken(token);
            //验证解析token
            Accounts info = JWTUtil.checkToken(token) ? JWTUtil.getTokenInfo(token) : null;
            ServerHttpRequest request = exchange.getRequest();
            
            HttpHeaders headers = request.getHeaders();
            MediaType contentType = headers.getContentType();
            long contentLength = headers.getContentLength();

            //判断请求类型
            if (contentLength > 0) {
                if (MediaType.APPLICATION_JSON.equals(contentType) || MediaType.APPLICATION_JSON_UTF8.equals(contentType)) {
                    return readJSONBody(exchange, chain, info);
                }
//                if (MediaType.APPLICATION_FORM_URLENCODED.equals(contentType)) {
//                    return readFormData(exchange, chain,gatewayContext);
//                }
                if (MediaType.MULTIPART_FORM_DATA.getSubtype().equals(contentType.getSubtype())) {
                    return readFormData(exchange, chain,info);
                }
            }

            return chain.filter(exchange);

        } else {

            String value = exchange.getRequest().getPath().value();

            if (value.indexOf("login") != -1) {
                return chain.filter(exchange);
            }

        }
        return null;
    }




    /*
    返回一个ServerHttpRequestDecorator对象。
    这个对象是用来构建一个request请求的适配。因为gateway使用webflux,流节点中(Mono、Flux)当request的数据被读取后,
    就不会在原来的节点存在。通俗来讲"就是拿出来用了,就没了。需要继续往下传递,必须再写一个数据放进去"
     */
    public ServerHttpRequestDecorator buildingServerHttpRequestDecorator(ServerWebExchange exchange,byte[] bytes){
        return new ServerHttpRequestDecorator(exchange.getRequest()) {

            //请求头配置
            @Override
            public HttpHeaders getHeaders() {
                HttpHeaders httpHeaders = new HttpHeaders();
                httpHeaders.putAll(super.getHeaders());
                httpHeaders.setContentLength(bytes.length);
                return httpHeaders;
            }

            //请求体,body数据配置
            @Override
            public Flux<DataBuffer> getBody() {
                Flux<DataBuffer> cachedFlux = Flux.defer(() -> {
                    DataBuffer buffer = exchange.getResponse().bufferFactory().wrap(bytes);
                    DataBufferUtils.retain(buffer);
                    return Mono.just(buffer);
                });

                return cachedFlux;
            }
        };
    }



    /**
     * 暂未解决的问题:使用form-data-上传文件时,通过修改http请求体,byte转string修改,再转回byte,除了txt文件意外,其他文件都会损坏
     */
    private Mono<Void> readFormData(ServerWebExchange exchange, GatewayFilterChain chain,Accounts tokenInfo) {

        return DataBufferUtils.join(exchange.getRequest().getBody()).flatMap(dataBuffer -> {
            StringBuilder builder = new StringBuilder();
            byte[] bytes = new byte[dataBuffer.readableByteCount()];
            log.info("dataBuffer数据长度==" + dataBuffer.readableByteCount());
            try {
                //读取-响应式流结构中,http-from-data的请求数据
                dataBuffer.read(bytes);
                String bodyInfo = new String(bytes);
                System.out.println(bodyInfo);

                //判断-from-data的请求内容,是否携带了文件。携带了文件的话 body请求体中,会有filename参数
                if (bodyInfo.contains("filename=")){
                    ServerHttpRequestDecorator decorator = buildingServerHttpRequestDecorator(exchange, bytes);
                    return chain.filter(exchange.mutate().request(decorator).build());
                }

                //插入
                String newBody = insertBodyForFromData(bodyInfo, tokenInfo);

                byte[] overByte = newBody.getBytes();

                ServerHttpRequestDecorator decorator = buildingServerHttpRequestDecorator(exchange, overByte);



                return chain.filter(exchange.mutate().request(decorator).build());

            } catch (Exception e) {
                e.printStackTrace();
            }
            return null;
        });


    }

    private Mono<Void> readJSONBody(ServerWebExchange exchange, GatewayFilterChain chain, Accounts tokenInfo) {

        // httpserverRequest 转 ServerRequest
        ServerRequest serverRequest = new DefaultServerRequest(exchange);

        Mono<String> modifiedBody = serverRequest.bodyToMono(String.class)
                .flatMap(body -> {//请求中的body数据
                    log.info("'[请求中的body数据]':\n" + body);

                    return Mono.just(insertBodyForJSON(body, tokenInfo));
                });
        BodyInserter bodyInserter = BodyInserters.fromPublisher(modifiedBody, String.class);
        HttpHeaders headers = new HttpHeaders();
        headers.putAll(exchange.getRequest().getHeaders());
        headers.remove(HttpHeaders.CONTENT_LENGTH);
        CachedBodyOutputMessage outputMessage = new CachedBodyOutputMessage(exchange, headers);

        return bodyInserter.insert(outputMessage, new BodyInserterContext())
                .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 {
                                httpHeaders.set(HttpHeaders.TRANSFER_ENCODING, "chunked");
                            }
                            return httpHeaders;
                        }

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


    private String insertBodyForJSON(String oldBody, Accounts insertUserData) {

        JSONObject jsonObject = JSON.parseObject(oldBody);
        jsonObject.put("userId", insertUserData.getId());
        log.info("[gatway添加用户Id信息] :[id=" + insertUserData.getId() + "]");
        jsonObject.put("userAccount", insertUserData.getAccount());
        log.info("[gatway添加用户账号信息] :[account=" + insertUserData.getAccount() + "]");
        return jsonObject.toJSONString();

    }

    private String insertBodyForFromData(String oldBody, Accounts insertUserData) {
        String boundary = oldBody.split("Content-Disposition: form-data;")[0];
        StringBuilder builder = new StringBuilder();
        log.info("请求格式为form-data");
        //http form-data报文格式,需要添加\r\n
        builder.append(boundary + "Content-Disposition: form-data; name=\"userId\"\r\n" +
                "\r\n" +
                "wuhong\r\n");
//                builder.append(boundary + "Content-Disposition: form-data; name=\"userId\"" +
//                        "\n" +
//                        "\n" +
//                        "wuhong\n");
        builder.append(oldBody);
        log.info("修改以后的数据");
        System.out.println(builder.toString());
        return builder.toString();

    }


    private byte[] insertBodyFromData(DataBuffer dataBuffer) {
        InputStream inputStream = dataBuffer.asInputStream();
        StringBuilder builder = new StringBuilder();
        byte[] bytes = new byte[dataBuffer.readableByteCount()];
        System.out.println("dataBuffer数据长度==" + dataBuffer.readableByteCount());
        try {
            dataBuffer.read(bytes);
            String bodyInfo = new String(bytes);
            System.out.println(bodyInfo);
            String formDataInfo = bodyInfo.split("Content-Disposition: form-data;")[0];
            builder.append(formDataInfo + "Content-Disposition: form-data; name=\"userId\"" +
                    "\n" +
                    "\n" +
                    "wuhong\n");
            builder.append(bodyInfo);
            System.out.println(builder.toString());
        } catch (Exception e) {
            e.printStackTrace();
        }
        String newBody = builder.toString();
        byte[] overByte = newBody.getBytes();

        return overByte;
    }


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


}

  • 4
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值