1

So I've been on this for two days and am not savvy enough in Java to fully realize an approach to solving this issue. On the client-side I've found some excellent user documentation and believe I have the proper pieces in order but when it comes to handling inbound multipart/formdata in the server, I am a bit lost.

The foundation for my application is based upon the HowToGraphQL Java tutorial as I feel they did a great job organizing their code base and make things very simple and understandable. Unfortunately file uploading is not an area they cover, and per a maintainer of the graphql-java package, no plans exist to directly support this feature.

However this is not to say this is not possible. The GraphQL specification was meant to be a guideline and how the community chooses to invoke the specs is up to them. A number of people have been successful in incorporating file uploading, albeit often through the inclusion of custom middleware

The roadblock for me is likely just lack of domain knowledge unfortunately. I work mostly with JavaScript and Python and mostly have academic experience with Java. Not entirely sure where to begin but I imagine I will need to track down the response object which I think is tied into the SimpleGraphQLServlet class and find a way to extract inbound file objects.

Would appreciate some more expert advice or help in coming up with a solution. I haven't found any solid Java based snippets on SO to address this so solving this would likely benefit others attempting to adopt this platform.

Edit:

Possibly found a means to do this, but still haven't worked out the specifics. In the GraphQLEndpoint class which extends the SimpleGraphQLServlet class we expose the doPost() method. Extracting the data however ends up resulting in a series of errors, the first being resolved with a @MultipartConfig annotation, the next error however leaves me once again stalled. "Missing content for MultiPart request."

This doPost() method seems to be called after the mutation occurs, so even if I manage to get the file data extracted, it seems that the data will be in the wrong place. Additionally I wonder if the missing content is the result of the Mutation consuming the post data, assuming it even arrived at all.

All other parameters arrive intact however, so it is simply a matter of accessing this File.

Scot Matson
  • 745
  • 6
  • 23

2 Answers2

1

The Java tutorial on HowToGraphQL is sadly woefully outdated. I'm working on updating it (I'm the author of the original), but it takes time. The current best practice is to directly use graphql-java's RuntimeWiring when working in schema-first manner*. There's some basic docs here: http://graphql-java.readthedocs.io/en/v6/schema.html

Regardless, the way to handle file uploads has not changed: stick the entire HttpServletRequestobject, or just the binary part, into the shared context:

graphQL.execute(ExecutionInput.newExecutionInput()
    .query(operation)
    .context(request.getPart("file")) //add the binary to the context
    .build());

Later on, in the DataFetcher, you can get a hold of it via: DataFetchingEnvironment#getContext:

Part file = environment.getContext();
file.getInputStream(); //do something with the binary content

You could, of course, use some other (dynamic) structure as the context, instead of the binary directly.

This side-loading way of handling uploads is a result of using JSON as the transport format, and JSON is textual. Same reason why file downloads are normally handled outside of GraphQL (where the response only contains and ID or a URL needed to initiate the download). The reference server implementation includes examples using the same strategy.
At the moment, there's no non-JSON GraphQL clients, as far as I know.

*For code-first, graphql-spqr (my project) remains the most convenient way.

kaqqao
  • 12,984
  • 10
  • 64
  • 118
  • Oh, hey @ kaqqao! Thanks for such a thorough response. I actually had reached out to you via Twitter about this as I was hoping to come up with a solution that fit well with the foundation you had already set up. Thanks for putting that tutorial together, it may be outdated but it still works very well with only a few minor tweaks. – Scot Matson Jan 19 '18 at 17:57
  • 1
    @ScotMatson Glad to be of service :) Sorry if I missed your message on Twitter :/ Wasn't intentional. And as for the file upload case, there's really no better way... graphql-java-tools I think allows you to inject the shared context object into the method, so the same strategy should be usable. – kaqqao Jan 20 '18 at 20:30
0

For the time being, the solution that is working for me unfortunately does not exactly follow what I would consider best practices, using uploadables, sending as multipart/form-data and then injecting middleware on the server. However I was able to transfer file data so this is a good enough start.

Ultimately I chose to encode my uploaded file as base64 and then send the data as a String type through a GraphQL mutation and then upon reception I was able to decode the base64 string.

Now what I'm seeing so far is only the file contents being transferred, not the metadata. Since some of the metadata is of value to me this will either need to be sent alongside the base64 string or perhaps I just have overlooked this element and it does exist.

Either way I'll leave a few resources I found useful while solving this and if anyone else runs into this problem it may be found useful. I don't see my current solution as the real answer here so perhaps in time as these packages evolve we will see better approaches to how files can be uploaded via Relay/graphql-java.

How to convert file to base64 in JavaScript?

Decode Base64 data in Java

Scot Matson
  • 745
  • 6
  • 23