Skip to content
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

Multipart messages do not support setting content-type #636

Closed
guy4286 opened this issue Aug 1, 2014 · 10 comments
Closed

Multipart messages do not support setting content-type #636

guy4286 opened this issue Aug 1, 2014 · 10 comments

Comments

@guy4286
Copy link

guy4286 commented Aug 1, 2014

I have a multipart message that I am documenting with Swagger 1.3.4 for a file upload operation.

I've defined two parameters:

  1. file - the file to upload
  2. data - the JSON or XML message to describe metadata about the file we are uploading.

For the first parameter Swagger lets me select a file with the standard "Choose File" button.
For the second, I can type my message content, but I cannot select a content type for this part. When the message is sent, no content type is specified for this part.

This is a problem because I want to support both JSON and XML, but implementations will have no way of knowing how to parse the message.

Please see the screenshot for clarification.

swaggerbugreport

@webron
Copy link
Contributor

webron commented Aug 1, 2014

Can you show your method's signature (including the annotations)?

@guy4286
Copy link
Author

guy4286 commented Aug 19, 2014

Sorry for the delayed response. I was away on vacation for several days.

We do not use the swagger annotations - we custom build the JSON messages that swagger UI uses to render the parameters. Perhaps that means this bug should be filed with swagger-ui instead of swagger-core?

Here is the JSON we are giving to Swagger to render those parameters:

        {
            "path" : "/v1/files/upload",
            "operations" : [
                {
                    "method" : "POST",
                    "summary" : "Uploads a file at the specified location.",
                    "notes" : "Uploads a file at the specified location.",
                    "type" : "fileUploadResponse",
                    "nickname" : "filesUpload",
                    "produces" : [
                        "application/json",
                        "application/xml"
                    ],
                    "consumes" : [
                        "application/json",
                        "application/xml"
                    ],
                    "parameters" : [
                        {
                            "name" : "file",
                            "description" : "File to upload",
                            "required" : true,
                            "type" : "file",
                            "paramType" : "body",
                            "allowMultiple" : false
                        },
                        {
                            "name" : "data",
                            "description" : "Definition of fileUploadRequest. Use Content-Type to drive message format (application/json or application/xml) of this body part.",
                            "required" : true,
                            "type" : "fileUploadRequest",
                            "paramType" : "form",
                            "allowMultiple" : false
                        }
                    ],
                    "responseMessages" : [
                        {
                            "code" : 200,
                            "message" : "File uploaded successfuly.",
                            "responseModel" : ""
                        },
                        {
                            "code" : 400,
                            "message" : "The message payload is invalid.",
                            "responseModel" : ""
                        },
                        {
                            "code" : 401,
                            "message" : "You are not authorized to use this API. Please provide a valid user name and password before trying to use an API.",
                            "responseModel" : ""
                        },
                        {
                            "code" : 500,
                            "message" : "Internal Server Error",
                            "responseModel" : ""
                        }
                    ],
                    "deprecated" : ""
                }
            ]
        }

Notice the "file" and "data" parameters which match the screenshot.

@webron
Copy link
Contributor

webron commented Aug 19, 2014

Yeah, your specification file is not valid.

Please take a look at the file explanation - https://github.com/wordnik/swagger-spec/blob/master/versions/1.2.md#435-file. Notice the values for consumes and paramType.

@guy4286
Copy link
Author

guy4286 commented Aug 19, 2014

That hasn't quite fixed the problem yet, so I'm still wondering whether there is a bug.

I've changed our JSON generation so that it matches what you described:

{
            "path" : "/v1/files/upload",
            "operations" : [
                {
                    "method" : "POST",
                    "summary" : "Uploads a file at the specified location.",
                    "notes" : "Uploads a file at the specified location.",
                    "type" : "fileUploadResponse",
                    "nickname" : "filesUpload",
                    "produces" : [
                        "application/json",
                        "application/xml"
                    ],
                    "consumes" : [
                        "application/json",
                        "application/xml"
                    ],
                    "parameters" : [
                        {
                            "name" : "file",
                            "description" : "File to upload",
                            "required" : true,
                            "type" : "file",
                            "paramType" : "form",
                            "allowMultiple" : false
                        },
                        {
                            "name" : "data",
                            "description" : "Definition of fileUploadRequest. Use Content-Type to drive message format (application/json or application/xml) of this body part.",
                            "required" : true,
                            "type" : "fileUploadRequest",
                            "paramType" : "body",
                            "allowMultiple" : false
                        }
                    ],
                    "responseMessages" : [
                        {
                            "code" : 200,
                            "message" : "File uploaded successfuly.",
                            "responseModel" : ""
                        },
                        {
                            "code" : 400,
                            "message" : "The message payload is invalid.",
                            "responseModel" : ""
                        },
                        {
                            "code" : 401,
                            "message" : "You are not authorized to use this API. Please provide a valid user name and password before trying to use an API.",
                            "responseModel" : ""
                        },
                        {
                            "code" : 500,
                            "message" : "Internal Server Error",
                            "responseModel" : ""
                        }
                    ],
                    "deprecated" : ""
                }
            ]
        }

However, when I click "Try It Out" Swagger sends a message which seems invalid:

------WebKitFormBoundaryOK78BBaHlU0capbP
Content-Disposition: form-data; name="file"
C:\fakepath\fetch.xml
------WebKitFormBoundaryOK78BBaHlU0capbP
Content-Disposition: form-data; name="file"; filename="fetch.xml"
Content-Type: text/xml
------WebKitFormBoundaryOK78BBaHlU0capbP--

