swagger-js
swagger-js copied to clipboard
No data (and no content-type) sent with post request, but swagger-ui works for the same spec
The synopsis is that when I use swagger-client (aka swagger-js) to post an object to a route that accepts JSON, it seems to be sending no content and hence no content-type. The server (.NET Core 3.1) then responds with 415 unsupported media type, however I think this is a side effect of the actual cause.
- If I change the spec so that the endpoint accepts the data as query parameters instead of POST data in the body, it works.
- If I use swagger-ui with the same spec, it posts data fine using either method (body or query)
Q&A (please complete the following information)
- OS: Windows Server 2012R2 and Windows Server 2019 Std
- Environment: Chrome Version 102.0.5005.115 (Official Build) (64-bit) and Version 103.0.5060.66 (Official Build) (64-bit) respectively
- Method of installation: manual, e.g. unpkg using the path https://unpkg.com/swagger-client which resolves to https://unpkg.com/[email protected]/dist/swagger-client.browser.min.js
- Swagger-Client version: 3.18.5
- Swagger/OpenAPI version: OpenAPI 3.0.1
Content & configuration
The spec is medium sized so I hope this is enough:
Endpoint definition:
{
"openapi": "3.0.1",
"info": {
"title": "Cust Pure Ice API Development",
"version": "v1"
},
// ... big snip
"/api/Trip/search": {
"post": {
"tags": [
"Trip"
],
"operationId": "TripInformationSearch",
"requestBody": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Cust.API.Models.Trip.TripInformationSearchRequest"
}
},
"text/json": {
"schema": {
"$ref": "#/components/schemas/Cust.API.Models.Trip.TripInformationSearchRequest"
}
},
"application/*+json": {
"schema": {
"$ref": "#/components/schemas/Cust.API.Models.Trip.TripInformationSearchRequest"
}
}
}
},
"responses": {
"200": {
"description": "Success",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Cust.API.Models.Trip.TripInformationSearchResponse"
}
}
}
},
"400": {
"description": "Bad Request",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Microsoft.AspNetCore.Mvc.ProblemDetails"
}
}
}
}
}
}
}
The TripInformationSearchRequest $ref:
"Cust.API.Models.Trip.TripInformationSearchRequest": {
"type": "object",
"properties": {
"tripIds": {
"type": "array",
"items": {
"type": "integer",
"format": "int64"
},
"nullable": true
},
"tripNumbers": {
"type": "array",
"items": {
"type": "integer",
"format": "int64"
},
"nullable": true
},
"keywords": {
"type": "string",
"nullable": true
},
"status": {
"$ref": "#/components/schemas/Cust.API.Models.Trip.TripStatus"
},
"routeType": {
"$ref": "#/components/schemas/Cust.API.Models.Trip.RouteType"
},
"hasGLPostDate": {
"type": "boolean",
"nullable": true
}
},
"additionalProperties": false
},
Swagger-Client usage:
(This is an app I'm maintaining, so I wouldn't exactly design it this way but I can't rewrite the whole thing now): A button event triggers the search function search_trips which is defined along with swagger-client in a giant object:
CUSTAPI = {
error_display(message) {
alert(message);
},
swagger_client: function (urlBase) {
var specUrl = urlBase + '/swagger/v1/swagger.json';
SwaggerClient.http.withCredentials = true; // this activates CORS, if necessary
var swaggerClient = new SwaggerClient(specUrl);
return swaggerClient;
},
// ... snip
search_trips: function (keywords, tripNumbersArr, status, routeType, hasGLPostDate, successCallback) {
var failedSwaggerLoadSpecCallback = function (reason) { CUSTAPI.error_display("Unable to connect to the API: " + reason); };
var failedApiRequestCallback = function (reason) { CUSTAPI.error_display("Unable to fulfill the API request: " + reason); };
CUSTAPI.swagger_client(oldApiClient_UrlBase)
.then(
function (swaggerClient) {
var searchRequest = {
keywords: keywords,
tripNumbers: tripNumbersArr, //array
status: status,
routeType: routeType,
hasGLPostDate: hasGLPostDate
};
return swaggerClient.apis.Trip.TripInformationSearch({ request: searchRequest }); // chaining promises
}, failedSwaggerLoadSpecCallback)
.then(function (response) {
if (response.ok)
successCallback(response.obj);
//else
// return response
}, failedApiRequestCallback);
},
Describe the bug you're encountering
When I call swaggerClient.apis.Trip.TripInformationSearch, the following request headers are sent:
POST /api/Trip/search HTTP/1.1
Accept-Encoding: gzip, deflate, br
Accept-Language: en-US,en;q=0.9
Connection: keep-alive
Content-Length: 0
Host: localhost:44393
Origin: https://localhost:44316
Referer: https://localhost:44316/
Sec-Fetch-Dest: empty
Sec-Fetch-Mode: cors
Sec-Fetch-Site: same-site
User-Agent: Mozilla/5.0 (Windows NT 6.3; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/102.0.0.0 Safari/537.36
accept: application/json
sec-ch-ua: " Not A;Brand";v="99", "Chromium";v="102", "Google Chrome";v="102"
sec-ch-ua-mobile: ?0
sec-ch-ua-platform: "Windows"
However, when I use the swagger-ui, the following request headers are sent:
POST /api/Trip/search HTTP/1.1
Accept-Encoding: gzip, deflate, br
Accept-Language: en-US,en;q=0.9
Connection: keep-alive
Content-Length: 159
Content-Type: application/json
Host: localhost:44393
Origin: https://localhost:44393
Referer: https://localhost:44393/swagger/index.html
Sec-Fetch-Dest: empty
Sec-Fetch-Mode: cors
Sec-Fetch-Site: same-origin
User-Agent: Mozilla/5.0 (Windows NT 6.3; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/102.0.0.0 Safari/537.36
accept: application/json
sec-ch-ua: " Not A;Brand";v="99", "Chromium";v="102", "Google Chrome";v="102"
sec-ch-ua-mobile: ?0
sec-ch-ua-platform: "Windows"
As you can see, swagger-ui sends 159 bytes of content, and swagger-client sends 0 bytes!
If I change the route definition to use query parameters instead of body, the spec changes to:
"/api/Trip/search": {
"post": {
"tags": [
"Trip"
],
"operationId": "TripInformationSearch",
"parameters": [
{
"name": "TripIds",
"in": "query",
"schema": {
"type": "array",
"items": {
"type": "integer",
"format": "int64"
}
}
},
{
"name": "TripNumbers",
"in": "query",
"schema": {
"type": "array",
"items": {
"type": "integer",
"format": "int64"
}
}
},
{
"name": "Keywords",
"in": "query",
"schema": {
"type": "string"
}
},
{
"name": "Status",
"in": "query",
"schema": {
"$ref": "#/components/schemas/Cust.API.Models.Trip.TripStatus"
}
},
{
"name": "RouteType",
"in": "query",
"schema": {
"$ref": "#/components/schemas/Cust.API.Models.Trip.RouteType"
}
},
{
"name": "HasGLPostDate",
"in": "query",
"schema": {
"type": "boolean"
}
}
],
"responses": {
"200": {
"description": "Success",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Cust.API.Models.Trip.TripInformationSearchResponse"
}
}
}
},
"400": {
"description": "Bad Request",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Microsoft.AspNetCore.Mvc.ProblemDetails"
}
}
}
}
}
}
}
},
and in this case, even though the body is 0 bytes and the content-type header is not there, the data is sent to the endpoint without changing the way of calling swaggerClient.apis.Trip.TripInformationSearch({ request: searchRequest });
Expected behavior
I would expect that a payload, content-length and content-type is sent with the post. I understand why content-type is missing with no payload, so I think the issue is just that the payload is not included for some reason.
Additional context or thoughts
.NET Core 3.1 routing is expected to return 415 if the content-type doesn't match the route. However I don't think this is the problem, I think this is just the side-effect of posting a zero-byte payload. As can be seen by using swagger-ui, when the payload is there, then the endpoint works.
I changed unpkg to the route https://unpkg.com/[email protected]/dist/swagger-client.browser.js and went debugging into swagger client source... It's hard to see what's going on exactly with exec but the general flow looks like this:
TripInformationSearch(...)function makeExecute()(line 3351)executefunction (line 24418) calls Swagger.executefunction execute(_ref)(line 985) - this is where the request is built- line 1003
var request = self.buildRequest(...)returns a request object with no body, not sure if this is the issue? function buildRequest(options)(line 1019) goes through the parameters, deduplicates them, and iterates over them. I note on line 1122 incombinedParameters.forEachthere is the testif (parameter.in === 'body' ...however, my parameters don't have an "in" spec, only a schema, and nothing ends up in the combinedParameters array. I'm losing my way in the code now, not sure if I'm following red herrings or not. combindParameters is made from theoperation.parameters, which doesn't exist in this case:
but you can see that there is requestBody.content.['application/json'].schema.properties:
Any advice would be appreciated! Thanks
I've tried random ideas from stackoverflow such as setting [Consumes("application/json")] on the endpoint, being explicit with [FromBody] etc. but to no avail. Since my original post I've found some other routes doing the same thing.
Hi @thinkOfaNumber,
This looks like a compatibility issue with browser fetch and node-fetch. Can you please create the most simplest OpenAPI fixture possible and minimal JavaScript code that's using SwaggerClient which demonstrates this bug in Node.js?
Thanks you!
I'm closing this for now as there's been no follow up from the @thinkOfaNumber.