dgs-framework icon indicating copy to clipboard operation
dgs-framework copied to clipboard

bug: File upload doesn't work with Spring WebFlux

Open shiyouping opened this issue 3 years ago • 8 comments

Expected behavior

According to DGS document and GraphQL multipart request specification, file upload should work as expected.

Actual behavior

com.netflix.graphql.dgs.webflux.handlers.DefaultDgsWebfluxHttpHandler will treat form-data requests as application/json and use com.fasterxml.jackson.databind.ObjectMapper to parse the requests. Then the framework will throw the following exception:

Error has been observed at the following site(s):
	*_______________________________________Mono.map ⇢ at com.netflix.graphql.dgs.webflux.handlers.DefaultDgsWebfluxHttpHandler.graphql(DefaultDgsWebfluxHttpHandler.kt:35)
	|_                                  Mono.flatMap ⇢ at com.netflix.graphql.dgs.webflux.handlers.DefaultDgsWebfluxHttpHandler.graphql(DefaultDgsWebfluxHttpHandler.kt:48)
	|_                                  Mono.flatMap ⇢ at com.netflix.graphql.dgs.webflux.handlers.DefaultDgsWebfluxHttpHandler.graphql(DefaultDgsWebfluxHttpHandler.kt:61)
	*___________________________________Mono.flatMap ⇢ at org.springframework.cloud.sleuth.instrument.web.TraceHandlerFunction.handle(TraceHandlerFunction.java:54)
	|_                                Mono.doFinally ⇢ at org.springframework.cloud.sleuth.instrument.web.TraceHandlerFunction.handle(TraceHandlerFunction.java:54)
	|_                                      Mono.map ⇢ at org.springframework.web.reactive.function.server.support.HandlerFunctionAdapter.handle(HandlerFunctionAdapter.java:62)
	*___________________________________Mono.flatMap ⇢ at org.springframework.web.reactive.DispatcherHandler.handle(DispatcherHandler.java:153)
	|_                                  Mono.flatMap ⇢ at org.springframework.web.reactive.DispatcherHandler.handle(DispatcherHandler.java:154)
	*_____________________________________Mono.defer ⇢ at org.springframework.web.server.handler.DefaultWebFilterChain.filter(DefaultWebFilterChain.java:119)
	*______________________________________Mono.then ⇢ at org.springframework.security.web.server.authentication.AuthenticationWebFilter.onAuthenticationSuccess(AuthenticationWebFilter.java:135)
	*___________________________________Mono.flatMap ⇢ at org.springframework.security.web.server.authentication.AuthenticationWebFilter.authenticate(AuthenticationWebFilter.java:124)
	|_                                Mono.doOnError ⇢ at org.springframework.security.web.server.authentication.AuthenticationWebFilter.authenticate(AuthenticationWebFilter.java:126)
	*___________________________________Mono.flatMap ⇢ at org.springframework.security.web.server.authentication.AuthenticationWebFilter.filter(AuthenticationWebFilter.java:114)
	|_                            Mono.onErrorResume ⇢ at org.springframework.security.web.server.authentication.AuthenticationWebFilter.filter(AuthenticationWebFilter.java:115)
	|_                                    checkpoint ⇢ com.hohomalls.web.filter.AuthenticationFilter [DefaultWebFilterChain]
	*_____________________________________Mono.defer ⇢ at org.springframework.web.server.handler.DefaultWebFilterChain.filter(DefaultWebFilterChain.java:119)
	*_____________________________________Mono.defer ⇢ at org.springframework.web.server.handler.DefaultWebFilterChain.filter(DefaultWebFilterChain.java:119)
	*_____________________________Mono.switchIfEmpty ⇢ at org.springframework.security.web.server.authorization.AuthorizationWebFilter.filter(AuthorizationWebFilter.java:55)
	|_                                    checkpoint ⇢ org.springframework.security.web.server.authorization.AuthorizationWebFilter [DefaultWebFilterChain]
	*_____________________________________Mono.defer ⇢ at org.springframework.web.server.handler.DefaultWebFilterChain.filter(DefaultWebFilterChain.java:119)
	|_                            Mono.onErrorResume ⇢ at org.springframework.security.web.server.authorization.ExceptionTranslationWebFilter.filter(ExceptionTranslationWebFilter.java:58)
	|_                                    checkpoint ⇢ org.springframework.security.web.server.authorization.ExceptionTranslationWebFilter [DefaultWebFilterChain]
	*__Operators$MultiSubscriptionSubscriber.onError ⇢ at org.springframework.cloud.sleuth.instrument.reactor.ScopePassingSpanSubscriber.onError(ScopePassingSpanSubscriber.java:96)
	*_____________________________________Mono.defer ⇢ at org.springframework.web.server.handler.DefaultWebFilterChain.filter(DefaultWebFilterChain.java:119)
	*___________________________________Mono.flatMap ⇢ at org.springframework.security.web.server.savedrequest.ServerRequestCacheWebFilter.filter(ServerRequestCacheWebFilter.java:39)
	|_                                    checkpoint ⇢ org.springframework.security.web.server.savedrequest.ServerRequestCacheWebFilter [DefaultWebFilterChain]
	*_____________________________________Mono.defer ⇢ at org.springframework.web.server.handler.DefaultWebFilterChain.filter(DefaultWebFilterChain.java:119)
	|_                                    checkpoint ⇢ org.springframework.security.web.server.context.SecurityContextServerWebExchangeWebFilter [DefaultWebFilterChain]
	*_____________________________________Mono.defer ⇢ at org.springframework.web.server.handler.DefaultWebFilterChain.filter(DefaultWebFilterChain.java:119)
	*______________________________________Mono.then ⇢ at org.springframework.security.web.server.authentication.AuthenticationWebFilter.onAuthenticationSuccess(AuthenticationWebFilter.java:135)
	*___________________________________Mono.flatMap ⇢ at org.springframework.security.web.server.authentication.AuthenticationWebFilter.authenticate(AuthenticationWebFilter.java:124)
	|_                                Mono.doOnError ⇢ at org.springframework.security.web.server.authentication.AuthenticationWebFilter.authenticate(AuthenticationWebFilter.java:126)
	*___________________________________Mono.flatMap ⇢ at org.springframework.security.web.server.authentication.AuthenticationWebFilter.filter(AuthenticationWebFilter.java:114)
	|_                            Mono.onErrorResume ⇢ at org.springframework.security.web.server.authentication.AuthenticationWebFilter.filter(AuthenticationWebFilter.java:115)
	|_                                    checkpoint ⇢ com.hohomalls.web.filter.AuthenticationFilter [DefaultWebFilterChain]
	*_____________________________________Mono.defer ⇢ at org.springframework.web.server.handler.DefaultWebFilterChain.filter(DefaultWebFilterChain.java:119)
	|_                                    checkpoint ⇢ org.springframework.security.web.server.context.ReactorContextWebFilter [DefaultWebFilterChain]
	*_____________________________________Mono.defer ⇢ at org.springframework.web.server.handler.DefaultWebFilterChain.filter(DefaultWebFilterChain.java:119)
	|_                                    checkpoint ⇢ org.springframework.security.config.web.server.ServerHttpSecurity$ServerWebExchangeReactorContextWebFilter [DefaultWebFilterChain]
	*_____________________________________Mono.defer ⇢ at org.springframework.web.server.handler.DefaultWebFilterChain.filter(DefaultWebFilterChain.java:119)
	*_____________________________________Mono.defer ⇢ at org.springframework.web.server.handler.DefaultWebFilterChain.filter(DefaultWebFilterChain.java:119)
	*___________________________________Mono.flatMap ⇢ at org.springframework.security.web.server.WebFilterChainProxy.filter(WebFilterChainProxy.java:56)
	|_                                    checkpoint ⇢ org.springframework.security.web.server.WebFilterChainProxy [DefaultWebFilterChain]
	*_____________________________________Mono.defer ⇢ at org.springframework.web.server.handler.DefaultWebFilterChain.filter(DefaultWebFilterChain.java:119)
	|_                                    checkpoint ⇢ org.springframework.cloud.sleuth.instrument.web.TraceWebFilter [DefaultWebFilterChain]
	*_____________________________________Mono.defer ⇢ at org.springframework.web.server.handler.DefaultWebFilterChain.filter(DefaultWebFilterChain.java:119)
	|_                                 Mono.doOnEach ⇢ at org.springframework.boot.actuate.metrics.web.reactive.server.MetricsWebFilter.filter(MetricsWebFilter.java:87)
	|_                               Mono.doOnCancel ⇢ at org.springframework.boot.actuate.metrics.web.reactive.server.MetricsWebFilter.filter(MetricsWebFilter.java:88)
	*_________________________Mono.transformDeferred ⇢ at org.springframework.boot.actuate.metrics.web.reactive.server.MetricsWebFilter.filter(MetricsWebFilter.java:82)
	|_                                    checkpoint ⇢ org.springframework.boot.actuate.metrics.web.reactive.server.MetricsWebFilter [DefaultWebFilterChain]
	*_____________________________________Mono.defer ⇢ at org.springframework.web.server.handler.DefaultWebFilterChain.filter(DefaultWebFilterChain.java:119)
	|_                                    checkpoint ⇢ com.hohomalls.web.filter.CorsFilter [DefaultWebFilterChain]
	*_____________________________________Mono.defer ⇢ at org.springframework.web.server.handler.DefaultWebFilterChain.filter(DefaultWebFilterChain.java:119)
	|_                            Mono.onErrorResume ⇢ at org.springframework.web.server.handler.ExceptionHandlingWebHandler.handle(ExceptionHandlingWebHandler.java:77)
	*_____________________________________Mono.error ⇢ at org.springframework.web.server.handler.ExceptionHandlingWebHandler$CheckpointInsertingHandler.handle(ExceptionHandlingWebHandler.java:98)
	|_                                    checkpoint ⇢ HTTP POST "/graphql" [ExceptionHandlingWebHandler]
