《Zuul网关》之GET\POST\PUT请求报文重组并支持multipart/form-data
1、重组参数
假设需要重新组装的参数如下:
@Setter @Getter public class DecodeParameters implements Serializable{ private static final long serialVersionUID = -874947393093003083830L; // 通用参数 private String channelNo; //业务参数 private String data; }
2、GET请求
GET请求主要通过RequestContext参数requestQueryParams重置,设定解密之后的参数数值来实现参数重组,转发给后端微服务。
public void processRequestBody(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、POST\PUT请求(contentType !=“multipart/form-data”)
POST\PUT请求需要通过重写RequestContext的HttpServletRequestWrapper InputStream流实现参数重组。
contentType =“application/json”这类这类场景的POST或者PUT请求,主要是通过重新设定InputStream流实现请求参数的重组,转发给后端。
public void processRequestBody(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= new JSONObject(); 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(new HttpServletRequestWrapper (context.getRequest()){ @Override public ServletInputStream getInputStream() throws IOException{ return new ServletInputStreamWrapper (reqBodyBytes); } @Override public int getContextLength(){ return reqBodyBytes.length; } @Override public long getContentLengthLong(){ return reqBodyBytes.length; }); } } }
4、POST\PUT请求(contentType =“multipart/form-data”)
contentType = “multipart/form-data”这类请求报文中,既有文本参数(data),又有文件参数(file),报文结构较为复杂。
在POST和PUT请求中主要通过重构multipart/form-data request 实现参数的重组,转发给后端。
public void processRequestBody(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 = new CommonsMultipartResolver(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(new HttpServletRequestWrapper (context.getRequest()){ @Override public ServletInputStream getInputStream() throws IOException{ return new ServletInputStreamWrapper (reqBodyBytes); } @Override public int getContextLength(){ return reqBodyBytes.length; } @Override public long getContentLengthLong(){ return reqBodyBytes.length; }); } } private boolean isJsonValidate(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 void setRequestByPart(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对请求报文的参数重置修改,由于不方便拷贝源代码,采用纯文本手敲代码,难免会出现格式和魔数不规范,请读者忽视。