commit
6fc6d9cea3
@ -1,34 +1,34 @@ |
||||
* [首页](/?t=1565767132305) |
||||
* [首页](/?t=1565916799573) |
||||
* 开发文档 |
||||
* [快速体验](files/10010_快速体验.md?t=1565767132307) |
||||
* [项目接入到SOP](files/10011_项目接入到SOP.md?t=1565767132325) |
||||
* [新增接口](files/10020_新增接口.md?t=1565767132325) |
||||
* [业务参数校验](files/10030_业务参数校验.md?t=1565767132325) |
||||
* [错误处理](files/10040_错误处理.md?t=1565767132325) |
||||
* [编写文档](files/10041_编写文档.md?t=1565767132326) |
||||
* [接口交互详解](files/10050_接口交互详解.md?t=1565767132326) |
||||
* [easyopen支持](files/10070_easyopen支持.md?t=1565767132326) |
||||
* [使用签名校验工具](files/10080_使用签名校验工具.md?t=1565767132326) |
||||
* [ISV管理](files/10085_ISV管理.md?t=1565767132326) |
||||
* [自定义路由](files/10086_自定义路由.md?t=1565767132326) |
||||
* [自定义返回结果](files/10087_自定义返回结果.md?t=1565767132326) |
||||
* [自定义过滤器](files/10088_自定义过滤器.md?t=1565767132326) |
||||
* [路由授权](files/10090_路由授权.md?t=1565767132326) |
||||
* [接口限流](files/10092_接口限流.md?t=1565767132326) |
||||
* [监控日志](files/10093_监控日志.md?t=1565767132327) |
||||
* [SDK开发](files/10095_SDK开发.md?t=1565767132327) |
||||
* [使用SpringCloudGateway](files/10096_使用SpringCloudGateway.md?t=1565767132327) |
||||
* [应用授权](files/10097_应用授权.md?t=1565767132327) |
||||
* [传统web开发](files/10100_传统web开发.md?t=1565767132327) |
||||
* [文件上传](files/10104_文件上传.md?t=1565767132327) |
||||
* [nacos注册中心](files/10106_nacos注册中心.md?t=1565767132327) |
||||
* [扩展其它注册中心](files/10107_扩展其它注册中心.md?t=1565767132327) |
||||
* [配置Sleuth链路追踪](files/10109_配置Sleuth链路追踪.md?t=1565767132327) |
||||
* [预发布灰度发布](files/10110_预发布灰度发布.md?t=1565767132327) |
||||
* [动态修改请求参数](files/10111_动态修改请求参数.md?t=1565767132327) |
||||
* [快速体验](files/10010_快速体验.md?t=1565916799577) |
||||
* [项目接入到SOP](files/10011_项目接入到SOP.md?t=1565916799596) |
||||
* [新增接口](files/10020_新增接口.md?t=1565916799596) |
||||
* [业务参数校验](files/10030_业务参数校验.md?t=1565916799596) |
||||
* [错误处理](files/10040_错误处理.md?t=1565916799596) |
||||
* [编写文档](files/10041_编写文档.md?t=1565916799596) |
||||
* [接口交互详解](files/10050_接口交互详解.md?t=1565916799597) |
||||
* [easyopen支持](files/10070_easyopen支持.md?t=1565916799597) |
||||
* [使用签名校验工具](files/10080_使用签名校验工具.md?t=1565916799597) |
||||
* [ISV管理](files/10085_ISV管理.md?t=1565916799597) |
||||
* [自定义路由](files/10086_自定义路由.md?t=1565916799597) |
||||
* [自定义返回结果](files/10087_自定义返回结果.md?t=1565916799597) |
||||
* [自定义过滤器](files/10088_自定义过滤器.md?t=1565916799597) |
||||
* [路由授权](files/10090_路由授权.md?t=1565916799598) |
||||
* [接口限流](files/10092_接口限流.md?t=1565916799598) |
||||
* [监控日志](files/10093_监控日志.md?t=1565916799598) |
||||
* [SDK开发](files/10095_SDK开发.md?t=1565916799598) |
||||
* [使用SpringCloudGateway](files/10096_使用SpringCloudGateway.md?t=1565916799598) |
||||
* [应用授权](files/10097_应用授权.md?t=1565916799598) |
||||
* [传统web开发](files/10100_传统web开发.md?t=1565916799598) |
||||
* [文件上传](files/10104_文件上传.md?t=1565916799598) |
||||
* [nacos注册中心](files/10106_nacos注册中心.md?t=1565916799599) |
||||
* [扩展其它注册中心](files/10107_扩展其它注册中心.md?t=1565916799599) |
||||
* [配置Sleuth链路追踪](files/10109_配置Sleuth链路追踪.md?t=1565916799599) |
||||
* [预发布灰度发布](files/10110_预发布灰度发布.md?t=1565916799599) |
||||
* [动态修改请求参数](files/10111_动态修改请求参数.md?t=1565916799599) |
||||
* 原理分析 |
||||
* [原理分析之@ApiMapping](files/90010_原理分析之@ApiMapping.md?t=1565767132328) |
||||
* [原理分析之路由存储](files/90011_原理分析之路由存储.md?t=1565767132328) |
||||
* [原理分析之如何路由](files/90012_原理分析之如何路由.md?t=1565767132328) |
||||
* [原理分析之文档归纳](files/90013_原理分析之文档归纳.md?t=1565767132328) |
||||
* [常见问题](files/90100_常见问题.md?t=1565767132328) |
||||
* [原理分析之@ApiMapping](files/90010_原理分析之@ApiMapping.md?t=1565916799599) |
||||
* [原理分析之路由存储](files/90011_原理分析之路由存储.md?t=1565916799599) |
||||
* [原理分析之如何路由](files/90012_原理分析之如何路由.md?t=1565916799599) |
||||
* [原理分析之文档归纳](files/90013_原理分析之文档归纳.md?t=1565916799600) |
||||
* [常见问题](files/90100_常见问题.md?t=1565916799600) |
||||
|
@ -0,0 +1,154 @@ |
||||
/* |
||||
* 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 com.gitee.sop.gatewaycommon.gateway.common; |
||||
|
||||
import org.springframework.core.io.InputStreamResource; |
||||
import org.springframework.http.HttpEntity; |
||||
import org.springframework.http.HttpHeaders; |
||||
import org.springframework.http.MediaType; |
||||
import org.springframework.util.LinkedMultiValueMap; |
||||
import org.springframework.util.MultiValueMap; |
||||
import org.springframework.web.multipart.MultipartFile; |
||||
import org.springframework.web.multipart.MultipartHttpServletRequest; |
||||
import org.springframework.web.util.UriComponentsBuilder; |
||||
|
||||
import javax.servlet.http.HttpServletRequest; |
||||
import java.io.IOException; |
||||
import java.nio.charset.Charset; |
||||
import java.util.HashSet; |
||||
import java.util.List; |
||||
import java.util.Map; |
||||
import java.util.Map.Entry; |
||||
import java.util.Set; |
||||
import java.util.stream.Collectors; |
||||
|
||||
import static java.util.Arrays.stream; |
||||
import static java.util.Collections.emptyMap; |
||||
import static org.springframework.util.StringUtils.isEmpty; |
||||
import static org.springframework.util.StringUtils.tokenizeToStringArray; |
||||
import static org.springframework.util.StringUtils.uriDecode; |
||||
|
||||
public class RequestContentDataExtractor { |
||||
public static MultiValueMap<String, Object> extract(HttpServletRequest request) throws IOException { |
||||
return (request instanceof MultipartHttpServletRequest) ? |
||||
extractFromMultipartRequest((MultipartHttpServletRequest) request) : |
||||
extractFromRequest(request); |
||||
} |
||||
|
||||
private static MultiValueMap<String, Object> extractFromRequest(HttpServletRequest request) throws IOException { |
||||
MultiValueMap<String, Object> builder = new LinkedMultiValueMap<>(); |
||||
Set<String> queryParams = findQueryParams(request); |
||||
|
||||
for (Entry<String, String[]> entry : request.getParameterMap().entrySet()) { |
||||
String key = entry.getKey(); |
||||
|
||||
if (!queryParams.contains(key) && entry.getValue() != null) { |
||||
for (String value : entry.getValue()) { |
||||
builder.add(key, value); |
||||
} |
||||
} |
||||
} |
||||
|
||||
return builder; |
||||
} |
||||
|
||||
private static MultiValueMap<String, Object> extractFromMultipartRequest(MultipartHttpServletRequest request) |
||||
throws IOException { |
||||
MultiValueMap<String, Object> builder = new LinkedMultiValueMap<>(); |
||||
Map<String, List<String>> queryParamsGroupedByName = findQueryParamsGroupedByName( |
||||
request); |
||||
Set<String> queryParams = findQueryParams(request); |
||||
|
||||
for (Entry<String, String[]> entry : request.getParameterMap().entrySet()) { |
||||
String key = entry.getKey(); |
||||
List<String> listOfAllParams = stream(request.getParameterMap().get(key)) |
||||
.collect(Collectors.toList()); |
||||
List<String> listOfOnlyQueryParams = queryParamsGroupedByName.get(key); |
||||
|
||||
if(listOfOnlyQueryParams != null) { |
||||
listOfOnlyQueryParams = listOfOnlyQueryParams.stream() |
||||
.map(param -> uriDecode(param, Charset.defaultCharset())) |
||||
.collect(Collectors.toList()); |
||||
if (!listOfOnlyQueryParams.containsAll(listOfAllParams)) { |
||||
listOfAllParams.removeAll(listOfOnlyQueryParams); |
||||
for (String value : listOfAllParams) { |
||||
builder.add(key, |
||||
new HttpEntity<>(value, newHttpHeaders(request, key))); |
||||
} |
||||
} |
||||
} |
||||
|
||||
if (!queryParams.contains(key)) { |
||||
for (String value : entry.getValue()) { |
||||
builder.add(key, |
||||
new HttpEntity<>(value, newHttpHeaders(request, key))); |
||||
} |
||||
} |
||||
} |
||||
|
||||
for (Entry<String, List<MultipartFile>> parts : request.getMultiFileMap().entrySet()) { |
||||
for (MultipartFile file : parts.getValue()) { |
||||
HttpHeaders headers = new HttpHeaders(); |
||||
headers.setContentDispositionFormData(file.getName(), file.getOriginalFilename()); |
||||
if (file.getContentType() != null) { |
||||
headers.setContentType(MediaType.valueOf(file.getContentType())); |
||||
} |
||||
|
||||
HttpEntity entity = new HttpEntity<>(new InputStreamResource(file.getInputStream()), headers); |
||||
builder.add(parts.getKey(), entity); |
||||
} |
||||
} |
||||
|
||||
return builder; |
||||
} |
||||
|
||||
private static HttpHeaders newHttpHeaders(MultipartHttpServletRequest request, |
||||
String key) { |
||||
HttpHeaders headers = new HttpHeaders(); |
||||
String type = request.getMultipartContentType(key); |
||||
|
||||
if (type != null) { |
||||
headers.setContentType(MediaType.valueOf(type)); |
||||
} |
||||
return headers; |
||||
} |
||||
|
||||
private static Set<String> findQueryParams(HttpServletRequest request) { |
||||
Set<String> result = new HashSet<>(); |
||||
String query = request.getQueryString(); |
||||
|
||||
if (query != null) { |
||||
for (String value : tokenizeToStringArray(query, "&")) { |
||||
if (value.contains("=")) { |
||||
value = value.substring(0, value.indexOf("=")); |
||||
} |
||||
result.add(value); |
||||
} |
||||
} |
||||
|
||||
return result; |
||||
} |
||||
|
||||
static Map<String, List<String>> findQueryParamsGroupedByName( |
||||
HttpServletRequest request) { |
||||
String query = request.getQueryString(); |
||||
if (isEmpty(query)) { |
||||
return emptyMap(); |
||||
} |
||||
return UriComponentsBuilder.fromUriString("?" + query).build().getQueryParams(); |
||||
} |
||||
} |
@ -1,20 +0,0 @@ |
||||
package com.gitee.sop.gatewaycommon.gateway.common; |
||||
|
||||
import lombok.extern.slf4j.Slf4j; |
||||
import org.springframework.web.server.ServerWebExchange; |
||||
|
||||
import javax.servlet.http.HttpServletRequest; |
||||
import java.nio.charset.StandardCharsets; |
||||
|
||||
/** |
||||
* @author tanghc |
||||
*/ |
||||
@Slf4j |
||||
public class ServletFileUploadUtil { |
||||
|
||||
public static HttpServletRequest getFileUploadRequest(ServerWebExchange exchange, String requestBody) { |
||||
byte[] data = requestBody.getBytes(StandardCharsets.UTF_8); |
||||
return new FileUploadHttpServletRequest(exchange.getRequest(), data); |
||||
} |
||||
|
||||
} |
@ -0,0 +1,102 @@ |
||||
package com.gitee.sop.gatewaycommon.gateway.common; |
||||
|
||||
import io.netty.buffer.ByteBufAllocator; |
||||
import org.springframework.core.io.buffer.DataBuffer; |
||||
import org.springframework.core.io.buffer.NettyDataBufferFactory; |
||||
import org.springframework.http.HttpHeaders; |
||||
import org.springframework.http.HttpMethod; |
||||
import org.springframework.http.server.reactive.ServerHttpRequest; |
||||
import org.springframework.http.server.reactive.ServerHttpRequestDecorator; |
||||
import org.springframework.web.util.UriComponentsBuilder; |
||||
import reactor.core.publisher.Flux; |
||||
|
||||
import java.net.URI; |
||||
|
||||
/** |
||||
* @author tanghc |
||||
*/ |
||||
public class SopServerHttpRequestDecorator extends ServerHttpRequestDecorator { |
||||
|
||||
private Flux<DataBuffer> bodyDataBuffer; |
||||
private HttpHeaders httpHeaders; |
||||
private URI uri; |
||||
|
||||
/** |
||||
* ServerHttpRequest包装,作用类似于HttpServletRequestWrapper |
||||
* @param delegate 老的request |
||||
* @param queryString get请求后面的参数 |
||||
*/ |
||||
public SopServerHttpRequestDecorator(ServerHttpRequest delegate, HttpHeaders newHeaders, String queryString) { |
||||
super(delegate); |
||||
if (delegate.getMethod() != HttpMethod.GET) { |
||||
throw new IllegalArgumentException("this constructor must be used by GET request."); |
||||
} |
||||
if (queryString == null) { |
||||
throw new IllegalArgumentException("queryString can not be null."); |
||||
} |
||||
if (newHeaders == null) { |
||||
throw new IllegalArgumentException("newHeaders can not be null."); |
||||
} |
||||
|
||||
this.httpHeaders = newHeaders; |
||||
this.uri = UriComponentsBuilder.fromUri(delegate.getURI()) |
||||
.replaceQuery(queryString) |
||||
.build(true) |
||||
.toUri(); |
||||
} |
||||
|
||||
|
||||
|
||||
/** |
||||
* ServerHttpRequest包装,作用类似于HttpServletRequestWrapper |
||||
* @param delegate 老的request |
||||
* @param newHeaders 新的headers |
||||
* @param bodyData 请求体内容 |
||||
*/ |
||||
public SopServerHttpRequestDecorator(ServerHttpRequest delegate, HttpHeaders newHeaders, byte[] bodyData) { |
||||
// 将请求体再次封装写回到request里,传到下一级,否则,由于请求体已被消费,后续的服务将取不到值
|
||||
super(delegate); |
||||
if (bodyData == null) { |
||||
throw new IllegalArgumentException("bodyData can not be null."); |
||||
} |
||||
if (newHeaders == null) { |
||||
throw new IllegalArgumentException("newHeaders can not be null."); |
||||
} |
||||
this.httpHeaders = newHeaders; |
||||
// 由于请求体已改变,这里要重新设置contentLength
|
||||
int contentLength = bodyData.length; |
||||
httpHeaders.setContentLength(contentLength); |
||||
if (contentLength <= 0) { |
||||
// TODO: this causes a 'HTTP/1.1 411 Length Required' on httpbin.org
|
||||
httpHeaders.set(HttpHeaders.TRANSFER_ENCODING, "chunked"); |
||||
} |
||||
this.bodyDataBuffer = stringBuffer(bodyData); |
||||
} |
||||
|
||||
/** |
||||
* 字符串转DataBuffer |
||||
* @param bytes 请求体 |
||||
* @return 返回buffer |
||||
*/ |
||||
private static Flux<DataBuffer> stringBuffer(byte[] bytes) { |
||||
NettyDataBufferFactory nettyDataBufferFactory = new NettyDataBufferFactory(ByteBufAllocator.DEFAULT); |
||||
DataBuffer buffer = nettyDataBufferFactory.allocateBuffer(bytes.length); |
||||
buffer.write(bytes); |
||||
return Flux.just(buffer); |
||||
} |
||||
|
||||
@Override |
||||
public URI getURI() { |
||||
return uri == null ? super.getURI() : uri; |
||||
} |
||||
|
||||
@Override |
||||
public HttpHeaders getHeaders() { |
||||
return this.httpHeaders; |
||||
} |
||||
|
||||
@Override |
||||
public Flux<DataBuffer> getBody() { |
||||
return bodyDataBuffer == null ? super.getBody() : bodyDataBuffer; |
||||
} |
||||
} |
@ -0,0 +1,51 @@ |
||||
package com.gitee.sop.gatewaycommon.gateway.filter; |
||||
|
||||
import com.gitee.sop.gatewaycommon.gateway.ServerWebExchangeUtil; |
||||
import com.gitee.sop.gatewaycommon.param.ApiParam; |
||||
import com.gitee.sop.gatewaycommon.param.ParamNames; |
||||
import com.gitee.sop.gatewaycommon.param.ParameterFormatter; |
||||
import lombok.extern.slf4j.Slf4j; |
||||
import org.springframework.beans.factory.annotation.Autowired; |
||||
import org.springframework.cloud.gateway.filter.GatewayFilterChain; |
||||
import org.springframework.cloud.gateway.filter.GlobalFilter; |
||||
import org.springframework.core.Ordered; |
||||
import org.springframework.web.server.ServerWebExchange; |
||||
import reactor.core.publisher.Mono; |
||||
|
||||
import static com.gitee.sop.gatewaycommon.gateway.filter.Orders.PARAMETER_FORMATTER_FILTER_ORDER; |
||||
|
||||
/** |
||||
* @author tanghc |
||||
*/ |
||||
@Slf4j |
||||
public class ParameterFormatterFilter implements GlobalFilter, Ordered { |
||||
|
||||
@Autowired(required = false) |
||||
private ParameterFormatter sopParameterFormatter; |
||||
|
||||
@Override |
||||
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) { |
||||
ApiParam apiParam = ServerWebExchangeUtil.getApiParam(exchange); |
||||
if (apiParam == null) { |
||||
return chain.filter(exchange); |
||||
} |
||||
// 校验成功后进行参数转换
|
||||
if (sopParameterFormatter != null) { |
||||
ServerWebExchange formatExchange = ServerWebExchangeUtil.format( |
||||
exchange |
||||
, apiParam |
||||
, sopParameterFormatter::format |
||||
, httpHeaders -> httpHeaders.set(ParamNames.HEADER_VERSION_NAME, apiParam.fetchVersion())); |
||||
if (formatExchange == null) { |
||||
return chain.filter(exchange); |
||||
} |
||||
return chain.filter(formatExchange); |
||||
} |
||||
return chain.filter(exchange); |
||||
} |
||||
|
||||
@Override |
||||
public int getOrder() { |
||||
return PARAMETER_FORMATTER_FILTER_ORDER; |
||||
} |
||||
} |
@ -0,0 +1,33 @@ |
||||
package com.gitee.sop.gatewaycommon.param; |
||||
|
||||
import org.springframework.http.HttpHeaders; |
||||
import org.springframework.http.HttpOutputMessage; |
||||
|
||||
import java.io.ByteArrayOutputStream; |
||||
import java.io.IOException; |
||||
import java.io.OutputStream; |
||||
|
||||
/** |
||||
* @author tanghc |
||||
*/ |
||||
public class FormHttpOutputMessage implements HttpOutputMessage { |
||||
|
||||
private HttpHeaders headers = new HttpHeaders(); |
||||
private ByteArrayOutputStream output = new ByteArrayOutputStream(); |
||||
|
||||
@Override |
||||
public HttpHeaders getHeaders() { |
||||
return this.headers; |
||||
} |
||||
|
||||
@Override |
||||
public OutputStream getBody() throws IOException { |
||||
return this.output; |
||||
} |
||||
|
||||
public byte[] getInput() throws IOException { |
||||
this.output.flush(); |
||||
return this.output.toByteArray(); |
||||
} |
||||
|
||||
} |
@ -0,0 +1,10 @@ |
||||
package com.gitee.sop.gatewaycommon.param; |
||||
|
||||
import com.alibaba.fastjson.JSONObject; |
||||
import com.gitee.sop.gatewaycommon.manager.Formatter; |
||||
|
||||
/** |
||||
* @author tanghc |
||||
*/ |
||||
public interface ParameterFormatter extends Formatter<JSONObject> { |
||||
} |
@ -1,10 +0,0 @@ |
||||
package com.gitee.sop.gatewaycommon.zuul.param; |
||||
|
||||
import com.alibaba.fastjson.JSONObject; |
||||
import com.gitee.sop.gatewaycommon.manager.ParameterFormatter; |
||||
|
||||
/** |
||||
* @author tanghc |
||||
*/ |
||||
public interface ZuulParameterFormatter extends ParameterFormatter<JSONObject> { |
||||
} |
Loading…
Reference in new issue