Original Stack Trace:
		at com.fasterxml.jackson.core.JsonParser._constructError(JsonParser.java:2391) ~[jackson-core-2.13.0.jar:2.13.0]
		at com.fasterxml.jackson.core.base.ParserMinimalBase._reportError(ParserMinimalBase.java:735) ~[jackson-core-2.13.0.jar:2.13.0]
		at com.fasterxml.jackson.core.base.ParserMinimalBase.reportUnexpectedNumberChar(ParserMinimalBase.java:557) ~[jackson-core-2.13.0.jar:2.13.0]
		at com.fasterxml.jackson.core.json.ReaderBasedJsonParser._handleInvalidNumberStart(ReaderBasedJsonParser.java:1718) ~[jackson-core-2.13.0.jar:2.13.0]
		at com.fasterxml.jackson.core.json.ReaderBasedJsonParser._parseNegNumber(ReaderBasedJsonParser.java:1467) ~[jackson-core-2.13.0.jar:2.13.0]
		at com.fasterxml.jackson.core.json.ReaderBasedJsonParser.nextToken(ReaderBasedJsonParser.java:784) ~[jackson-core-2.13.0.jar:2.13.0]
		at com.fasterxml.jackson.databind.ObjectMapper._initForReading(ObjectMapper.java:4762) ~[jackson-databind-2.13.0.jar:2.13.0]
		at com.fasterxml.jackson.databind.ObjectMapper._readMapAndClose(ObjectMapper.java:4668) ~[jackson-databind-2.13.0.jar:2.13.0]
		at com.fasterxml.jackson.databind.ObjectMapper.readValue(ObjectMapper.java:3630) ~[jackson-databind-2.13.0.jar:2.13.0]
		at com.fasterxml.jackson.databind.ObjectMapper.readValue(ObjectMapper.java:3613) ~[jackson-databind-2.13.0.jar:2.13.0]
		at com.netflix.graphql.dgs.webflux.handlers.DefaultDgsWebfluxHttpHandler.graphql$lambda-0(DefaultDgsWebfluxHttpHandler.kt:79) ~[graphql-dgs-spring-webflux-autoconfigure-4.9.14.jar:4.9.14]
		at reactor.core.publisher.FluxMapFuseable$MapFuseableSubscriber.onNext(FluxMapFuseable.java:113) ~[reactor-core-3.4.12.jar:3.4.12]
		at reactor.core.publisher.FluxOnErrorResume$ResumeSubscriber.onNext(FluxOnErrorResume.java:79) ~[reactor-core-3.4.12.jar:3.4.12]
		at reactor.core.publisher.FluxOnErrorResume$ResumeSubscriber.onNext(FluxOnErrorResume.java:79) ~[reactor-core-3.4.12.jar:3.4.12]
		at reactor.core.publisher.FluxMapFuseable$MapFuseableSubscriber.onNext(FluxMapFuseable.java:127) ~[reactor-core-3.4.12.jar:3.4.12]
		at reactor.core.publisher.FluxContextWrite$ContextWriteSubscriber.onNext(FluxContextWrite.java:107) ~[reactor-core-3.4.12.jar:3.4.12]
		at reactor.core.publisher.FluxMapFuseable$MapFuseableConditionalSubscriber.onNext(FluxMapFuseable.java:295) ~[reactor-core-3.4.12.jar:3.4.12]
		at reactor.core.publisher.FluxFilterFuseable$FilterFuseableConditionalSubscriber.onNext(FluxFilterFuseable.java:337) ~[reactor-core-3.4.12.jar:3.4.12]
		at reactor.core.publisher.Operators$MonoSubscriber.complete(Operators.java:1816) ~[reactor-core-3.4.12.jar:3.4.12]
		at reactor.core.publisher.MonoCollect$CollectSubscriber.onComplete(MonoCollect.java:159) ~[reactor-core-3.4.12.jar:3.4.12]
		at org.springframework.cloud.sleuth.instrument.reactor.ScopePassingSpanSubscriber.onComplete(ScopePassingSpanSubscriber.java:103) ~[spring-cloud-sleuth-instrumentation-3.1.0.jar:3.1.0]
		at reactor.core.publisher.FluxMapFuseable$MapFuseableSubscriber.onComplete(FluxMapFuseable.java:150) ~[reactor-core-3.4.12.jar:3.4.12]
		at org.springframework.cloud.sleuth.instrument.reactor.ScopePassingSpanSubscriber.onComplete(ScopePassingSpanSubscriber.java:103) ~[spring-cloud-sleuth-instrumentation-3.1.0.jar:3.1.0]
		at reactor.core.publisher.FluxPeekFuseable$PeekFuseableSubscriber.onComplete(FluxPeekFuseable.java:277) ~[reactor-core-3.4.12.jar:3.4.12]
		at org.springframework.cloud.sleuth.instrument.reactor.ScopePassingSpanSubscriber.onComplete(ScopePassingSpanSubscriber.java:103) ~[spring-cloud-sleuth-instrumentation-3.1.0.jar:3.1.0]
		at reactor.core.publisher.FluxMap$MapSubscriber.onComplete(FluxMap.java:142) ~[reactor-core-3.4.12.jar:3.4.12]
		at reactor.netty.channel.FluxReceive.onInboundComplete(FluxReceive.java:400) ~[reactor-netty-core-1.0.13.jar:1.0.13]
		at reactor.netty.channel.ChannelOperations.onInboundComplete(ChannelOperations.java:419) ~[reactor-netty-core-1.0.13.jar:1.0.13]
		at reactor.netty.http.server.HttpServerOperations.onInboundNext(HttpServerOperations.java:590) ~[reactor-netty-http-1.0.13.jar:1.0.13]
		at reactor.netty.channel.ChannelOperationsHandler.channelRead(ChannelOperationsHandler.java:93) ~[reactor-netty-core-1.0.13.jar:1.0.13]
		at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:379) ~[netty-transport-4.1.70.Final.jar:4.1.70.Final]
		at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:365) ~[netty-transport-4.1.70.Final.jar:4.1.70.Final]
		at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:357) ~[netty-transport-4.1.70.Final.jar:4.1.70.Final]
		at reactor.netty.http.server.HttpTrafficHandler.channelRead(HttpTrafficHandler.java:264) ~[reactor-netty-http-1.0.13.jar:1.0.13]
		at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:379) ~[netty-transport-4.1.70.Final.jar:4.1.70.Final]
		at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:365) ~[netty-transport-4.1.70.Final.jar:4.1.70.Final]
		at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:357) ~[netty-transport-4.1.70.Final.jar:4.1.70.Final]
		at io.netty.channel.CombinedChannelDuplexHandler$DelegatingChannelHandlerContext.fireChannelRead(CombinedChannelDuplexHandler.java:436) ~[netty-transport-4.1.70.Final.jar:4.1.70.Final]
		at io.netty.handler.codec.ByteToMessageDecoder.fireChannelRead(ByteToMessageDecoder.java:324) ~[netty-codec-4.1.70.Final.jar:4.1.70.Final]
		at io.netty.handler.codec.ByteToMessageDecoder.channelRead(ByteToMessageDecoder.java:296) ~[netty-codec-4.1.70.Final.jar:4.1.70.Final]
		at io.netty.channel.CombinedChannelDuplexHandler.channelRead(CombinedChannelDuplexHandler.java:251) ~[netty-transport-4.1.70.Final.jar:4.1.70.Final]
		at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:379) ~[netty-transport-4.1.70.Final.jar:4.1.70.Final]
		at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:365) ~[netty-transport-4.1.70.Final.jar:4.1.70.Final]
		at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:357) ~[netty-transport-4.1.70.Final.jar:4.1.70.Final]
		at io.netty.channel.DefaultChannelPipeline$HeadContext.channelRead(DefaultChannelPipeline.java:1410) ~[netty-transport-4.1.70.Final.jar:4.1.70.Final]
		at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:379) ~[netty-transport-4.1.70.Final.jar:4.1.70.Final]
		at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:365) ~[netty-transport-4.1.70.Final.jar:4.1.70.Final]
		at io.netty.channel.DefaultChannelPipeline.fireChannelRead(DefaultChannelPipeline.java:919) ~[netty-transport-4.1.70.Final.jar:4.1.70.Final]
		at io.netty.channel.nio.AbstractNioByteChannel$NioByteUnsafe.read(AbstractNioByteChannel.java:166) ~[netty-transport-4.1.70.Final.jar:4.1.70.Final]
		at io.netty.channel.nio.NioEventLoop.processSelectedKey(NioEventLoop.java:719) ~[netty-transport-4.1.70.Final.jar:4.1.70.Final]
		at io.netty.channel.nio.NioEventLoop.processSelectedKeysOptimized(NioEventLoop.java:655) ~[netty-transport-4.1.70.Final.jar:4.1.70.Final]
		at io.netty.channel.nio.NioEventLoop.processSelectedKeys(NioEventLoop.java:581) ~[netty-transport-4.1.70.Final.jar:4.1.70.Final]
		at io.netty.channel.nio.NioEventLoop.run(NioEventLoop.java:493) ~[netty-transport-4.1.70.Final.jar:4.1.70.Final]
		at io.netty.util.concurrent.SingleThreadEventExecutor$4.run(SingleThreadEventExecutor.java:986) ~[netty-common-4.1.70.Final.jar:4.1.70.Final]
		at io.netty.util.internal.ThreadExecutorMap$2.run(ThreadExecutorMap.java:74) ~[netty-common-4.1.70.Final.jar:4.1.70.Final]
		at io.netty.util.concurrent.FastThreadLocalRunnable.run(FastThreadLocalRunnable.java:30) ~[netty-common-4.1.70.Final.jar:4.1.70.Final]
		at java.base/java.lang.Thread.run(Thread.java:833) ~[na:na]

