best way to tell swaggerui where the host is - swagger-ui

When I build my swagger.json file I do not know which host to use. However I can work it out when my page that hosts swaggerui loads (in fact I might want to offer the user a choice). I hoped to see an options.host on the config for the swaggerUI object - I dont see one. Is there an existing way of doing this that I cant find or do I simply have to hack my way through the code and add this capability (pointers to the best place to do it would be welcome)

Swagger has a built-in json definition for host config, or can accept multiple inputs.
{
"swagger": "2.0",
"info": {
"title": "Why API",
"description": "Don't make that mistake again",
"version": "0.0.1"
},
"host": "127.0.0.1:3000",
"schemes": [
"https"
]
}
Or
"host": "test.mydomain.com:3000",
"schemes": [
"https"
],
Or you can have a dynamic host by defining a var and calling a hostname or machine name or other environment variables.
dynamic example
if (typeof this.host === 'undefined' || this.host === '') {
this.host = location.host;
}
if (location.port) {
this.host = this.host + ':' + location.port;
}

Here is what I do, since the loaded in document is just a JSON object:
var swaggerDoc = require('./api/swagger.json');
if (process.env.NODE_ENV === 'development') {
swaggerDoc.host="localhost:" + process.env.PORT
}
// Initialize the Swagger middleware
swaggerTools.initializeMiddleware(swaggerDoc, function (middleware) {
// Other initialization
}
This way you don't pollute your API specification with development environment configuration.

In recent versions of Swagger UI it's possible to do this, for example in onComplete:
window.swaggerUi.api.setHost("your.host:4242");

If you are hosting it on same app server, just remove the host key from the json and provide relative path in key "basePath". as -
"basePath": "/rest/createcampaign".

two ways
One modify swagger.js so that it accepts host option. swagger-UI passes options to swagger-js so that works. I submitted a pull to swagger-js with this fix
Second choice is that swagger-UI accepts a 'spec' parameter. This means that the hosting page can load the swagger.json file, JSON.parse it , set 'host' in it and then pass to swaggerUi constructor. This is harder for the caller but doesn't require code changes to swagger

There are 2 ways which you can follow:
Load the index.html and replace the https://petstore.swagger.io/v2/swagger.json with the url where your swagger.json is hosting.
you can expose the local swagger.json on the same server.
When you follow this approach make sure you include static files in the end of above steps.
If you don't want to expose swagger.json as an API, copy the sawgger.json in the dist folder of swagger. The index.html and swagger.json must be in same repository for this. It is inside the index.html of dist folder of swagger-ui-dist.
const ui = SwaggerUIBundle({
spec: location.host,
url: "swagger.json",
dom_id: "#swagger-ui",
deepLinking: true,
presets: [SwaggerUIBundle.presets.apis, SwaggerUIStandalonePreset],
plugins: [SwaggerUIBundle.plugins.DownloadUrl],
layout: "StandaloneLayout"
});
// End Swagger UI call region
window.ui = ui;
};
Second way, host parameter in the swagger.yaml/swagger.json either make it empty
"host":""
or omit host parameter.
Swagger take the server's host as host where the swagger ui is hosted.

This is how I did this using the Java client:
DefaultApi api = new DefaultApi();
api.getApiClient().setBasePath("http://localhost:8080");
//call the API

if you use OpenApi 3.0
Variables can have arbitrary values, or may be restricted to an enum. In any case, a default value is required, which will be used if the client does not supply a value.
swagger doc
In the swagger-ui there will be the default value but the field is an input field so it is possible to customize it at runtime.

Swagger UI express itself is giving the following snippet it's getting the current host and publish dynamic with host
app.use('/api-docs', function(req, res, next){
swaggerDocument.host = req.get('host');
req.swaggerDoc = swaggerDocument;
next();
}, swaggerUi.serve, swaggerUi.setup());

Related

Changing swagger-ui server variable at runtime

