|  |  |  | @ -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())); | 
			
		
	
		
			
				
					|  |  |  |  |     } | 
			
		
	
		
			
				
					|  |  |  |  | } |