Steps to reproduce

I have used cURL, Postman and Altair GraphQL Client to test the file upload without luck.

scalar Upload

type Mutation {
    # Upload a file and return the URL on the server
    uploadFile(file: Upload!, rootDir: String!, subDir: String!): String
}
  @DgsData(parentType = "Mutation")
  public Mono<String> uploadFile(DataFetchingEnvironment env) {
    FileDataFetcher.log.info("Received a request to upload a file");
}
  • Use cURL to upload the file:
curl http://localhost:8080/graphql \
-F operations='{"query":"mutation ($file: Upload!) {\n  uploadFile(file: $file, rootDir: \"rootDir\", subDir: \"subDir\")\n}","variables":{"file":null},"operationName":null}' \
-F map='{ "0": ["variables.file"] }' \
-F [email protected]
  • Error responses in cURL, Postman and Altair GraphQL Client:
{
  "errors": [
    {
      "message": "Unexpected character ('-' (code 45)) in numeric value: expected digit (0-9) to follow minus sign, for valid numeric value\n at [Source: (String)\"------WebKitFormBoundaryq8A4KbH60pKaWgNR\r\nContent-Disposition: form-data; name=\"operations\"\r\n\r\n{\"query\":\"mutation ($file: Upload!) {\\n  uploadFile(file: $file, rootDir: \\\"rootDir\\\", subDir: \\\"subDir\\\")\\n}\",\"variables\":{\"file\":null},\"operationName\":null}\r\n------WebKitFormBoundaryq8A4KbH60pKaWgNR\r\nContent-Disposition: form-data; name=\"map\"\r\n\r\n{\"0\":[\"variables.file\"]}\r\n------WebKitFormBoundaryq8A4KbH60pKaWgNR\r\nContent-Disposition: form-data; name=\"0\"; filename=\"movie.txt\"\r\nContent-Type: text/plain\r\"[truncated 60 chars]; line: 1, column: 3]",
      "path": null,
      "locations": [
        "/graphql"
      ],
      "extensions": {
        "errorType": "INTERNAL",
        "origin": "APP",
        "debugInfo": {
          "exceptionId": "625c0be7-4a5f-4a92-b2fc-6f8d45447ad9",
          "exceptionName": "JsonParseException"
        }
      }
    }
  ],
  "status": 500
}