Using an openapi v3 configuration I have a server variable called 'hostname' that is used to build the url, like:
...
servers:
- url: http://{hostname}/api
variables:
hostname:
"default": "some default here"
....
At runtime I'd like to be able to change the 'hostname' server variable. I've found the UI element on the page,
<input type="text" value="some default here" data-variable="hostname">
Changing the variable by editing the input field works fine, but changing the input field via jQuery isn't working, even when triggering the "change" event after setting the value, the value reverts when expanding one of the api sections. I also tried triggering the keyup/keydown and focusin/focusout events to better simulate how a user would change the field.
Is there a more swagger-ui approach to changing this value through an exposed call? I've looked through window.ui but its kind of cryptic.
I have an api.yaml file hosted on different IoT devices. Each device will have a different hostname based on its configuration. When the page is loaded I'm trying to use javascript to set the 'hostname' server variable to be window.location.hostname, for example via javascript.
You can simply specify a relative server url – it will be resolved relative to the location of the OpenAPI definition file.
For example, if you have
servers:
- url: /api
and the API definition file is at
http://foobar/spec/api.yaml
then the base url for API calls will be resolved to
http://foobar/api
You can alter the spec json before it render by writing a plugin
const ui = SwaggerUI({
// ...
plugins: [
// add a plugin to alter spec before it rendered
{
statePlugins: {
spec: {
wrapActions: {
updateJsonSpec: function(oriAction, system) {
return (spec) => {
// change spec.servers here to add new entry, use concat to put it as the first & default one
spec.servers = [{url: someDynamicUrlInRuntime}].concat(spec.servers || [])
return oriAction(spec)
}
}
}
}
}
}
]
// ...
})
Adapting the answer from nevermind for use with SwaggerUIBundle as used in the index.html from swagger-ui:
plugins: [
SwaggerUIBundle.plugins.DownloadUrl,
// Custom plugin that replaces the server list with the current url
function() {
return {
statePlugins: {
spec: {
wrapActions: {
updateJsonSpec: function(oriAction, system) {
return (spec) => {
spec.servers = [{url: window.location.origin}]
return oriAction(spec)
}
}
}
}
}
}
}
],
This way the index.html can be served from the backend service itself and will refer only on its own url.

Setting service worker to exclude certain urls only

