Skip to content

@ApiParam of type InputStream needs special handling #1531

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
BigSocial opened this issue Nov 2, 2015 · 14 comments
Closed

@ApiParam of type InputStream needs special handling #1531

BigSocial opened this issue Nov 2, 2015 · 14 comments

Comments

@BigSocial
Copy link

This Java/Jersey code...

@POST
@Path("/bin")
@Consumes({MediaType.APPLICATION_OCTET_STREAM})
@Produces({MediaType.APPLICATION_JSON})
@ApiOperation(
        value = "Create a new Source from an uploaded file. This version expects the file as the raw contents of the request.",
        response = Source.class
        )
public Response makeSourceRaw(
        @ApiParam(value = "A file", required = true) InputStream inputStream
        )

...works perfectly (i.e. it allows access to the unencoded body) but currently generates this Swagger 2.0...

parameters: [
    {
        in: "body",
        name: "body",
        description: "A file",
        required: true,
        schema: {
             $ref: "#/definitions/InputStream"
        }
    }
]

The result is Swagger UI generates the unhelpful...

image

...and swagger-codegen generates a Java client that treats InputStream as if it were part of my model (i.e. creates a io.swagger.client.model.InputStream class), not an important concept in Java/Jersey.

Is there any way right now to make Swagger (core, UI, codegen, etc.) understand that an InputStream param should be treated as a "raw" file upload? If not, consider this a issue.

Also see my original comment on Sep 27 2015 here OAI/OpenAPI-Specification#326

@fehguy
Copy link
Contributor

fehguy commented Nov 3, 2015

What are your dependencies? If you're not using the swagger-jersey-jaxrs module, I'd expect to see this behavior, which is indeed not helpful. But in the module, you'll see that a FormDataParam with type InputStream should convert to the file type.

https://github.com/swagger-api/swagger-core/blob/master/modules/swagger-jersey-jaxrs/src/main/java/io/swagger/jersey/SwaggerJerseyJaxrs.java

@BigSocial
Copy link
Author

@fehguy Thanks for the quick reply. We're using swagger-jersey2-jaxrs 1.5.4. I don't believe the FormDataParam is meaningful/significant in this case. The is not a form based POST, it is a POST with the file as the raw bytes of the request body. We have a form based POST which works fine (see that code below), but also need the raw POST to work with core, UI and codegen.

@POST
@Path("/form")
@Consumes(MediaType.MULTIPART_FORM_DATA)
@Produces({ MediaType.APPLICATION_JSON})
public Response makeSource(
        @ApiParam(value = "The file", required = true) @FormDataParam(value="file") InputStream inputStream,
        @ApiParam(hidden=true) @FormDataParam(value="file") FormDataContentDisposition fileDisposition
        )

@fehguy
Copy link
Contributor

fehguy commented Nov 3, 2015

OK got it. That's different then--and you're bringing up a different use case than the file type was intended to support.

The file type is exclusively an multipart/form-data POST operation. If you want to send a file this way, it's the way to go, because it allows the client to tell how long the body is so the server can support streaming, etc.

You can always send BASE64-encoded strings to the server by sending a JSON body with a content structure like such:

{
  "contents": "VGhpcyBpcyBhbiBhbWF6aW5nIGZpbGUgY29udGVudCB0aGF0IHJlYWxseSByZWFsbHkgaXMgYXdlc29tZS4="
}

This would still be content-type:application/json but the swagger-ui is not going to support this out of the box. You can easily hack it in, but since there's no standard way to send the data, there's no standard way to support it in the UI.

@BigSocial
Copy link
Author

@fehguy We understand it's different, but it's also the way every major REST API that takes file uploads works. That includes S3, OpenStack, Box, DropBox, etc. all of which take the file as the raw body of the request either through a PUT or POST. So it's in no way a fringe use case. It's the most efficient way to get a file local from the client (which in many cases will not be a browser) up to a server because no encoding is required, and it's directly supported by most HTTP client libraries.

