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

Опять же, это решение может быть не тем, что здесь нужно, но, надеюсь, оно поможет тому, кому нужно подобное решение.

Другие вопросы по тегам