spring-cloud / spring-cloud-gateway

An API Gateway built on Spring Framework and Spring Boot providing routing and more.
http://cloud.spring.io
Apache License 2.0
4.51k stars 3.31k forks source link

How to know the resonpse body (i.e. string format) is null or empty in spring-cloud-gateway? #1424

Closed valuetodays closed 4 years ago

valuetodays commented 4 years ago
ServerHttpResponseDecorator decoratedResponse = new ServerHttpResponseDecorator(exchange.getResponse()) {
    @Override
    public Mono<Void> writeWith(Publisher<? extends DataBuffer> body) {
        if (body instanceof Flux) {
            Flux<? extends DataBuffer> fluxBody = (Flux<? extends DataBuffer>) body;
            // can i know response body string is null/empty by fluxBody, how? 
        }
       return super.writeWith(body);
    }
};

Thx.

TYsewyn commented 4 years ago

You could check the Content-Length or Transfer-Encoding headers using the getHeaders() method. That method will retrieve the headers from the original response.

valuetodays commented 4 years ago

I tried just now, the Content-Length is -1 when downstream returns a json, and it is 0 when get null from downstream. I checked the javadoc in org.springframework.http.HttpHeaders#getContentLength, it said:

    /**
     * Return the length of the body in bytes, as specified by the
     * {@code Content-Length} header.
     * <p>Returns -1 when the content-length is unknown.
     */

Why Content-Length is -1 when downstream return a json? Is there something wrong with downstream? My downstream is just a springboot application with springcloud-config and eureka configured. SpringBoot Version: 2.1.6.RELEASE SpringCloud Version: Greenwich.RELEASE

valuetodays commented 4 years ago

I checked many times and got the following results from a SpingBoot application.

The following code snippets works for me, but I am not sure whether it is exact enough .

ServerHttpResponseDecorator decoratedResponse = new ServerHttpResponseDecorator(exchange.getResponse()) {
    @Override
    public Mono<Void> writeWith(Publisher<? extends DataBuffer> body) {
        if (body instanceof Flux) {
            Flux<? extends DataBuffer> fluxBody = (Flux<? extends DataBuffer>) body;

            String transferEncoding = this.getDelegate().getHeaders().getFirst(HttpHeaders.TRANSFER_ENCODING);
            long contentLength = this.getDelegate().getHeaders().getContentLength();

            // when "Transfer-Encoding" is null && "Content-Length" is 0
            if (null == transferEncoding && 0 == contentLength) {
                // do something
            }
        }
       return super.writeWith(body);
    }
};
kkocel commented 4 years ago

@billysAnna in case you don't trust those headers - you can read the body and get it's lenght

valuetodays commented 4 years ago

@kkocel I tried, but the following code snippets not work.

Flux<? extends DataBuffer> fluxBody = (Flux<? extends DataBuffer>) body;
Flux<DataBuffer> respDataBuffer = fluxBody.buffer().map(dataBuffers -> {
    // when get null from downstream, cannot go here
});

So, I want to know whether fuluxBody can indicate that or not.

spencergibb commented 4 years ago

switchOnEmpty()?

TYsewyn commented 4 years ago

Please take a look at the snippet below to work with those data buffers. Try to always use the DataBufferUtils class since those util methods also take retaining/releasing byte buffers into account. I didn't test this prior to posting it since I don't know your use case but this might point you in the right direction.

Hope this helps!

ServerHttpResponseDecorator decoratedResponse = new ServerHttpResponseDecorator(exchange.getResponse()) {
    @Override
    public Mono<Void> writeWith(Publisher<? extends DataBuffer> body) {
        Mono<DataBuffer> newBody = DataBufferUtils.join(body)
            .map(content -> {
                // Using readPosition() and readableByteCount() you can determine if the body is empty.
                // You can also take the HTTP headers into account again.
                // In case null and empty should behave the same you could return Mono.empty() here.
            })
            .switchIfEmpty(/* Provide a fallback in case there was no content. */)
        return super.writeWith(newBody);
    }
};
valuetodays commented 4 years ago

@TYsewyn @spencergibb I tried what you said. That works for me.

Still another way is to let the downstream return a wrapped object instead of a simple one. if the downstream return a User object, tell them to return Rest wrapper object.

public class Rest<T> {
     private int errorCode;
     private String errorMsg;
     private T data;
     // getter and setter ....
}

Thus, an api named /user/findById/{id} will return Rest object even the user with specified id not exists., which will always response a non-null object back to gateway. And I do not face the question any more. :)