Swagger-ui: OPTIONS method made for POST / PUT methods

Created on 3 Aug 2012  ยท  17Comments  ยท  Source: swagger-api/swagger-ui

Hi there,

I'm facing a strange problem I never had before with swagger: for all my POST/PUT method, swagger is making first an OPTIONS method, and then a POST/PUT one if Allowed.

I'm not really sure I want to implement OPTIONS method, which is extremely rare.

How to avoid this?

Thanks!

Most helpful comment

2 years later... and I re-open this thread. Believe me I have been searching for days with no luck. God forgive me...

I don't believe a relevant or accurate answer has been posted to this. it is now 2 years later and I still don't see a clear explanation of how to handle an incoming Ajax Preflight OPTIONS request. This is NOT browser specific. Yes disabling disabling CORS in Chrome removes the problem... but I can't believe you are suggesting that I ask all of my users to disable CORS. More importantly this issue surfaces when no browser is involved. Using Postman I can make calls all day long to the API directly. As soon as I try to make a cal via Ajax in an HTML page an exception is thrown. Here is the scenario...

Client: ANY
Web Server: Apache on Amazon Linux AMI using jQuery 1.6.2
Content: Simple html page with form using ajax to make a GET, or POST, call to my API
API Server: Amazon Linux AMI running Node Swagger Express (updated to current)
Behavior:
Browse to HTML page
Click Submit Button
Exception is printed to console

Exception:

Error: Path [/ping] defined in Swagger, but OPTIONS operation is not.
    at middleware (/var/node/api/node_modules/swagger-express-mw/node_modules/swagger-node-runner/lib/connect_middleware.js:31:21)
    at Layer.handle [as handle_request] (/var/node/api/node_modules/express/lib/router/layer.js:82:5)
    at trim_prefix (/var/node/api/node_modules/express/lib/router/index.js:302:13)
    at /var/node/api/node_modules/express/lib/router/index.js:270:7
    at Function.proto.process_params (/var/node/api/node_modules/express/lib/router/index.js:321:12)
    at next (/var/node/api/node_modules/express/lib/router/index.js:261:10)
    at /var/node/api/app.js:38:8
    at Layer.handle [as handle_request] (/var/node/api/node_modules/express/lib/router/layer.js:82:5)
    at trim_prefix (/var/node/api/node_modules/express/lib/router/index.js:302:13)
    at /var/node/api/node_modules/express/lib/router/index.js:270:7

Headers (as returned by Postman):

Access-Control-Allow-Credentials โ†’ true
Access-Control-Allow-Headers โ†’ X-Requested-With,content-type
Access-Control-Allow-Methods โ†’ GET, POST, OPTIONS, PUT, PATCH, DELETE
Access-Control-Allow-Origin โ†’ *
Connection โ†’ keep-alive
Content-Length โ†’ 17
Content-Type โ†’ application/json; charset=utf-8
Date โ†’ Tue, 01 Dec 2015 08:09:03 GMT
ETag โ†’ W/"11-df5e0e4c"
X-Powered-By โ†’ Express

Note: No response is returned by API due to exception.

Web Page Code:

<!DOCTYPE html>
<html>
<head>
    <meta name="author" content="Rusty Chapin">
    <meta name="description" content="Sample execution of API to create new contact">
    <title>Create New Contact</title>
    <meta name="viewport" content="width=device-width, minimum-scale=1.0, maximum-scale=1.0">
    <script src="http://ajax.aspnetcdn.com/ajax/jQuery/jquery-1.6.2.min.js"></script>
    <script>
        $(document).ready(function(){
           var getResults = function(){
                var contactemail = $('#contactemail').val();
                var contactname = $('#contactname').val();

                $.ajax({
                    type:"POST",
                    url: "http://xxx.xxx.xxx.xxx:10010/contact",
                    dataType: 'json',
                    contentType: 'application/json',
                    data: { contactemail: contactemail, contactname: contactname },
                    success: function(response){
                        $('#results').html('<h2 class="loading">Contact ID: ' + 
                                response[0].contactid + '<br>Contact Email: ' + 
                                response[0].contactemail + '<br>Contact Name: ' + 
                                response[0].contactname + '<h2 />');
                    },
                    error: function(response){
                        $('#results').html('<h2 class="loading">Response: ' + 
                                response + '<h2 />');
                    }
                });
           };

           $('#submit').click(getResults);
        });

   </script>
