Micronaut-core: MultipartBody with declarative client not working

Created on 30 Jul 2019  路  1Comment  路  Source: micronaut-projects/micronaut-core

Having a declarative client like:

@Client("/graphql")
static interface GraphQLClient {

        @Post
        @Header(name = CONTENT_TYPE, value = MULTIPART_FORM_DATA)
        GraphQLResponseBody post(@Body MultipartBody body)
}

and invoking it with:

void "test post with multipart body"() {
        given:
        MultipartBody body = MultipartBody.builder()
                .addPart("operations", '{ "query": "mutation ($file: Upload!) { singleUpload(file: $file) { id } }", "variables": { "file": null } }')
                .addPart("map", '{ "0": ["variables.file"] }')
                .addPart("0", "a.txt", TEXT_PLAIN_TYPE, "Alpha file content.".bytes)
                .build()

        when:
        GraphQLResponseBody response = graphQLClient.post(body)

        then:
        response != null
}

results in the following exception:


io.micronaut.http.codec.CodecException: Error encoding object [io.micronaut.http.client.multipart.MultipartBody@65da5a2b] to JSON: No serializer found for class io.micronaut.http.client.multipart.MultipartBody and no properties discovered to create BeanSerializer (to avoid exception, disable SerializationFeature.FAIL_ON_EMPTY_BEANS)

    at io.micronaut.jackson.codec.JsonMediaTypeCodec.encode(JsonMediaTypeCodec.java:176)
    at io.micronaut.jackson.codec.JsonMediaTypeCodec.encode(JsonMediaTypeCodec.java:182)
    at io.micronaut.http.client.DefaultHttpClient.lambda$buildNettyRequest$39(DefaultHttpClient.java:1508)
    at java.util.Optional.map(Optional.java:215)
    at io.micronaut.http.client.DefaultHttpClient.buildNettyRequest(DefaultHttpClient.java:1508)
    at io.micronaut.http.client.DefaultHttpClient.sendRequestThroughChannel(DefaultHttpClient.java:1546)
    at io.micronaut.http.client.DefaultHttpClient.lambda$null$26(DefaultHttpClient.java:1024)
    at io.netty.util.concurrent.DefaultPromise.notifyListener0(DefaultPromise.java:500)
    at io.netty.util.concurrent.DefaultPromise.notifyListeners0(DefaultPromise.java:493)
    at io.netty.util.concurrent.DefaultPromise.notifyListenersNow(DefaultPromise.java:472)
    at io.netty.util.concurrent.DefaultPromise.notifyListeners(DefaultPromise.java:413)
    at io.netty.util.concurrent.DefaultPromise.setValue0(DefaultPromise.java:538)
    at io.netty.util.concurrent.DefaultPromise.setSuccess0(DefaultPromise.java:527)
    at io.netty.util.concurrent.DefaultPromise.trySuccess(DefaultPromise.java:98)
    at io.netty.channel.DefaultChannelPromise.trySuccess(DefaultChannelPromise.java:84)
    at io.netty.channel.nio.AbstractNioChannel$AbstractNioUnsafe.fulfillConnectPromise(AbstractNioChannel.java:302)
    at io.netty.channel.nio.AbstractNioChannel$AbstractNioUnsafe.finishConnect(AbstractNioChannel.java:337)
    at io.netty.channel.nio.NioEventLoop.processSelectedKey(NioEventLoop.java:685)
    at io.netty.channel.nio.NioEventLoop.processSelectedKeysOptimized(NioEventLoop.java:632)
    at io.netty.channel.nio.NioEventLoop.processSelectedKeys(NioEventLoop.java:549)
    at io.netty.channel.nio.NioEventLoop.run(NioEventLoop.java:511)
    at io.netty.util.concurrent.SingleThreadEventExecutor$5.run(SingleThreadEventExecutor.java:918)
    at io.netty.util.internal.ThreadExecutorMap$2.run(ThreadExecutorMap.java:74)
    at io.netty.util.concurrent.FastThreadLocalRunnable.run(FastThreadLocalRunnable.java:30)
    at java.lang.Thread.run(Thread.java:748)
Caused by: com.fasterxml.jackson.databind.exc.InvalidDefinitionException: No serializer found for class io.micronaut.http.client.multipart.MultipartBody and no properties discovered to create BeanSerializer (to avoid exception, disable SerializationFeature.FAIL_ON_EMPTY_BEANS)
    at com.fasterxml.jackson.databind.exc.InvalidDefinitionException.from(InvalidDefinitionException.java:77)
    at com.fasterxml.jackson.databind.SerializerProvider.reportBadDefinition(SerializerProvider.java:1191)
    at com.fasterxml.jackson.databind.DatabindContext.reportBadDefinition(DatabindContext.java:313)
    at com.fasterxml.jackson.databind.ser.impl.UnknownSerializer.failForEmpty(UnknownSerializer.java:71)
    at com.fasterxml.jackson.databind.ser.impl.UnknownSerializer.serialize(UnknownSerializer.java:33)
    at com.fasterxml.jackson.databind.ser.DefaultSerializerProvider._serialize(DefaultSerializerProvider.java:480)
    at com.fasterxml.jackson.databind.ser.DefaultSerializerProvider.serializeValue(DefaultSerializerProvider.java:319)
    at com.fasterxml.jackson.databind.ObjectMapper._configAndWriteValue(ObjectMapper.java:3905)
    at com.fasterxml.jackson.databind.ObjectMapper.writeValueAsBytes(ObjectMapper.java:3243)
    at io.micronaut.jackson.codec.JsonMediaTypeCodec.encode(JsonMediaTypeCodec.java:173)
    ... 24 more

When I create a RxHttpClient and try:

void "test post with multipart body 2"() {
        given:
        RxHttpClient httpClient = embeddedServer.applicationContext.createBean(RxHttpClient, embeddedServer.getURL())
        MultipartBody body = MultipartBody.builder()
                .addPart("operations", '{ "query": "mutation ($file: Upload!) { singleUpload(file: $file) { id } }", "variables": { "file": null } }')
                .addPart("map", '{ "0": ["variables.file"] }')
                .addPart("0", "a.txt", TEXT_PLAIN_TYPE, "Alpha file content.".bytes)
                .build()

        when:
        def response = httpClient.exchange(
                HttpRequest.POST("/graphql", body)
                        .contentType(MULTIPART_FORM_DATA_TYPE),
                String).blockingFirst()

        then:
        response != null
}

it works.

It seems the declarative client is not supporting a MultipartBody as argument.

Environment Information

  • Operating System: OSX
  • Micronaut Version: 1.2.0.RC2
  • JDK Version: 8
notabug

Most helpful comment

@Header(name = CONTENT_TYPE, value = MULTIPART_FORM_DATA) isn't how Micronaut determines what the content type is. We look for values on the @Produces and @Consumes annotations which are meta annotations of all of the http method annotations (@Put, etc).

        @Post(produces = MULTIPART_FORM_DATA)
        GraphQLResponseBody post(@Body MultipartBody body)

>All comments

@Header(name = CONTENT_TYPE, value = MULTIPART_FORM_DATA) isn't how Micronaut determines what the content type is. We look for values on the @Produces and @Consumes annotations which are meta annotations of all of the http method annotations (@Put, etc).

        @Post(produces = MULTIPART_FORM_DATA)
        GraphQLResponseBody post(@Body MultipartBody body)
Was this page helpful?
0 / 5 - 0 ratings