The JSON message we are trying to send as part of this message is missing.

@fehguy
Copy link
Contributor

fehguy commented Aug 19, 2014

The problem is that file uploads need to be sent with content-type multipart/form-data. What you're seeing in the debug log is correct.

To fix this, your server needs to consume mulitpart/form-data for this request, and your consumes should represent that as well:

"consumes": [
  "multipart/form-data"
]

@guy4286
Copy link
Author

guy4286 commented Aug 20, 2014

Yes, I had tried that as webron had also suggested. The problem is that once I do that, I can no longer select the content type of the "data" parameter. It gives me only the "multipart/form-data" option.

See the screenshot:
image

Then when the request gets to the server, it wouldn't know how to process the data parameter (which could be either JSON or XML). It doesn't look like Swagger is sending the data parameter payload anyway - using the multipart/form-data consumes, this is the message that swagger sends (notice that there is no JSON sent by Swagger even though I specified it in the UI):

------WebKitFormBoundaryVm5la0DOFwOEEesi
Content-Disposition: form-data; name="file"
C:\fakepath\readme.txt
------WebKitFormBoundaryVm5la0DOFwOEEesi
Content-Disposition: form-data; name="file"; filename="readme.txt"
Content-Type: text/plain
------WebKitFormBoundaryVm5la0DOFwOEEesi--

Just so you can see what I did, here is the JSON that Swagger is generating the UI from with the consumes value you had recommended:

        {
            "path" : "/v1/files/upload",
            "operations" : [
                {
                    "method" : "POST",
                    "summary" : "Uploads a file at the specified location.",
                    "notes" : "Uploads a file at the specified location.",
                    "type" : "fileUploadResponse",
                    "nickname" : "filesUpload",
                    "produces" : [
                        "application/json",
                        "application/xml"
                    ],
                    "consumes" : [
                        "multipart/form-data"
                    ],
                    "parameters" : [
                        {
                            "name" : "file",
                            "description" : "File to upload",
                            "required" : true,
                            "type" : "file",
                            "paramType" : "form",
                            "allowMultiple" : false
                        },
                        {
                            "name" : "data",
                            "description" : "Definition of fileUploadRequest. Use Content-Type to drive message format (application/json or application/xml) of this body part.",
                            "required" : true,
                            "type" : "fileUploadRequest",
                            "paramType" : "body",
                            "allowMultiple" : false
                        }
                    ],
                    "responseMessages" : [
                        {
                            "code" : 200,
                            "message" : "File uploaded successfuly.",
                            "responseModel" : ""
                        },
                        {
                            "code" : 400,
                            "message" : "The message payload is invalid.",
                            "responseModel" : ""
                        },
                        {
                            "code" : 401,
                            "message" : "You are not authorized to use this API. Please provide a valid user name and password before trying to use an API.",
                            "responseModel" : ""
                        },
                        {
                            "code" : 500,
                            "message" : "Internal Server Error",
                            "responseModel" : ""
                        }
                    ],
                    "deprecated" : ""
                }
            ]
        }

So that's why I had changed the consumes to be application/json, application/xml, then at least I get a UI which looks like I expect where I can select the correct content type for the data parameter:

image

So, it still seems like Swagger isn't behaving as expected here.

@fehguy
Copy link
Contributor

fehguy commented Aug 20, 2014

No, I don't know how to say this except, you cannot send a file with anything other that multipart/form-data with the UI, period. It will switch to that content type when you try to pass a file.

@fehguy fehguy closed this as completed Aug 28, 2014
@thorntech
Copy link

I totally agree with @guy4286 that you should be able to specify a content type on the parts other than the file. This is critical for Java applications that need the content type of the part in order to know how to deserialize the json or xml into a Java object.

For what its worth, I was able to get this working by modifying swagger-ui,js to use a Blob as the FormData. I had to add a "contentType" field to the Parameter object. I know this violates the spec, but this worked for my project.

swagger-ui.js line 1797:

if (param.paramType === 'form') {
      if (param.type.toLowerCase() !== 'file' && map[param.name] !== void 0) {
        if(typeof param.contentType !== 'undefined') {
            bodyParam.append(param.name, new Blob([map[param.name]], { type: param.contentType}));
        }
        else {
            bodyParam.append(param.name, map[param.name]);
        }
      }
    }

In the swagger doc, I can define the file and the json object parameters like so:

        {
         "name": "story",
         "description": "The story object in json format",
         "required": true,
         "paramType": "form",
         "type": "Story",
         "contentType": "application/json"
        },
        {
         "name": "file",
         "description": "The base64 encoded multipart file attachment. ",            
         "required": true,
         "paramType": "form",
         "type": "File"
        }

@webron
Copy link
Contributor

webron commented Dec 2, 2014

@thorntech - technically speaking, they are all files, just the content type differs per file.

That said, it may be an interesting adjustment to the Swagger Spec - allowing you to declare the required content type(s) for a file type. In Swagger 2.0, you can overcome it for now using vendor extensions, but feel free to open an issue about it on the Swagger Spec repo so we can consider this change for a future version. The tooling support for it (including the ui) should come after it is part of the spec.

@thorntech
Copy link

A new issue has been created here - OAI/OpenAPI-Specification#222

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

4 participants