</head>
<body>
<div class="container">
    <header>
        <h1>Create New Contact</h1>
    </header>
    <form>
        <input type="email" placeholder="Email Address" id="contactemail" />
        <input type="text" placeholder="Name" id="contactname" />
        <button id="submit">Submit</button>
    </form>
    <section id="results">
        <br>
    </section>
    <footer>
       Created for use with...
    </footer>
</div>
</body>
</html>

API Code:
app.js

'use strict';

var SwaggerExpress = require('swagger-express-mw');
var SwaggerUi = require('swagger-tools/middleware/swagger-ui');
var app = require('express')();
module.exports = app; // for testing

var config = {
  appRoot: __dirname // required config
};

SwaggerExpress.create(config, function(err, swaggerExpress) {
    if (err) { throw err; }

    // Add swagger-ui (This must be before swaggerExpress.register)
    app.use(SwaggerUi(swaggerExpress.runner.swagger));

    // Add headers
    app.use(function (req, res, next) {

       // Website you wish to allow to connect
       res.setHeader('Access-Control-Allow-Origin', '*');

       // Request methods you wish to allow
       res.setHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS, PUT, PATCH, DELETE');

       // Request headers you wish to allow
       res.setHeader('Access-Control-Allow-Headers', 'X-Requested-With,content-type');

       // Website you wish to allow to connect
       res.setHeader('Content-Type', 'application/json');

       // Pass to next layer of middleware
       next();
    });

    // install middleware
    swaggerExpress.register(app);

    var port = process.env.PORT || 10010;

    app.listen(port);

    if (swaggerExpress.runner.swagger.paths['/ping']) {
      console.log('running at: http://127.0.0.1:' + port);
      console.log('Ping test: http://127.0.0.1:' + port + '/ping');
    }
});

ping.js

'use strict';

var util = require('util');

module.exports = {
  ping: ping
};

function ping(req, res) {
  // this sends back a JSON response which is a single string
  return res.json({ result: 'pong' });
}

swagger.yaml

swagger: "2.0"
info:
  version: "0.0.1"
  title: My API
# during dev, should point to your local machine
host: xxx.xxx.xxx.xxx:10010
# basePath prefixes all resource paths 
basePath: /
# 
schemes:
  # tip: remove http to make production-grade
  - http
  - https
# format of bodies a client can send (Content-Type)
consumes:
  - application/json
# format of the responses to the client (Accepts)
produces:
  - application/json
paths:
  /contact:
    x-swagger-router-controller: contact
    post:
      description: Creates a new contact
      operationId: create
      parameters:
        - name: contactemail
          in: query
          description: Email address of new contact
          required: true
          type: string
        - name: contactname
          in: query
          description: Name of new contact
          required: false
          type: string
      responses:
        "200":
          description: Success
          schema:
            $ref: "#/definitions/CreateContactSuccess"
        default:
          description: Error
          schema:
            $ref: "#/definitions/ErrorResponse"
  /ping:
    # binds a127 app logic to a route
    x-swagger-router-controller: ping
    get:
      description: Returns 'pong' to the caller
      # used as the method name of the controller
      operationId: ping
      responses:
        "200":
          description: Success
          schema:
            # a pointer to a definition
            $ref: "#/definitions/PingResponse"
        # responses may fall through to errors
        default:
          description: Error
          schema:
            $ref: "#/definitions/ErrorResponse"
  /swagger:
    x-swagger-pipe: swagger_raw
# complex objects have schema definitions
definitions:
  CreateContactSuccess:
    required:
      - contactid
      - contactemail
      - contactname
    properties:
      contactid:
        type: string
      contactemail:
        type: string
      contactname:
        type: string
  PingResponse:
    required:
      - message
    properties:
      message:
        type: string
  ErrorResponse:
    required:
      - message
    properties:
      message:
        type: string

All 17 comments

howdy, I believe this is new to the latest chrome browser. I don't know how to avoid this when using POST/PUT--as I recall, firefox doesn't do this.

Are you indeed using chrome?

Indeed, I'm on chrome.

