spring-projects / spring-framework

Spring Framework
https://spring.io/projects/spring-framework
Apache License 2.0
56.21k stars 37.97k forks source link

WebClient: Explicit Content-Type application/octet-stream is overwritten #33303

Closed othi closed 2 days ago

othi commented 1 month ago

Affected version: 6.1.11

The API I'm consuming requires the Content-Type header to be application/octet-stream for certain binary payloads.

The Content-Type is set explicitly using RequestBodySpec.contentType() but it is later overwritten in this method: https://github.com/spring-projects/spring-framework/blob/29dce74fcdd62c6c877973378368fe342afb5fbd/spring-web/src/main/java/org/springframework/http/codec/ResourceHttpMessageWriter.java#L165-L176

This is unintuitive behaviour and not configurable. To circumvent this, I have to rename the file to an extension not present in spring-web/src/main/resources/org/springframework/http/mime.types so my explicitly set header is respected.

To reproduce, here is a function that sets the content type of the request:

    public void test() throws IOException {
        File f = File.createTempFile("test", ".pdf");

        WebClient webClient = WebClient.create();
        RequestBodySpec builder = webClient.method(HttpMethod.POST).uri("http://localhost:12458");
        builder.contentType(MediaType.APPLICATION_OCTET_STREAM);
        builder.body(BodyInserters.fromValue(new FileSystemResource(f)));
        builder.retrieve().toBodilessEntity().block();
    }

Resulting request using netcat:

me@dev:~$ nc -l 12458
POST / HTTP/1.1
accept-encoding: gzip
user-agent: ReactorNetty/1.1.9
host: localhost:12458
accept: */*
Content-Type: application/pdf   <---------
content-length: 0

Expected result:

Content-Type: application/octet-stream

Thanks

imvtsl commented 1 month ago

@bclozel Is it okay if I work on this issue?

bclozel commented 4 days ago

Sorry for the delayed feedback.

I'm not sure we can support this specific use case without undoing the mime type detection, which is widely used. I think application/octet-stream is assumed as the default for many client libraries, hence our check.

Have you considered using a custom media type like application/octet-stream;force=true or something equivalent? An additional parameter would skip this check.

othi commented 2 days ago

That does work in my case but it still seems unfortunate to have to do that.

Would it not be acceptable to you to have the setter set a flag to indicate that it was called, making the choice of the content-type explicit? I understand that mime type detection is a helpful feature, but if the setter is called on the builder, surely the user did not want that choice to be overridden?

bclozel commented 2 days ago

Thanks for the proposal, but I think we're going to stick with the current behavior.

The ResourceHttpMessageWriter is trying to detect a more precise media type, and here it does. I'm not sure what the requirement is here, maybe because the remote server doesn't support anything but application/octet-stream? In this case, if the filename is not meant to be used at all as source of truth, I would suggest using a Resource implementation that does not expose the filename information. This would also work and probably better reflect the use case you're facing: the file information is not relevant, only the content bytes.