Spring Cloud Gateway:修改后的响应主体被截断

时间:2018-01-26 21:47:06

标签: java spring spring-cloud gateway reactor-netty

我已经尝试过一些Spring Cloud Gateway,我试图修改响应体。使用响应装饰器,我能够看到主体被修改,但是,缓冲区大小仍然是原始响应的大小。有没有办法将缓冲区大小扩展到新响应主体的大小?

Eigen::internal::set_is_malloc_allowed(false);

示例:预期重写的响应为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); } } 如果原始内容为11字节0123456789abcdefg,则重写的响应将被截断为<p>test</p>

5 个答案:

答案 0 :(得分:1)

我使用 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);
  }
}

它应该将所有响应块放在缓冲区中,当磁通完成时应该释放它。

答案 1 :(得分:0)

您可以使用

// 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是您想要的任何对象,它必须可序列化。

答案 2 :(得分:0)

您还需要重写content-length标头。 像这样:

byte[] bytes = "0123456789abcdefg".getBytes();
DataBuffer b = dataBufferFactory.wrap(bytes);
response.getHeaders().setContentLength(bytes.length);

我希望这会有所帮助:)

答案 3 :(得分:0)

我遇到了同样的问题,在两个缓冲区中接收到数据。基于@Alex解决方案,我通过加入而没有ByteOutpoutStream对其进行了改进。

在我的解决方案中,我使用了DefaultDataBufferFactoryjoin()方法。

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

答案 4 :(得分:0)

我一直在寻找一种通用的方法来修改响应(我基本上是在修改 JSON 正文)而无需处理 Mono(即 ModifyResponseBodyGatewayFilterFactory 很好)所以我开始借助来自 this gistthis blog entry 的信息,看看 SCG 的 ModifyResponseBodyGatewayFilterFactory 今天做了什么。

我能够想出一个快速而肮脏的解决方案并将其写下来here。基本上,我只是修改了 @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); })); } 今天正在做的事情,并针对我的用例进行了一些更改。解决方案的一个片段在这里:

#include <QtWidgets>

class Helper: public QObject{
public:
    Helper(QLineEdit *le): QObject(le), m_le(le){
        m_le->installEventFilter(this);
    }
    bool eventFilter(QObject *watched, QEvent *event){
        if(watched == m_le && event->type() == QEvent::Show){
            QString filename = m_le->text();
            QFileInfo fi(filename);
            QString base = fi.baseName();
            m_le->setSelection(0, base.length());
        }
        return QObject::eventFilter(watched, event);
    }
private:
    QLineEdit* m_le;
};

class StyledItemDelegate: public QStyledItemDelegate{
public:
    using QStyledItemDelegate::QStyledItemDelegate;
    QWidget* createEditor(QWidget *parent, const QStyleOptionViewItem & option, const QModelIndex &index) const{
        QWidget *editor = QStyledItemDelegate::createEditor(parent, option, index);
        if(QLineEdit *le = qobject_cast<QLineEdit *>(editor)){
            new Helper(le);
        }
        return editor;
    }
};

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);
    QTreeWidget w;
    w.setItemDelegate(new StyledItemDelegate);
    for(const QString & filename: {"foo.txt", "foo.tar.gz", "foo.cpp"}){
        auto item = new QTreeWidgetItem({filename});
        item->setFlags(item->flags() | Qt::ItemIsEditable);
        w.addTopLevelItem(item);
    }
    w.show();
    return a.exec();
}

同样,这个解决方案可能不是这里需要的,但希望这能帮助需要类似解决方案的人。