《Zuul网关》之GETPOSTPUT请求报文重组并支持multipart/form-data

摘要:
publicvoidprocessRequestBody{RequestContextcontext=RequestContext。getCurrentContext();HttpServletRequestrequest=context.getRequest();Stringmethod=request.getMethod().toUpperCase();如果{JSONObjectrequestBodyObj=newJSONObject() ; 请求主体对象。put;//注意,数据首先转换为json对象格式,然后转换为jsonstringrequestBodyObjput;StringrequestBody=JSONObject。toJSONString;byte[]reqBodyBytes=requestBody。getBytes();上下文setRequest;}}}4.在POSTPUT request contentType=“multipart/form data”等请求消息中,有文本参数和文件参数,消息结构复杂。在POST和PUT请求中,主要通过重构多部分/表单数据请求来重新组织参数,并将其转发到后端。

1、重组参数

假设需要重新组装的参数如下:

@Setter
@Getter
public class DecodeParameters implementsSerializable{

  private static final long serialVersionUID = -874947393093003083830L;

  //通用参数
  privateString channelNo;

  //业务参数
  privateString data;

}

2、GET请求

GET请求主要通过RequestContext参数requestQueryParams重置,设定解密之后的参数数值来实现参数重组,转发给后端微服务。

public voidprocessRequestBody(DecodeParameters parameters){
RequestContext context
=RequestContext.getCurrentContext(); HttpServletRequest request =context.getRequest(); String method =request.getMethod().toUpperCase(); if(HttpMethod.GET.matches(method)){   Map<String,List<String>> requestQueryParam =context.getRequestQueryParams();     if(Objects.isNull(requestQueryParams)){    requestQueryParams = new HashMap<>();    }else{ requestQueryParams .remove("channelNo");    } //放置业务参数 if(StringUtils.isNotBlank(parameters.getData)){   JSONObject data =JSONObject.parseObject(paramters.getData());   for(String key : data.keySet()){    List<String> list = new ArrayList<String>(){     {       add(data.getString(key));     }   };   requestQueryParams.put(key,list);   } } //放置通用参数 List<String> list = new ArrayList<>(Arrays.asList(parameters.getChannelNo()));
requestQueryParams.put (
"channelNo", list);   }
}

3、POSTPUT请求(contentType !=“multipart/form-data”)

POSTPUT请求需要通过重写RequestContext的HttpServletRequestWrapper InputStream流实现参数重组。

contentType =“application/json”这类这类场景的POST或者PUT请求,主要是通过重新设定InputStream流实现请求参数的重组,转发给后端。

public voidprocessRequestBody(DecodeParameters parameters){
  RequestContext context =RequestContext.getCurrentContext();
  HttpServletRequest request =context.getRequest();
  String method =request.getMethod().toUpperCase();
  if(HttpMethod.POST.matches(method) ||HttpMethod.PUT.matches(method)){
      JSONObject requestBodyObj= newJSONObject();
  requestBodyObj.put("channelNo", parameters.getChannelNo());
  //注意data先转化成json对象格式,后面同一转化成json字符串
  requestBodyObj.put("data", JSONObject.parseObject(parameters.getData()));
  String requestBody=JSONObject.toJSONString(requestBodyObj);
  byte[] reqBodyBytes =requestBody.getBytes();
  context.setRequest(newHttpServletRequestWrapper (context.getRequest()){
    @Override
    public ServletInputStream getInputStream() throwsIOException{
      return newServletInputStreamWrapper (reqBodyBytes);
    }

    @Override
    public intgetContextLength(){
      returnreqBodyBytes.length;
    }

    @Override
    public longgetContentLengthLong(){
      returnreqBodyBytes.length;
    });
    }
  }
}
 

4、POSTPUT请求(contentType =“multipart/form-data”) 

contentType = “multipart/form-data”这类请求报文中,既有文本参数(data),又有文件参数(file),报文结构较为复杂。

在POST和PUT请求中主要通过重构multipart/form-data request 实现参数的重组,转发给后端。

public voidprocessRequestBody(DecodeParameters param){
  RequestContext context =RequestContext.getCurrentContext();
  HttpServletRequest request =context.getRequest();
  String method =request.getMethod().toUpperCase();
  String contentType =request.getContentType();
  if(HttpMethod.POST.matches(method) ||HttpMethod.PUT.matches(method)){
      if(StringUtils.isNotBlank(contextType) && contentType.toLowerCase().startsWith("multipart/form-data")){
      //重构multipart/form-data request
      MultipartEntityBuilder multiEntityBuilder =MultipartEntityBuilder.create()
      .setMode(HttpMultipartMode.BROWSER_COMPATIBLE)
      .setCharset(StandardCharsets.UTF_8)
      .setContentType(ContentType.create(multipart/form-data))
      //务必获取原始报文boudary数值并重新设置boudary
      .setBoundary(contentType.substring(contentType.indexOf("boundary+")+9))

      //数据包文本参数重组
      if(Objects.nonNull(param)){
      //规范业务参数格式
        String paramStr;
        if(StringUtils.isBlank(param.getData()) || !isJsonValidate(param.getData())){
          paramStr=JSONObject.toJSONString(param);
        }else{
          JSONObject dataObj =JSONObject.parseObject(param.getData());
          JSONObject paramObj =JSONObject.parseObject(JSONObject.toJSONString(param));
          paramObj.put("data", dataObj);
          paramStr =JSONObject.toJSONString(paramObj);
  }

  //重组
  ContentType dataContentType = ContentType.create("application/json",StandardCharsets.UTF_8);
  multiEntityBuilder.addTextBody("data",paramStr, dataContentType);
  //数据包文件参数重组
  MultipartResolver resolver = newCommonsMultipartResolver(request.getSession().getServletContext());
  MultipartHttpServletRequest multipartHttpServletRequest =resolver.resolveMultipart(request);
  boolean isSuccess = buildBinaryBody(multiEntityBuilder,multipartHttpServletRequest);
if(!isSuccess){
setRequestByPart(multipartHttpServletRequest, multiEntityBuilder);
}   ByteArrayOutSteam byteArrayOutputStream();   multiEntityBuilder.build().writeTo(byteArrayOutputStream);   
byte[] reqBodyBytes =byteArrayOutputSteam.toByteArray();   context.setRequest(newHttpServletRequestWrapper (context.getRequest()){     @Override     public ServletInputStream getInputStream() throwsIOException{       return newServletInputStreamWrapper (reqBodyBytes);     }     @Override     public intgetContextLength(){       returnreqBodyBytes.length;     }     @Override     public longgetContentLengthLong(){       returnreqBodyBytes.length;     });   } } private booleanisJsonValidate(String str){ try{   JSON.parse(str);   return true; }catch(JSONException e){   return false;   } } private boolean buildBinaryBody(MultipartEntityBuilder multiEntityBuilder, MultipartHttpServletRequest multipartHttpServletRequest)thows IOException{   MultiValueMap<String,MultipartFile> multiFiles =multipartHttpServletRequest.getMultiFileMap();   for(String key : multiFiles.keySet()){   List<MultipartFile> multipartFile =multiFiles.get(key);   for(MultipartFile multipartFile : multipartFiles){     String fileName =multipartFile.getOriginalFilename();     multiEntityBuilder.addBinaryBody(key, multipartFile.getInputStream(), ContentType.DEFAULT_BINARY,fileName);     }   }
return multiFiles.isEmpty() ? false: true; }
private voidsetRequestByPart(MultipartEntityBuilder multiEntityBuilder, MultipartHttpServletRequest multipartHttpServletRequest)
thows IOException, ServletException{
Collection<Part> parts = multipartHttpServletRequest.getParts();
if(!CollectionUtils.isEmpty(parts)){
for(Part part: parts){
String name = part.getName();
if(StringUtils.isNotBlank(name) && name.startsWith("file参数名")){
ContentType contentType = ContentType.create(part.getContentType(), StandardCharsets.UTF_8);
multiEntityBuilder.addBinaryBody(name, part.getInputStream(), contentType,part.getSubmittedFileName());
}
}
}
}

后续

本文主要为了讲述如何实现Zuul对请求报文的参数重置修改,由于不方便拷贝源代码,采用纯文本手敲代码,难免会出现格式和魔数不规范,请读者忽视。

免责声明:文章转载自《《Zuul网关》之GETPOSTPUT请求报文重组并支持multipart/form-data》仅用于学习参考。如对内容有疑问,请及时联系本站处理。

上篇Win7中,取消共享文件夹后有个小锁javaScript 全局变量注意下篇

宿迁高防,2C2G15M,22元/月;香港BGP,2C5G5M,25元/月 雨云优惠码:MjYwNzM=

相关文章

Android9.0 Settings 修改踩坑记录

问题现象 上图展示的很清楚,当系统语言为中文时,PreferenceScreen 中的折叠项 summary 描述重复显示的 bug,系统语言为英文时正常。 修改历程 先搜索 当前显示了 字符串,还真找到了 prebuiltssdkcurrentsupportv7preference esvalues-zh-rCNvalues-zh-rCN.xml &l...

关于WinForm/Web如何使用缓存Cach

Cache 的绝对到期与滑动到期 绝对到期:设置绝对过期时间 到了指定时间以后会失效。(类似Cookie机制) 相对到期也称滑动到期:设置相对过期时间 指定时间内无访问会失效。(类似Session机制) HttpRuntime.Cache与HttpContext.Current.Cache 为同一个对象HttpRuntime.Cache.Add 存在相同...

审计基础-PHP命令执行

1. 命令执行 1.1 程序执行函数 程序执行函数 这些函数和 执行运算符 是紧密关联的。 因此,当运行在 安全模式 时,你必须考虑 safe_mode_exec_dir指示 exec PHP 457 exec — 执行一个外部程序 exec ( string $command ) : string 返回命令执行结果的最后一行内容,实际不echo出来的话回...

C# WebApi 接口传参详解

本篇打算通过get、post、put、delete四种请求方式分别谈谈基础类型(包括int/string/datetime等)、实体、数组等类型的参数如何传递。 一、get请求 对于取数据,我们使用最多的应该就是get请求了吧。下面通过几个示例看看我们的get请求参数传递。 1、基础类型参数 ? 1 2 3 4 5 [HttpGet] publ...

实现客户端程序自动更新使用FTP

 最近做的一个项目中需要用到客户端自动更新功能,最初的想法是利用ClickOnce技术来完成,但在实践中发现根本行不能,原因如下: 1)项目应用到了DevExpress控件包,用ClickOnce发布的自动更新程序,客户在安装时报在GAC中找不到控件dll的错。 2)ClickOnce安装无法实现根据用户安装时录入的参数(比如数据库服务器名、数据库用户名...

查询指定距离内的快递柜或者店铺

背景:我们在淘宝购物时,选择了某个地址,有时会提示可以选择放到附近的快递柜子,这种是如何实现的呢?用redis geo api可以简单的实现该功能 思路:1. 我们先将所有的快递柜子存到redis中,这些快递柜信息要包含经纬度             /** * * @param longitude 经度 * @param...