Spring Cloud Gateway: измененное тело ответа усечено
Я немного экспериментировал с Spring Cloud Gateway и пытаюсь изменить тело ответа. Используя декоратор ответа, я могу видеть, что тело изменено, однако размер буфера по-прежнему равен размеру исходного ответа. Есть ли способ увеличить размер буфера до размера тела нового ответа?
public class ModifyBodyGatewayFilterImpl implements GatewayFilter {
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
logger.info("\n\nexchange.getAttributes():\n {}\n\n", exchange.getAttributes());
ServerHttpResponse response = exchange.getResponse();
DataBufferFactory dataBufferFactory = response.bufferFactory();
ServerHttpResponseDecorator decoratedResponse = new ServerHttpResponseDecorator(response) {
@Override
public Mono<Void> writeWith(Publisher<? extends DataBuffer> body) {
Flux<? extends DataBuffer> flux = (Flux<? extends DataBuffer>) body;
Flux<? extends DataBuffer> f = flux.flatMap( dataBuffer -> {
byte[] origRespContent = new byte[dataBuffer.readableByteCount()];
dataBuffer.read(origRespContent);
System.out.println("content::: " + (new String(origRespContent)));
//alocating a new buffer size does not help.
DataBuffer b = dataBufferFactory.allocateBuffer(256);
b.write("0123456789abcdefg".getBytes());
return Flux.just(b);
});
return super.writeWith(f);
}
};
ServerWebExchange swe = exchange.mutate().response(decoratedResponse).build();
return chain.filter(swe);
}
}
Пример: ожидаемый переписанный ответ 0123456789abcdefg
Если исходный контент составляет 11 байт <p>test</p>
затем переписанный ответ усекается до 0123456789a
,
5 ответов
Вы можете использовать это
// prepare the mono to be returned
DataBufferFactory dataBufferFactory = exchange.getResponse().bufferFactory();
ObjectMapper objMapper = new ObjectMapper();
byte[] obj;
try {
obj = objMapper.writeValueAsBytes(response);
return exchange.getResponse().writeWith(Mono.just(obj).map(r -> dataBufferFactory.wrap(r)));
} catch (JsonProcessingException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return exchange.getResponse().setComplete();
где response
любой объект, который вы хотите, должен быть сериализуемым.
Я решил эту проблему с помощью метода buffer():
public class ModifyBodyGatewayFilterImpl implements GatewayFilter {
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
ServerHttpResponse response = exchange.getResponse();
DataBufferFactory dataBufferFactory = response.bufferFactory();
ServerHttpResponseDecorator decoratedResponse = new ServerHttpResponseDecorator(response) {
@Override
public Mono<Void> writeWith(Publisher<? extends DataBuffer> body) {
if (body instanceof Flux) {
Flux<? extends DataBuffer> flux = (Flux<? extends DataBuffer>) body;
return super.writeWith(flux.buffer().map(dataBuffers -> {
ByteOutputStream outputStream = new ByteOutputStream();
dataBuffers.forEach(i -> {
byte[] array = new byte[i.readableByteCount()];
i.read(array);
outputStream.write(array);
});
outputStream.write("0123456789abcdefg".getBytes());
return dataBufferFactory.wrap(outputStream.getBytes());
}));
}
return super.writeWith(body);
}
};
ServerWebExchange swe = exchange.mutate().response(decoratedResponse).build();
return chain.filter(swe);
}
}
он должен поместить все части ответа в буфер, который должен быть освобожден после завершения потока.
Вам нужно переписать content-length
заголовок тоже. Нравится:
byte[] bytes = "0123456789abcdefg".getBytes();
DataBuffer b = dataBufferFactory.wrap(bytes);
response.getHeaders().setContentLength(bytes.length);
Надеюсь, это поможет:)
У меня была такая же проблема, данные поступали в два буфера. На основе решения @Alex я улучшил его, присоединившись, но без
ByteOutpoutStream
.
В своем решении я использовал
DefaultDataBufferFactory
и
join()
метод.
@Override
public Mono<Void> writeWith(final Publisher<? extends DataBuffer> body) {
if (body instanceof Flux) {
Flux<? extends DataBuffer> fluxBody = (Flux<? extends DataBuffer>) body;
return super.writeWith(fluxBody.buffer().map(dataBuffers -> {
var joinedBuffers = new DefaultDataBufferFactory().join(dataBuffers);
byte[] content = new byte[joinedBuffers.readableByteCount()];
joinedBuffers.read(content);
final var responseBody = new String(content, StandardCharsets.UTF_8);
// modify body
return bufferFactory.wrap(responseBody.getBytes());
}));
}
return super.writeWith(body);
}
Я искал общий способ изменить ответ (в основном я изменяю тело JSON) без необходимости иметь дело с
Flux
(т.е.,
Mono
это нормально), поэтому я начал смотреть на то, что SCG делает сегодня, и с помощью информации из этой сути и из этой записи в блоге .
Я смог придумать быстрое и грязное решение и написал его здесь . В основном я просто изменил то, что
ModifyResponseBodyGatewayFilterFactory
делает сегодня и немного изменить его для моего варианта использования. Фрагмент решения здесь:
@SuppressWarnings("unchecked")
@Override
public Mono<Void> writeWith(Publisher<? extends DataBuffer> body) {
Class inClass = String.class;
Class outClass = String.class;
String originalResponseContentType = exchange.getAttribute(ORIGINAL_RESPONSE_CONTENT_TYPE_ATTR);
HttpHeaders httpHeaders = new HttpHeaders();
httpHeaders.add(HttpHeaders.CONTENT_TYPE, originalResponseContentType);
ClientResponse clientResponse = prepareClientResponse(body, httpHeaders);
// TODO: flux or mono
Mono modifiedBody = extractBody(exchange, clientResponse, inClass)
.flatMap(originalBody -> Mono.just(applyTransform((String) originalBody, config))
.switchIfEmpty(Mono.empty());
BodyInserter bodyInserter = BodyInserters.fromPublisher(modifiedBody, outClass);
CachedBodyOutputMessage outputMessage = new CachedBodyOutputMessage(exchange,
exchange.getResponse().getHeaders());
return bodyInserter.insert(outputMessage, new BodyInserterContext()).then(Mono.defer(() -> {
Mono<DataBuffer> messageBody = writeBody(getDelegate(), outputMessage, outClass);
HttpHeaders headers = getDelegate().getHeaders();
if (!headers.containsKey(HttpHeaders.TRANSFER_ENCODING)
|| headers.containsKey(HttpHeaders.CONTENT_LENGTH)) {
messageBody = messageBody.doOnNext(data -> headers.setContentLength(data.readableByteCount()));
}
// TODO: fail if isStreamingMediaType?
return getDelegate().writeWith(messageBody);
}));
}
Опять же, это решение может быть не тем, что здесь нужно, но, надеюсь, оно поможет тому, кому нужно подобное решение.