shiyouping avatar Jan 10 '22 14:01 shiyouping

Thanks for reporting the issue @shiyouping.

berngp avatar Jan 17 '22 20:01 berngp

Thanks, we do not support File uploads with webflux yet.

On Mon, Jan 17, 2022 at 12:11 PM Bernardo Gomez Palacio < @.***> wrote:

Thanks for reporting the issue @shiyouping https://github.com/shiyouping .

— Reply to this email directly, view it on GitHub https://github.com/Netflix/dgs-framework/issues/819#issuecomment-1014856346, or unsubscribe https://github.com/notifications/unsubscribe-auth/AJ5JPXLEEMMBET5AMWYPBDDUWRZXRANCNFSM5LTYPEUA . You are receiving this because you are subscribed to this thread.Message ID: @.***>

srinivasankavitha avatar Jan 19 '22 14:01 srinivasankavitha

Any schedule to support it? Thanks in advance. @srinivasankavitha @berngp

shiyouping avatar Jan 21 '22 08:01 shiyouping

We do not plan on adding support in the next few months at least, since WebFlux is not used internally. You are also the first to request this feature. We do welcome contributions from folks willing to help out with this.

On Fri, Jan 21, 2022 at 12:23 AM Shi Youping @.***> wrote:

Any schedule to support it? Thanks in advance.