We understand Swagger UI upload support might be an issue because the browser may not allow that type of direct upload of a file selected by the user (we haven't tried this), but at least Swagger UI can document it correctly.

@fehguy
Copy link
Contributor

fehguy commented Nov 3, 2015

Sure, that's fair, but if you're looking for support, help with more details. Specifically:

  • What content-type would you expect the API to describe? I'm guessing it's application/json but if my memory serves correctly, some of the ones you listed are using octet-stream
  • If the server does represent InputStream, it could be using a vendor extension for the property to state that it's base64-encoded. Or are you expecting a different encoding?
  • It is possible that the UI represents this using an extension, which is perfectly legal from the spec point of view but it may be tricky if the content-type is json

@BigSocial
Copy link
Author

No problem. The details are this. The scenario is a binary file (think Word document, or PDF) sent as the body of the HTTP POST request with Content-Type = application/octet-stream and no encoding (in which case the client code will set Content-Length) or a Transfer-Encoding negotiated between the client code and the server (like chunked, ZIP, etc.). No JSON, no base64, etc. In this case, the InputStream on the server (i.e. in the makeSourceRaw method in the first comment) contains the raw bytes of the file.

I refer you to the W3C HTTP spec where an Entity Body is defined as entity-body = *OCTET and message-body = entity-body (when there is no Transfer-Encoding) AKA the message body of a HTTP request can be plain old binary data.

I think maybe we're not quite on the same page. Am I missing something here?

@webron
Copy link
Contributor

webron commented Nov 3, 2015

@fehguy - I believe @BigSocial is looking for the solution as described at OAI/OpenAPI-Specification#50 (comment).

@HenrikHL
Copy link

HenrikHL commented Nov 4, 2015

I have a similar problem with @ApiParam and InputStream. My endPoints look very much like the ones above - only difference is that my InputStream is not a file. I use Jackson (from fasterxml) to transform (map) the InputStream into an Object (in the following example it maps into a Comparison.class). Here is an example:

@PUT
@ApiOperation(value="Puts...",
        notes = "Updates...",
        response = Comparison.class,
        responseContainer = "List"
)
@Consumes( MediaType.APPLICATION_JSON )
@Produces( MediaType.APPLICATION_JSON )
public Response putComparisons(@ApiParam(value="The Comparison(s) to update", required = true) InputStream inputStream) {
    //Transform the inputStream into a Comparison.class and update the database
    return ...;
}

This produces the same Swagger documentation as listed in the exmaple provided by @BigSocial - the body parameter is treated like an InputStream (which of course it is)... For documentation purposes I would like to be able to define what the InputStream actually is (in my case a Comparison.class)

Since we are using an old version of JBoss (which still uses the codehaus version of Jackson) the mapping cannot be done prior to the endPoint call - because the codehaus mapping is done wrong.
Ideally the above code would look like this:

@PUT
@ApiOperation(value="Puts...",
        notes = "Updates...",
        response = Comparison.class,
        responseContainer = "List"
)
@Consumes( MediaType.APPLICATION_JSON )
@Produces( MediaType.APPLICATION_JSON )
public Response putComparisons(@ApiParam(value="The Comparison(s) to update", required = true) Comparison comparison) {
    //Update the database with the Comparison
    return ...;
}

Notice - InputStream has been replaced with Comparison

What I would like is a "datatype" parameter on the @ApiParam:

@ApiParam(value="...", datatype="Comparison.class", ...)

In this way the documentation produced by Swagger would be more correct. The "datatype" parameter would always superseed the actual Parameter class

@evigeant
Copy link

evigeant commented Dec 1, 2015

I have a very similar need where I need to both upload and download files in my API and the files are encrypted/decrypted therefore I cannot send Java File objects, I need to work with InputStream. Further, I'm trying to generate the client code and server skeleton from the swagger definition of the API.

I think the spec needs a way to describe a binary object that shouldn't be handled in memory. In java the corresponding type would be java.io.InputStream.

I propose this:

Data Types

Common Name type format Comments
string string
byte string byte base64 encoded characters
binary string binary any sequence of octets
blob string blob a long sequence of octets (treated as a stream)

@webron
Copy link
Contributor

webron commented Dec 1, 2015

Not sure the spec should deal with how things are handled but rather what they are. To me, in your case, binary and blob are the same. If you want to have something that's implementation-specific, that's where the extensions come in.

@jmdacruz
Copy link

What about multiple representations using multipart/related or multipart/mixed? This is useful to upload a binary file which also has metadata, or to upload different representations of the same resource.

@HaloFour
Copy link

HaloFour commented Aug 5, 2016

I have a situation similar to that of @HenrikHL . I am accepting a relatively large blob of JSON containing an array of entities. I want to be able to accept the request body as an InputStream in Jersey so that I can manually stream through the JSON array and deserialize the elements one-by-one rather than incur the cost of deserialization all up front but I don't see a good way to also have Swagger show that the request body type is the array of typed objects.

@RadicalQuiver
Copy link

RadicalQuiver commented Dec 23, 2016

I too have the same problem as @HenrikHL and @HaloFour . I was looking for the same solution as @HenrikHL as well. he ability to override the datatype via @ApiParam.dataType=Foo.class

It would also help solve some problems we have where the resource method needs to consume a concrete class, whereas the actual Resource is best described by an interface.

I recognize that this is not the same use case as the original one, so would we be better off creating a separate issue for this use case ?

@fehguy
Copy link
Contributor

fehguy commented Dec 25, 2016

I believe @webron has covered the current spec behavior in his comments. For 3.0, that will change.

@fehguy fehguy closed this as completed Dec 25, 2016
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

8 participants