I built an app using create react which by default includes a service worker. I want the app to be run anytime someone enters the given url except when they go to /blog/, which is serving a set of static content. I use react router in the app to catch different urls.
I have nginx setup to serve /blog/ and it works fine if someone visits /blog/ without visiting the react app first. However because the service worker has a scope of ./, anytime someone visits any url other than /blog/, the app loads the service worker. From that point on, the service worker bypasses a connection to the server and /blog/ loads the react app instead of the static contents.
Is there a way to have the service worker load on all urls except /blog/?
So, considering, you have not posted any code relevant to the service worker, you might consider adding a simple if conditional inside the code block for fetch
This code block should already be there inside your service worker.Just add the conditionals
self.addEventListener( 'fetch', function ( event ) {
if ( event.request.url.match( '^.*(\/blog\/).*$' ) ) {
return false;
}
// OR
if ( event.request.url.indexOf( '/blog/' ) !== -1 ) {
return false;
}
// **** rest of your service worker code ****
note you can either use the regex or the prototype method indexOf.
per your whim.
the above would direct your service worker, to just do nothing when the url matches /blog/
Another way to blacklist URLs, i.e., exclude them from being served from cache, when you're using Workbox can be achieved with workbox.routing.registerNavigationRoute:
workbox.routing.registerNavigationRoute("/index.html", {
blacklist: [/^\/api/,/^\/admin/],
});
The example above demonstrates this for a SPA where all routes are cached and mapped into index.html except for any URL starting with /api or /admin.
here's whats working for us in the latest CRA version:
// serviceWorker.js
window.addEventListener('load', () => {
if (isAdminRoute()) {
console.info('unregistering service worker for admin route')
unregister()
console.info('reloading')
window.location.reload()
return false
}
we exclude all routes under /admin from the server worker, since we are using a different app for our admin area. you can change it of course for anything you like, here's our function in the bottom of the file:
function isAdminRoute() {
return window.location.pathname.startsWith('/admin')
}
Here's how you do it in 2021:
import {NavigationRoute, registerRoute} from 'workbox-routing';
const navigationRoute = new NavigationRoute(handler, {
allowlist: [
new RegExp('/blog/'),
],
denylist: [
new RegExp('/blog/restricted/'),
],
});
registerRoute(navigationRoute);
How to Register a Navigation Route
If you are using or willing to use customize-cra, the solution is quite straight-forward.
Put this in your config-overrides.js:
const { adjustWorkbox, override } = require("customize-cra");
module.exports = override(
adjustWorkbox(wb =>
Object.assign(wb, {
navigateFallbackWhitelist: [
...(wb.navigateFallbackWhitelist || []),
/^\/blog(\/.*)?/,
],
})
)
);
Note that in the newest workbox documentation, the option is called navigateFallbackAllowlist instead of navigateFallbackWhitelist. So, depending on the version of CRA/workbox you use, you might need to change the option name.
The regexp /^/blog(/.*)?/ matches /blog, /blog/, /blog/abc123 etc.
Try using the sw-precache library to overwrite the current service-worker.js file that is running the cache strategy. The most important part is setting up the config file (i will paste the one I used with create-react-app below).
Install yarn sw-precache
Create and specify the config file which indicates which URLs to not cache
modify the build script command to make sure sw-precache runs and overwrites the default service-worker.js file in the build output directory
I named my config file sw-precache-config.js is and specified it in build script command in package.json. Contents of the file are below. The part to pay particular attention to is the runtimeCaching key/option.
"build": "NODE_ENV=development react-scripts build && sw-precache --config=sw-precache-config.js"
CONFIG FILE: sw-precache-config.js
module.exports = {
staticFileGlobs: [
'build/*.html',
'build/manifest.json',
'build/static/**/!(*map*)',
],
staticFileGlobsIgnorePatterns: [/\.map$/, /asset-manifest\.json$/],
swFilePath: './build/service-worker.js',
stripPrefix: 'build/',
runtimeCaching: [
{
urlPattern: /dont_cache_me1/,
handler: 'networkOnly'
}, {
urlPattern: /dont_cache_me2/,
handler: 'networkOnly'
}
]
}
Update (new working solution)
In the last major release of Create React App (version 4.x.x), you can easily implement your custom worker-service.js without bleeding. customize worker-service
Starting with Create React App 4, you have full control over customizing the logic in this service worker, by creating your own src/service-worker.js file, or customizing the one added by the cra-template-pwa (or cra-template-pwa-typescript) template. You can use additional modules from the Workbox project, add in a push notification library, or remove some of the default caching logic.
You have to upgrade your react script to version 4 if you are currently using older versions.
Working solution for CRA v4
Add the following code to the file service-worker.js inside the anonymous function in registerRoute-method.
// If this is a backend URL, skip
if (url.pathname.startsWith("/backend")) {
return false;
}
To simplify things, we can add an array list of items to exclude, and add a search into the fetch event listener.
Include and Exclude methods below for completeness.
var offlineInclude = [
'', // index.html
'sitecss.css',
'js/sitejs.js'
];
var offlineExclude = [
'/networkimages/bigimg.png', //exclude a file
'/networkimages/smallimg.png',
'/admin/' //exclude a directory
];
self.addEventListener("install", function(event) {
console.log('WORKER: install event in progress.');
event.waitUntil(
caches
.open(version + 'fundamentals')
.then(function(cache) {
return cache.addAll(offlineInclude);
})
.then(function() {
console.log('WORKER: install completed');
})
);
});
self.addEventListener("fetch", function(event) {
console.log('WORKER: fetch event in progress.');
if (event.request.method !== 'GET') {
console.log('WORKER: fetch event ignored.', event.request.method, event.request.url);
return;
}
for (let i = 0; i < offlineExclude.length; i++)
{
if (event.request.url.indexOf(offlineExclude[i]) !== -1)
{
console.log('WORKER: fetch event ignored. URL in exclude list.', event.request.url);
return false;
}
}

Change default API spec URL in Swagger UI

I'm using the Swagger Editor Docker image for defining my API specification and generate a nodejs server to run as stub api and serve the documentation for my API spec.
I would like to know if it is possible to change the default URL to my own in order to present my spec when I open the page, and how I would do that.
For instance, I would like to change the default address to http://localhost:8080/spec
If I understand correctly, you used Swagger Codegen to generate a Node.js server. Now when you npm start and open Swagger UI, you want the input box (the spec URL) to display http://localhost:8080/spec instead of http://localhost/api-docs.
To change the path the .yaml spec is served from, edit your index.js as follows:
// 1 - add swaggerUi options
var uiOptions = {
apiDocs: '/spec' // <-- override the default /api-docs
};
swaggerTools.initializeMiddleware(swaggerDoc, function (middleware) {
...
// 2 - pass these options to swaggerUi
app.use(middleware.swaggerUi(uiOptions));
The port number is specified by the serverPort variable in index.js:
var serverPort = 8080;

How to access swagger yaml defined objects from javascript

I used Swagger Yaml to describe an endpoint and generate the mock server. The existing endpoint (that I'm mocking) doesn't follow RESTful principles 100%, so I simply want to overwrite the response that is returned by the mock server. The simple server code is shown below:
var swagger = require('swagger-server');
var server = swagger('map-cache.yaml');
var port = 7072;
server.post('/map-qa_trunk/v2/getData', function(req, res, next) {
var foo = {
err : 123,
msg : "error message"
};
res.json(foo);
});
server.listen(port, function() {
console.log('Map Cache Mock Server is now running at http://localhost:' + port);
});
In the Yaml definition, there is an object defined called MapResponseData, how do I create an instance of this object so that I can populate it as needed and return in the res.json()? Something similar to below:
var response = getMapResponseData(); // don't know what this call should be
response.fieldA = 123;
res.json(response);
I am guessing this should be possible, since Swagger parsed the YAML file and is aware of all definitions that were specified.
Try outputting the request object to console.log to see if you can find reference to the swagger definition. Another option would be to pull the parsed swagger definition from the yaml file (using js-yaml for example) and extracting from there.
However, my best advice is to use swagger-tools instead of swagger-server. The swagger-server package is alpha version and has fewer downloads, revisions, and users than swagger-tools. Benefit of swagger-tools is that it will be actively maintained and there is a larger community that can support you. To convert your project to swagger-tools, use swagger.io > Swagger Editor > Online Editor > Paste yaml in left pane > Generate Server > Node.js
In swagger-tools the entire Swagger Yaml definition is contained in each request object:
req.swagger.swaggerObject
and you can pull the response object definitions from that as needed.

Can't read from file issue in Swagger UI

I have incorporated swagger-ui in my application.
When I try and see the swagger-ui I get the documentation of the API nicely but after some time it shows some error icon at the button.
The Error message is like below:
[{"level":"error","message":"Can't read from file
http://MYIP/swagger/docs/v1"}]
I am not sure what is causing it. If I refresh it works and shows error after few seconds.
I am guessing "http://MYIP/swagger/docs/v1" is not publicly accessible.
By default swagger ui uses an online validator: online.swagger.io. If it cannot access your swagger url then you will see that error message.
Possible solutions:
Disable validation:
config.EnableSwagger().EnableSwaggerUi(c => c.DisableValidator());
Make your site publicly accessible
Host the validator locally:
You can get the validator from: https://github.com/swagger-api/validator-badge#running-locally
You will also need to tell swaggerui the location of the validator
config.EnableSwagger().EnableSwaggerUi(c => c.SetValidatorUrl(<validator_url>));
To supplement the accepted answer...I just uncommented one line in the SwaggerConfig.cs. I only wanted to get rid of the red error on the main swagger page by disabling the validator.
// By default, swagger-ui will validate specs against swagger.io's online validator and display the result
// in a badge at the bottom of the page. Use these options to set a different validator URL or to disable the
// feature entirely.
//c.SetValidatorUrl("http://localhost/validator");
c.DisableValidator();
If you are using files from swagger-ui github repo, then you can disable schema validation from your index.html file by setting validatorUrl to null in it:
window.onload = function() {
// Build a system
const ui = SwaggerUIBundle({
url: "/docs/open_api.json",
dom_id: '#swagger-ui',
validatorUrl : null, # <----- Add this line
deepLinking: true,
presets: [
SwaggerUIBundle.presets.apis,
SwaggerUIStandalonePreset
],
plugins: [
SwaggerUIBundle.plugins.DownloadUrl
],
layout: "StandaloneLayout"
})
If you using PHP Laravel framework with L5-Swagger just uncomment
'validatorUrl' => null,
from the config file /config/l5-swagger.php
Setting this.model.validatorUrl = null; in dist/swagger-ui.js worked for me ..
// Default validator
if(window.location.protocol === 'https:') {
//this.model.validatorUrl = 'https://online.swagger.io/validator';
this.model.validatorUrl = null;
} else {
//this.model.validatorUrl = 'http://online.swagger.io/validator';
this.model.validatorUrl = null;
}
To anynoe having similar issue when using Swashbuckle.OData:
I was having issues to integrated Swagger with our OData endpoints (using ODataController for API and Swashbuckle.OData NuGet package). I had to write our own document filter for it and add it:
GlobalConfiguration.Configuration
.EnableSwagger(c =>
{
c.SingleApiVersion("v1", "OurSolution.API");
c.DocumentFilter<SwaggerDocumentFilter>();
//c.CustomProvider((defaultProvider) => new ODataSwaggerProvider(defaultProvider, c, GlobalConfiguration.Configuration));
c.IncludeXmlComments(GetXmlCommentsPath());
c.UseFullTypeNameInSchemaIds();
c.RootUrl(req => ConfigurationManager.AppSettings["AppUrl"]);
})
.EnableSwaggerUi(c =>
{
c.DisableValidator();
});
Apparently in order to avoid validation error I had to comment out line which is setting ODataSwaggerProvider along with turning off validator as mentioned in posts above. This makes usefulness of Swashbuckle.OData questionable yet I didn't test whatever it works with vanilla Swashbuckle.
Note: I used approach described on GitHub page for Swashbuckle.OData but it was not working: showing no possible endpoints at all. Maybe somebody knows better solution.

Resources