But it does the same on Firefox. I think it is related to Same Origin Policy protection. My APIs enable CORS and JSON-P calls, but it seems Swagger does not support that, since you are using it on the same domain as your API.

I'll try to have a look this week end, if I have time. Maybe you already have been confronted to this case, and have an idea on how to solve it.

Thanks a lot for your support ;)

howdy, did you end up figuring this out?

I might have a clearer idea on what's going on:

The problem is definitely coming from the Same Origin Policy. It seems that since jQuery 1.5+ it is not possible anymore to make other domains ajax POST requests (see my stackoverflow question here http://stackoverflow.com/questions/11803613/doing-cross-domain-request-with-ajax-and-jquery-1-7-2). It works like a charm for GET queries, and if I downgrade jQuery to 1.4 to have ajax POST working, that's swagger that don't work anymore :)

So at te moment, I'm implementing all on the same domain and try to have a solution later..

I'm not sure if this is real issue. The OPTIONS request is the standard CORS preflight. You either host swagger-ui on the same server. If not, your server needs to send a Access-Control-Allow-Origin header and respond to OPTIONS for FF/Safari/Chrome. For IE, even that does not work.

@guillaumepotier please make sure you are returning the supported OPTIONs in your API-- you can see examples here:

nodejs:

https://github.com/wordnik/swagger-node-express/blob/master/Common/node/swagger.js#L31-33

scala:

https://github.com/wordnik/swagger-core/blob/master/samples/scala-jaxrs/src/main/scala/com/wordnik/swagger/sample/util/ApiOriginFilter.scala#L29-31

please reopen if you have an issue with this.

Sorry to reopen this issue. The last example is a little confusing. WIth chrome the preflight is called for any cross domain call. So basically anyone implementing my api makes an extra call. Currently my node instance is making a best guess and returning the swagger UI index page.

Do you recommend we attach a 'option' call for each put and post call? Is there an approach that would allow for a global option handler? What should we send back in the option response? It does not appear that the preflight cares what it gets as long as it gets a valid endpoint.

Any swagger best practices would be great.

Any suggested workarounds? Adding an OPTION call for each put and post call seems redundant.

Thanks.

This thread (http://stackoverflow.com/a/18947262/1216965) was relevant for me.

I had to start chrome while disabling cross site scripting (http://stackoverflow.com/a/12927204/1216965) if I was loading the index.html directly from the file system. However, if swagger-ui is hosted on a http web server, this should not be a problem.

@neowulf33 can you see here, for more info on CORS:

https://github.com/wordnik/swagger-core/wiki/CORS

Thanks @fehguy, I did have CORS enabled on the documented service which enabled me to host the swagger-ui on one server and the documented service on another service.

However, when testing on the local machine, and especially when loading the swagger-ui using the "filesystem://" and interrogating the documented service running on port 8080; it seems like CORS won't be honored by Chrome.

Hence, when testing on the local machine, I found that I needed to start Chrome while disabling cross site scripting to resolve this issue.

Sounds like you have it covered then. Please reopen if not.

2 years later... and I re-open this thread. Believe me I have been searching for days with no luck. God forgive me...

I don't believe a relevant or accurate answer has been posted to this. it is now 2 years later and I still don't see a clear explanation of how to handle an incoming Ajax Preflight OPTIONS request. This is NOT browser specific. Yes disabling disabling CORS in Chrome removes the problem... but I can't believe you are suggesting that I ask all of my users to disable CORS. More importantly this issue surfaces when no browser is involved. Using Postman I can make calls all day long to the API directly. As soon as I try to make a cal via Ajax in an HTML page an exception is thrown. Here is the scenario...

Client: ANY
Web Server: Apache on Amazon Linux AMI using jQuery 1.6.2
Content: Simple html page with form using ajax to make a GET, or POST, call to my API
API Server: Amazon Linux AMI running Node Swagger Express (updated to current)
Behavior:
Browse to HTML page
Click Submit Button
Exception is printed to console

Exception:

Error: Path [/ping] defined in Swagger, but OPTIONS operation is not.
    at middleware (/var/node/api/node_modules/swagger-express-mw/node_modules/swagger-node-runner/lib/connect_middleware.js:31:21)
    at Layer.handle [as handle_request] (/var/node/api/node_modules/express/lib/router/layer.js:82:5)
    at trim_prefix (/var/node/api/node_modules/express/lib/router/index.js:302:13)
    at /var/node/api/node_modules/express/lib/router/index.js:270:7
    at Function.proto.process_params (/var/node/api/node_modules/express/lib/router/index.js:321:12)
    at next (/var/node/api/node_modules/express/lib/router/index.js:261:10)
    at /var/node/api/app.js:38:8
    at Layer.handle [as handle_request] (/var/node/api/node_modules/express/lib/router/layer.js:82:5)
    at trim_prefix (/var/node/api/node_modules/express/lib/router/index.js:302:13)
    at /var/node/api/node_modules/express/lib/router/index.js:270:7

Headers (as returned by Postman):

Access-Control-Allow-Credentials โ†’ true
Access-Control-Allow-Headers โ†’ X-Requested-With,content-type
Access-Control-Allow-Methods โ†’ GET, POST, OPTIONS, PUT, PATCH, DELETE
Access-Control-Allow-Origin โ†’ *
Connection โ†’ keep-alive
Content-Length โ†’ 17
Content-Type โ†’ application/json; charset=utf-8
Date โ†’ Tue, 01 Dec 2015 08:09:03 GMT
ETag โ†’ W/"11-df5e0e4c"
X-Powered-By โ†’ Express

Note: No response is returned by API due to exception.

Web Page Code:

<!DOCTYPE html>
<html>
<head>
    <meta name="author" content="Rusty Chapin">
    <meta name="description" content="Sample execution of API to create new contact">
    <title>Create New Contact</title>
    <meta name="viewport" content="width=device-width, minimum-scale=1.0, maximum-scale=1.0">
    <script src="http://ajax.aspnetcdn.com/ajax/jQuery/jquery-1.6.2.min.js"></script>
    <script>
        $(document).ready(function(){
           var getResults = function(){
                var contactemail = $('#contactemail').val();
                var contactname = $('#contactname').val();

                $.ajax({
                    type:"POST",
                    url: "http://xxx.xxx.xxx.xxx:10010/contact",
                    dataType: 'json',
                    contentType: 'application/json',
                    data: { contactemail: contactemail, contactname: contactname },
                    success: function(response){
                        $('#results').html('<h2 class="loading">Contact ID: ' + 
                                response[0].contactid + '<br>Contact Email: ' + 
                                response[0].contactemail + '<br>Contact Name: ' + 
                                response[0].contactname + '<h2 />');
                    },
                    error: function(response){
                        $('#results').html('<h2 class="loading">Response: ' + 
                                response + '<h2 />');
                    }
                });
           };

           $('#submit').click(getResults);
        });

   </script>
</head>
<body>
<div class="container">
    <header>
        <h1>Create New Contact</h1>
    </header>
    <form>
        <input type="email" placeholder="Email Address" id="contactemail" />
        <input type="text" placeholder="Name" id="contactname" />
        <button id="submit">Submit</button>
    </form>
    <section id="results">
        <br>
    </section>
    <footer>
       Created for use with...
    </footer>
</div>
</body>
</html>

API Code:
app.js

'use strict';

var SwaggerExpress = require('swagger-express-mw');
var SwaggerUi = require('swagger-tools/middleware/swagger-ui');
var app = require('express')();
module.exports = app; // for testing

var config = {
  appRoot: __dirname // required config
};

SwaggerExpress.create(config, function(err, swaggerExpress) {
    if (err) { throw err; }

    // Add swagger-ui (This must be before swaggerExpress.register)
    app.use(SwaggerUi(swaggerExpress.runner.swagger));

    // Add headers
    app.use(function (req, res, next) {

       // Website you wish to allow to connect
       res.setHeader('Access-Control-Allow-Origin', '*');

       // Request methods you wish to allow
       res.setHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS, PUT, PATCH, DELETE');

       // Request headers you wish to allow
       res.setHeader('Access-Control-Allow-Headers', 'X-Requested-With,content-type');

       // Website you wish to allow to connect
       res.setHeader('Content-Type', 'application/json');

       // Pass to next layer of middleware
       next();
    });

    // install middleware
    swaggerExpress.register(app);

    var port = process.env.PORT || 10010;

    app.listen(port);

    if (swaggerExpress.runner.swagger.paths['/ping']) {
      console.log('running at: http://127.0.0.1:' + port);
      console.log('Ping test: http://127.0.0.1:' + port + '/ping');
    }
});

ping.js

'use strict';

var util = require('util');

module.exports = {
  ping: ping
};

function ping(req, res) {
  // this sends back a JSON response which is a single string
  return res.json({ result: 'pong' });
}

swagger.yaml

swagger: "2.0"
info:
  version: "0.0.1"
  title: My API
# during dev, should point to your local machine
host: xxx.xxx.xxx.xxx:10010
# basePath prefixes all resource paths 
basePath: /
# 
schemes:
  # tip: remove http to make production-grade
  - http
  - https
# format of bodies a client can send (Content-Type)
consumes:
  - application/json
# format of the responses to the client (Accepts)
produces:
  - application/json
paths:
  /contact:
    x-swagger-router-controller: contact
    post:
      description: Creates a new contact
      operationId: create
      parameters:
        - name: contactemail
          in: query
          description: Email address of new contact
          required: true
          type: string
        - name: contactname
          in: query
          description: Name of new contact
          required: false
          type: string
      responses:
        "200":
          description: Success
          schema:
            $ref: "#/definitions/CreateContactSuccess"
        default:
          description: Error
          schema:
            $ref: "#/definitions/ErrorResponse"
  /ping:
    # binds a127 app logic to a route
    x-swagger-router-controller: ping
    get:
      description: Returns 'pong' to the caller
      # used as the method name of the controller
      operationId: ping
      responses:
        "200":
          description: Success
          schema:
            # a pointer to a definition
            $ref: "#/definitions/PingResponse"
        # responses may fall through to errors
        default:
          description: Error
          schema:
            $ref: "#/definitions/ErrorResponse"
  /swagger:
    x-swagger-pipe: swagger_raw
# complex objects have schema definitions
definitions:
  CreateContactSuccess:
    required:
      - contactid
      - contactemail
      - contactname
    properties:
      contactid:
        type: string
      contactemail:
        type: string
      contactname:
        type: string
  PingResponse:
    required:
      - message
    properties:
      message:
        type: string
  ErrorResponse:
    required:
      - message
    properties:
      message:
        type: string

@rustychapin I also encountered this issue.

It can be easily solved by adding a middleware before the swagger middleware(s) that checks the HTTP method being called, and responds with an empty response if appropriate as per this flowchart: http://www.html5rocks.com/static/images/cors_server_flowchart.png

This package looks promising: https://www.npmjs.com/package/cors

However, it is something I would have expected to be handled automatically.

Not sure if it is connected but cors module https://www.npmjs.com/package/cors should be activated before the swagger middleware if it is activated afterwards then you need to implement it on controllers and services.

well, I got it finally, I was accessing swagger docs via 127.0.0.1 but in app.js I use localhost. change 1 of them and it will work.

maybe it helps someone.

As far as I understand, Swagger editor should send the POST requests after evaluating the response of the OPTIONS request.

But it seems not to do so.

I try to develop a Slim based server - i.e. to use the swagger-generate produced slim server.

I have added php code from https://github.com/swagger-api/swagger-ui/issues/1209#issuecomment-333789474 to send proper response to the OPTIONS request.

I also have upated .htacces accordingly.

I can see these changes in the response header of the OPTION request:

Access-Control-Allow-Headers: API-Key,accept, Content-Type
Access-Control-Allow-Methods: POST, GET, DELETE, PUT, PATCH, OPTIONS
Access-Control-Allow-Origin: *
Access-Control-Max-Age: 1728000
Connection: Keep-Alive
Content-Length: 0
Content-Type: text/html; charset=UTF-8
Date: Wed, 29 May 2019 13:08:46 GMT
Keep-Alive: timeout=5, max=100
Server: Apache/2.4.25 (Debian)
X-Powered-By: PHP/7.3.3

But I still do not see swagger UI sending the POST request.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

hkosova picture hkosova  ยท  40Comments

alexmnyc picture alexmnyc  ยท  40Comments

newtechfellas picture newtechfellas  ยท  47Comments

azamatsulaiman picture azamatsulaiman  ยท  34Comments

grosch picture grosch  ยท  75Comments