— Reply to this email directly, view it on GitHub https://github.com/Netflix/dgs-framework/issues/819#issuecomment-1018284109, or unsubscribe https://github.com/notifications/unsubscribe-auth/AJ5JPXMT34YNS74RYUHZKOTUXEJZDANCNFSM5LTYPEUA . You are receiving this because you commented.Message ID: @.***>

srinivasankavitha avatar Jan 21 '22 17:01 srinivasankavitha

I am having the same issue Would be great if this is supported in the future Thanks

ghost avatar Mar 11 '22 13:03 ghost

Is there any way of doing a temporal fix or some hack in order to still use webflux but just use spring web mvc in the file uploads or I must have all the project with spring mvc? Because right now I am forced to use Base64 as inputs in my file uploads and this is not really well performant.

ghost avatar Apr 19 '22 16:04 ghost

@zanonena You can still use Spring Webflux to upload the files without dgs-framework. Here is a sample code https://github.com/shiyouping/hohomalls/blob/master/server/hohomalls-web/src/main/java/com/hohomalls/web/controller/FileController.java

shiyouping avatar Apr 19 '22 16:04 shiyouping

@zanonena You can still use Spring Webflux to upload the files without dgs-framework. Here is a sample code https://github.com/shiyouping/hohomalls/blob/master/server/hohomalls-web/src/main/java/com/hohomalls/web/controller/FileController.java

