|
|
|
@ -5,36 +5,58 @@ import com.gitee.sop.gatewaycommon.bean.SopConstants; |
|
|
|
|
import com.gitee.sop.gatewaycommon.result.ResultExecutor; |
|
|
|
|
import org.apache.commons.lang3.StringUtils; |
|
|
|
|
import org.reactivestreams.Publisher; |
|
|
|
|
import org.springframework.beans.factory.annotation.Autowired; |
|
|
|
|
import org.springframework.cloud.gateway.filter.GatewayFilterChain; |
|
|
|
|
import org.springframework.cloud.gateway.filter.GlobalFilter; |
|
|
|
|
import org.springframework.cloud.gateway.filter.NettyWriteResponseFilter; |
|
|
|
|
import org.springframework.cloud.gateway.filter.factory.rewrite.CachedBodyOutputMessage; |
|
|
|
|
import org.springframework.cloud.gateway.filter.factory.rewrite.MessageBodyEncoder; |
|
|
|
|
import org.springframework.cloud.gateway.support.BodyInserterContext; |
|
|
|
|
import org.springframework.cloud.gateway.support.DefaultClientResponse; |
|
|
|
|
import org.springframework.core.Ordered; |
|
|
|
|
import org.springframework.core.io.buffer.DataBuffer; |
|
|
|
|
import org.springframework.core.io.buffer.DataBufferFactory; |
|
|
|
|
import org.springframework.core.io.buffer.DataBufferUtils; |
|
|
|
|
import org.springframework.http.HttpHeaders; |
|
|
|
|
import org.springframework.http.HttpStatus; |
|
|
|
|
import org.springframework.http.MediaType; |
|
|
|
|
import org.springframework.http.ResponseCookie; |
|
|
|
|
import org.springframework.http.client.reactive.ClientHttpResponse; |
|
|
|
|
import org.springframework.http.codec.ServerCodecConfigurer; |
|
|
|
|
import org.springframework.http.server.reactive.ServerHttpResponse; |
|
|
|
|
import org.springframework.http.server.reactive.ServerHttpResponseDecorator; |
|
|
|
|
import org.springframework.util.MultiValueMap; |
|
|
|
|
import org.springframework.web.reactive.function.BodyInserter; |
|
|
|
|
import org.springframework.web.reactive.function.BodyInserters; |
|
|
|
|
import org.springframework.web.reactive.function.client.ExchangeStrategies; |
|
|
|
|
import org.springframework.web.reactive.function.client.ClientResponse; |
|
|
|
|
import org.springframework.web.server.ServerWebExchange; |
|
|
|
|
import reactor.core.publisher.Flux; |
|
|
|
|
import reactor.core.publisher.Mono; |
|
|
|
|
import reactor.core.scheduler.Schedulers; |
|
|
|
|
|
|
|
|
|
import javax.annotation.PostConstruct; |
|
|
|
|
import java.util.Collections; |
|
|
|
|
import java.util.List; |
|
|
|
|
import java.util.Map; |
|
|
|
|
import java.util.Set; |
|
|
|
|
import java.util.stream.Collectors; |
|
|
|
|
|
|
|
|
|
import static java.util.function.Function.identity; |
|
|
|
|
import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.ORIGINAL_RESPONSE_CONTENT_TYPE_ATTR; |
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
* 修改返回结果 |
|
|
|
|
* |
|
|
|
|
* @author tanghc |
|
|
|
|
*/ |
|
|
|
|
public class GatewayModifyResponseGatewayFilter implements GlobalFilter, Ordered { |
|
|
|
|
|
|
|
|
|
@Autowired |
|
|
|
|
private ServerCodecConfigurer codecConfigurer; |
|
|
|
|
@Autowired |
|
|
|
|
private Set<MessageBodyEncoder> bodyEncoders; |
|
|
|
|
|
|
|
|
|
private Map<String, MessageBodyEncoder> messageBodyEncoders; |
|
|
|
|
|
|
|
|
|
@Override |
|
|
|
|
@SuppressWarnings("unchecked") |
|
|
|
|
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) { |
|
|
|
@ -54,12 +76,14 @@ public class GatewayModifyResponseGatewayFilter implements GlobalFilter, Ordered |
|
|
|
|
} |
|
|
|
|
Class inClass = String.class; |
|
|
|
|
Class outClass = String.class; |
|
|
|
|
|
|
|
|
|
HttpHeaders httpHeaders = new HttpHeaders(); |
|
|
|
|
//explicitly add it in this way instead of 'httpHeaders.setContentType(originalResponseContentType)'
|
|
|
|
|
//this will prevent exception in case of using non-standard media types like "Content-Type: image"
|
|
|
|
|
// explicitly add it in this way instead of
|
|
|
|
|
// 'httpHeaders.setContentType(originalResponseContentType)'
|
|
|
|
|
// this will prevent exception in case of using non-standard media
|
|
|
|
|
// types like "Content-Type: image"
|
|
|
|
|
httpHeaders.add(HttpHeaders.CONTENT_TYPE, originalResponseContentType); |
|
|
|
|
ResponseAdapter responseAdapter = new ResponseAdapter(body, httpHeaders); |
|
|
|
|
DefaultClientResponse clientResponse = new DefaultClientResponse(responseAdapter, ExchangeStrategies.withDefaults()); |
|
|
|
|
ClientResponse clientResponse = prepareClientResponse(exchange, body, httpHeaders); |
|
|
|
|
|
|
|
|
|
//TODO: flux or mono
|
|
|
|
|
Mono modifiedBody = clientResponse.bodyToMono(inClass) |
|
|
|
@ -72,16 +96,21 @@ public class GatewayModifyResponseGatewayFilter implements GlobalFilter, Ordered |
|
|
|
|
return Mono.just(ret); |
|
|
|
|
}); |
|
|
|
|
|
|
|
|
|
BodyInserter bodyInserter = BodyInserters.fromPublisher(modifiedBody, outClass); |
|
|
|
|
CachedBodyOutputMessage outputMessage = new CachedBodyOutputMessage(exchange, exchange.getResponse().getHeaders()); |
|
|
|
|
BodyInserter bodyInserter = BodyInserters.fromPublisher(modifiedBody, |
|
|
|
|
outClass); |
|
|
|
|
CachedBodyOutputMessage outputMessage = new CachedBodyOutputMessage(exchange, |
|
|
|
|
exchange.getResponse().getHeaders()); |
|
|
|
|
return bodyInserter.insert(outputMessage, new BodyInserterContext()) |
|
|
|
|
.then(Mono.defer(() -> { |
|
|
|
|
Flux<DataBuffer> messageBody = outputMessage.getBody(); |
|
|
|
|
Mono<DataBuffer> messageBody = writeBody(getDelegate(), |
|
|
|
|
outputMessage, outClass); |
|
|
|
|
HttpHeaders headers = getDelegate().getHeaders(); |
|
|
|
|
if (!headers.containsKey(HttpHeaders.TRANSFER_ENCODING)) { |
|
|
|
|
messageBody = messageBody.doOnNext(data -> headers.setContentLength(data.readableByteCount())); |
|
|
|
|
if (!headers.containsKey(HttpHeaders.TRANSFER_ENCODING) |
|
|
|
|
|| headers.containsKey(HttpHeaders.CONTENT_LENGTH)) { |
|
|
|
|
messageBody = messageBody.doOnNext(data -> headers |
|
|
|
|
.setContentLength(data.readableByteCount())); |
|
|
|
|
} |
|
|
|
|
//TODO: use isStreamingMediaType?
|
|
|
|
|
// TODO: fail if isStreamingMediaType?
|
|
|
|
|
return getDelegate().writeWith(messageBody); |
|
|
|
|
})); |
|
|
|
|
} |
|
|
|
@ -96,6 +125,42 @@ public class GatewayModifyResponseGatewayFilter implements GlobalFilter, Ordered |
|
|
|
|
return chain.filter(exchange.mutate().response(responseDecorator).build()); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
private ClientResponse prepareClientResponse( |
|
|
|
|
ServerWebExchange exchange, |
|
|
|
|
Publisher<? extends DataBuffer> body, |
|
|
|
|
HttpHeaders httpHeaders |
|
|
|
|
) { |
|
|
|
|
ClientResponse.Builder builder; |
|
|
|
|
builder = ClientResponse.create(exchange.getResponse().getStatusCode(), codecConfigurer.getReaders()); |
|
|
|
|
return builder.headers(headers -> headers.putAll(httpHeaders)) |
|
|
|
|
.body(Flux.from(body)).build(); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
private Mono<DataBuffer> writeBody(ServerHttpResponse httpResponse, |
|
|
|
|
CachedBodyOutputMessage message, Class<?> outClass) { |
|
|
|
|
Mono<DataBuffer> response = DataBufferUtils.join(message.getBody()); |
|
|
|
|
if (byte[].class.isAssignableFrom(outClass)) { |
|
|
|
|
return response; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
List<String> encodingHeaders = httpResponse.getHeaders() |
|
|
|
|
.getOrEmpty(HttpHeaders.CONTENT_ENCODING); |
|
|
|
|
for (String encoding : encodingHeaders) { |
|
|
|
|
MessageBodyEncoder encoder = messageBodyEncoders.get(encoding); |
|
|
|
|
if (encoder != null) { |
|
|
|
|
DataBufferFactory dataBufferFactory = httpResponse.bufferFactory(); |
|
|
|
|
response = response.publishOn(Schedulers.parallel()).map(buffer -> { |
|
|
|
|
byte[] encodedResponse = encoder.encode(buffer); |
|
|
|
|
DataBufferUtils.release(buffer); |
|
|
|
|
return encodedResponse; |
|
|
|
|
}).map(dataBufferFactory::wrap); |
|
|
|
|
break; |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
return response; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
@Override |
|
|
|
|
public int getOrder() { |
|
|
|
|
return NettyWriteResponseFilter.WRITE_RESPONSE_FILTER_ORDER - 1; |
|
|
|
@ -140,4 +205,10 @@ public class GatewayModifyResponseGatewayFilter implements GlobalFilter, Ordered |
|
|
|
|
return null; |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
@PostConstruct |
|
|
|
|
public void after() { |
|
|
|
|
this.messageBodyEncoders = bodyEncoders == null ? Collections.emptyMap() : bodyEncoders.stream() |
|
|
|
|
.collect(Collectors.toMap(MessageBodyEncoder::encodingType, identity())); |
|
|
|
|
} |
|
|
|
|
} |