Thanks! but I must use GraphQL

ghost avatar Apr 20 '22 13:04 ghost

I must admit that DGS library is well designed and I have never had any problems using it in WebFlux mode. That is, until I had to implement file uploads. The lack of file uploads in WebFlux is not documented, so I was very disappointed when I found this issue open.

I need file uploads, so I came up with workaround. This is essentially custom HTTP handler bean with few classes operating on Part class instead of MultipartFile. I'm not familiar with Kotlin unfortunately, so I can't make a PR at the moment. For those who may find it handy I'm leaving a link to my Java hack.

It would be great if maintainers implemented file uploads in WebFlux flavor!

Pretty, pretty please with sugar on top :wink:

bartebor avatar Nov 04 '22 15:11 bartebor

@bartebor - Thanks for posting your solution for the workaround. Unfortunately, this feature is unlikely to be prioritized anytime soon due to internal conflicting requests. We do not use Webflux within Netflix yet. We are open to contributions though, so if anyone if interested, we would welcome the support!

srinivasankavitha avatar Nov 04 '22 16:11 srinivasankavitha

Any chance this will be added soon or has been added?

hwhh avatar Nov 09 '23 02:11 hwhh

Unfortunately this is not a priority for us since we do not use the webflux stack internally, and therefore unlikely we will support this in the future for webflux. We'll update our documentation to reflect this.

On Wed, Nov 8, 2023 at 6:29 PM hwhh @.***> wrote:

Any chance this will be added soon or has been added?

— Reply to this email directly, view it on GitHub https://github.com/Netflix/dgs-framework/issues/819#issuecomment-1803066427, or unsubscribe https://github.com/notifications/unsubscribe-auth/AJ5JPXLYDA7USO32EF56ZELYDQ5YPAVCNFSM5LTYPEUKU5DIOJSWCZC7NNSXTN2JONZXKZKDN5WW2ZLOOQ5TCOBQGMYDMNRUGI3Q . You are receiving this because you were mentioned.Message ID: @.***>

srinivasankavitha avatar Nov 09 '23 23:11 srinivasankavitha

Hello.

Let me first say that I really like the DGS framework. It's great to work with. However I'm disappointed to find out that you're not considering handling this. This seems like a must have for anyone who wants to use DGS and works with webflux (which is probably an increasing number of users).

☝🏼In any case it would be great if you could add this limitation to the file upload documentation because it's not obvious where the issue comes from at first and it's only once I included "multipart" in my search terms that I found this issue.

Although I do understand why Netflix chooses not to do this, I'm wondering about what this would actually cost from its point of view considering @bartebor seems to have provided some code that would serve as a base for this and that the dev teams working on this are probably used to the code and can do that in a timely fashion. Is the cost that great ? It feels that it would be of great value for the community at least. Considering you would still have to take the time to review the code of any new contributor and that this new contributor would have to go through the process of understanding how the DGS code works, isn't there any amount of value in doing this ?

If I knew where to start I might actually be that contributor but I'm not sure what I would be getting into and I can't afford that right now. Maybe in a couple weeks once I've set up a workaround and delivered a first version of my project. But we all know how that generally goes.

I won't hide it, I hope you change you mind on the subject. 😁

Thanks again for all the work you're doing. It's much appreciated. 🫶🏼

lthoulon-locala avatar Jan 23 '24 08:01 lthoulon-locala

As a side effect of some bigger news coming soon, this will actually be supported soon.

paulbakker avatar Jan 23 '24 16:01 paulbakker

Awesome. Meanwhile if anyone is looking for a Kotlin version of the hack initially provided by @bartebor You'll find it here: https://gist.github.com/lthoulon-locala/02efaf339d9f6b8795bc48f425926efe

I reworked this based on the original Netflix files so that the code is as close as possible to theirs.

lthoulon-locala avatar Jan 24 '24 10:01 lthoulon-locala