I am currently migrating our API docs (which were Swagger 1.5) to Swagger 2.0 (OpenApi 3.0)
The API docs are Swagger docs which get generated with java annotations using maven packages swagger-annotations and swagger-jaxrs. I have already updated the pom.xml with new versions so it looks like:
<dependency>
<groupId>io.swagger.core.v3</groupId>
<artifactId>swagger-annotations</artifactId>
<version>2.0.6</version>
</dependency>
<dependency>
<groupId>io.swagger.core.v3</groupId>
<artifactId>swagger-jaxrs2</artifactId>
<version>2.0.6</version>
</dependency>
And also all the old annotations are replaced with the new ones (which change quite a lot) and looks fine.
The thing is we were using a BeanConfig to define the docs general config and auto-scan all the REST resources so the documentation got generated automatically at /swagger.json.
The problem is I can't find the "new way" of doing such thing as creating a BeanConfig and auto-scan the resources so everything gets generated at /swagger.json or /openapi.json (maybe now is something like OpenAPIDefinition?)
If somebody could point me to the right direction I would be very grateful...
After some research, I could find some documentation about it in their Github for JAX-RS application, so the result is something similar to what I was doing but now instead of using a BeanConfig, it uses OpenAPI and Info:
#ApplicationPath("/sample")
public class MyApplication extends Application {
public MyApplication(#Context ServletConfig servletConfig) {
super();
OpenAPI oas = new OpenAPI();
Info info = new Info()
.title("Swagger Sample App bootstrap code")
.description("This is a sample server Petstore server. You can find out more about Swagger " +
"at [http://swagger.io](http://swagger.io) or on [irc.freenode.net, #swagger](http://swagger.io/irc/). For this sample, " +
"you can use the api key `special-key` to test the authorization filters.")
.termsOfService("http://swagger.io/terms/")
.contact(new Contact()
.email("apiteam#swagger.io"))
.license(new License()
.name("Apache 2.0")
.url("http://www.apache.org/licenses/LICENSE-2.0.html"));
oas.info(info);
SwaggerConfiguration oasConfig = new SwaggerConfiguration()
.openAPI(oas)
.prettyPrint(true)
.resourcePackages(Stream.of("io.swagger.sample.resource").collect(Collectors.toSet()));
try {
new JaxrsOpenApiContextBuilder()
.servletConfig(servletConfig)
.application(this)
.openApiConfiguration(oasConfig)
.buildContext(true);
} catch (OpenApiConfigurationException e) {
throw new RuntimeException(e.getMessage(), e);
}
}
}
Though OP has answered their own question, but adding a few more details for people like me who landed on this post as I wanted to migrate from swagger 1.x to swagger 2.0 (openAPI 3) and needed complete config.
(This example is for embedded jetty)
// Jetty configuration
// ContextHandlerCollection contexts
ServletContextHandler context = new ServletContextHandler(ServletContextHandler.SESSIONS);
context.setContextPath("/api");
context.addFilter(GzipFilter.class, "/*", EnumSet.allOf(DispatcherType.class));
ResourceConfig resourceConfig = new ResourceConfig(ImmutableSet.<Class<?>>builder()
.add(MyRestService.class)
.build());
resourceConfig.registerClasses(OpenApiResource.class,AcceptHeaderOpenApiResource.class); // for swagger, this will cerate openapi.json at <host>/api/openapi.json
context.addServlet(new ServletHolder(new ServletContainer(resourceConfig)), "/*");
contexts.addHandler(context);
If you need to change default swagger config, that can be done by what OP has described in their answer:
OpenAPI oas = new OpenAPI();
Info info = new Info()
.title("Swagger Sample App bootstrap code")
.description("This is a sample server Petstore server. You can find out more about Swagger " +
"at [http://swagger.io](http://swagger.io) or on [irc.freenode.net, #swagger](http://swagger.io/irc/). For this sample, " +
"you can use the api key `special-key` to test the authorization filters.")
.termsOfService("http://swagger.io/terms/")
.contact(new Contact()
.email("apiteam#swagger.io"))
.license(new License()
.name("Apache 2.0")
.url("http://www.apache.org/licenses/LICENSE-2.0.html"));
oas.info(info);
SwaggerConfiguration oasConfig = new SwaggerConfiguration()
.openAPI(oas)
.prettyPrint(true)
.resourcePackages(Stream.of("io.swagger.sample.resource").collect(Collectors.toSet()));
try {
new JaxrsOpenApiContextBuilder()
.servletConfig(servletConfig)
.application(this)
.openApiConfiguration(oasConfig)
.buildContext(true);
} catch (OpenApiConfigurationException e) {
throw new RuntimeException(e.getMessage(), e);
}
There is a much simpler solution for the above requirement.
import io.swagger.v3.jaxrs2.integration.resources.OpenApiResource;
import io.swagger.v3.oas.annotations.OpenAPIDefinition;
import io.swagger.v3.oas.annotations.info.Contact;
import io.swagger.v3.oas.annotations.info.Info;
import org.glassfish.jersey.server.ResourceConfig;
#OpenAPIDefinition(
info =
#Info(
title = "Sample rest service",
version = "1.0.0",
description = "Sample rest service",
contact =
#Contact(
url = "https://jira2.cerner.com/projects/Dey",
name = "ADey")))
public class SampleRestApplication extends ResourceConfig {
public SampleRestApplication() {
register(OpenApiResource.class);
}
}
your service will load your API spec at /openApi.yaml|json.
Another variant on the same theme. You can package up your openAPI config generation logic into a stand-alone class like so:
#Provider
public class SwaggerInfoBlackMagic implements Feature {
#Context ServletConfig config;
#Context Application app;
#Override
public boolean configure(FeatureContext context) {
//The aim here is to force construction of a (convincing) OpenApiContext before swagger does!
//This has been lifted from BaseOpenApiResource
String ctxId = getContextIdFromServletConfig(config);
try {
OpenApiContext ctx = new JaxrsOpenApiContextBuilder()
.servletConfig(config)
.application(app)
//Might need more of these depending on your setup..
//.resourcePackages(resourcePackages)
//.configLocation(configLocation)
.openApiConfiguration(getOpenApi())
.ctxId(ctxId)
.buildContext(true); //this also stores the instance statically
} catch (OpenApiConfigurationException e) {
throw new RuntimeException(e);
}
return true;
}
private OpenAPIConfiguration getOpenApi() {...}
}
Then whenever you need it you can simply add:
jersey().register(SwaggerInfoBlackMagic.class);
It's the same as the above but slightly tidier.
Related
Struggling getting Swagger implemented with a Java api using dropwizard (I come from .net world, and still learning the java architecture). Followed the swagger docs and samples. When I hit my /swagger endpoint, I get the console error Uncaught ReferenceError: SwaggerUIBundle is not defined. So, it seems that my servlet for the swagger endpoint is working, it's just that my configuration isn't delivering the swagger data to the index.html file? Seems like I am a small change away from getting this...
EDIT ADD: Also, hitting /swagger/openapi.json does not deliver the json, rather is a 404.
I've built and compiled the app, and the swagger has been added to the resources/swagger/assess of the repo
pom.xml
<dependency>
<groupId>io.swagger.core.v3</groupId>
<artifactId>swagger-jaxrs2</artifactId>
<scope>compile</scope>
<version>2.2.7</version>
</dependency>
<dependency>
<groupId>io.dropwizard</groupId>
<artifactId>dropwizard-assets</artifactId>
</dependency>
root/conf/swagger.yml
defaultName: My Api
server:
rootPath: /swagger/
logging:
level: ERROR
appenders:
- type: console
threshold: ALL
timeZone: UTC
target: stdout
I've recreated the above in the application.conf file as well with no changes... This seems like part of my problem, where to properly apply swagger configuration?
resources/app/index.html (Script only)
window.onload = function() {
var url = window.location.search.match(/url=([^&]+)/);
if (url && url.length > 1) {
url = decodeURIComponent(url[1]);
} else {
url = '/swagger/openapi.json';
}
// Build a system
const ui = SwaggerUIBundle({ // SwaggerUIBundle is never resolved
url: url,
dom_id: '#swagger-ui',
presets: [
SwaggerUIBundle.presets.apis,
SwaggerUIStandalonePreset
],
plugins: [
SwaggerUIBundle.plugins.DownloadUrl
],
layout: "StandaloneLayout"
})
window.ui = ui
}
</script>
configuration file
public class MyConfig extends TypesafeConfiguration implements ... { //TyepsafeConfguration extends Configuration as in the Sample setup
#NotEmpty
#JsonProperty
private String defaultName = "My Api";
public String getDefaultName() { // greyed out as unused?
return defaultName;
}
}
service file
public class MyService extends Application<MyConfiguration> {
#Override
public String getName() {
return "My Service";
}
#Override
public void initialize(final Bootstrap<MyConfiguration> bootstrap) {
// Swagger
bootstrap.addBundle(new AssetsBundle("/app", "/swagger", "index.html"));
}
#Override
public void run(final MyConfiguration configuration,
final Environment environment) {
OpenAPI oas = new OpenAPI();
Info info = new Info()
.title("My Title")
.description("My Description")
.termsOfService("https://myurl.com/tos")
.license(new License()
.name("Public Domain")
.url("https://myurl.com/LICENSE.md"));
oas.info(info);
List<Server> servers = new ArrayList<>();
servers.add(new Server().url("/api"));
oas.servers(servers);
SwaggerConfiguration oasConfig = new SwaggerConfiguration()
.openAPI(oas)
.prettyPrint(true)
.resourcePackages(Stream.of("path.to.resources").collect(Collectors.toSet()));
environment.jersey().register(FooResource.class);
environment.jersey().register(BarResource.class);
environment.getObjectMapper().setSerializationInclusion(JsonInclude.Include.NON_NULL);
// eg.
environment.jersey().register(new OpenApiResource().openApiConfiguration(oasConfig));
}
FooResource.java
public MyResource() {
#Override
#GET
#Path("/metadata")
#Public
#ApiOperation(value = "Get some metadata", notes = "returns metadata", response = CapabilityStatement.class)
#ApiResponses(#ApiResponse(code = 200, message = "Successful operation", examples = #Example(#ExampleProperty(value = ""))))
public CapabilityStatement metadata() {
return Capabilities.getCapabilities(baseURL);
}
}
Introduction
I would like to be able to have two different spring profiles, and depending on the profile to change to a hardcoded address for our feign builders.
Currently was have the following:
return builder.target(cls, "http://" + serviceName);
But I would actually like to do the following and over-ride the address:
return builder.target(cls, "http://our-server:8009/" + serviceName);
Why
Sometimes we don't want to run all the services within our development environment. Additionally, some of the services are only available through a zuul gateway sometimes.
So we run the same code in different situations and conditions.
Technical Details
We have the following code that we use for building our Feign Clients.
We had been using the #FeignClient annotation in the past, but lately we decided to start building our feignClients manually.
Example below:
#FeignClient(name = "ab-document-store", configuration = MultiPartSupportConfiguration.class, fallback = DocumentStoreFallback.class)
We call the feignRegistrar class with the following command:
return registerFeignClient(DocumentStoreClient.class, true);
#RequiredArgsConstructor
//#Component
#Slf4j
public class FeignRegistrar {
#Autowired
private Decoder decoder;
#Autowired
private Encoder encoder;
#Autowired
private Client client;
#Autowired
private Contract feignContract;
#Autowired
private ObjectFactory<HttpMessageConverters> messageConverters;
#Autowired
private List<RequestInterceptor> interceptors;
public <T> T register(Class<T> cls, String serviceName, boolean isDocumentStore) {
if(isDocumentStore){
encoder = new MultipartFormEncoder(new SpringEncoder(messageConverters));
}
//Client trustSSLSockets = new Client.Default(getSSLSocketFactory(), new NoopHostnameVerifier());
Feign.Builder builder = Feign.builder()
.client(client)
.encoder(encoder)
.decoder(decoder)
.contract(feignContract)
.logger(new Slf4Logger())
.logLevel(Logger.Level.HEADERS);
builder.requestInterceptor(new RequestInterceptor() {
#Override
public void apply(RequestTemplate template) {
template.header("X-Service-Name", serviceName);
}
});
for(RequestInterceptor interceptor : interceptors) {
builder.requestInterceptor(interceptor);
}
log.debug("Registering {} - as feign proxy ", serviceName);
return builder.target(cls, "http://" + serviceName);
}
public static class Slf4Logger extends Logger {
#Override
protected void log(String configKey, String format, Object... args) {
log.info("{} - {}", configKey, args);
}
}
}
Spring Cloud Property Over-ride
We have also been using property files such as application-ENV.property with entries such as the following:
ab-document-store.ribbon.NIWSServerListClassName:com.netflix.loadbalancer.ConfigurationBasedServerList
ab-document-store.ribbon.listOfServers: localhost:8025
Unfortunately, listOfServers is not enough for us. We would like to be able to assign a directory/path as well. Something like:
ab-document-store.ribbon.listOfServers: localhost:8025/ab-document-store
Otherworkaround
I have thought about sneaking in a header into all requests such as X-SERVICE-NAME using a feign interceptor. Then we could point all services to an address (e.g. localhost:9001) , and forward/proxy those requests to localhost:9001/X-SERVICE-NAME.
However, I would prefer a much easier solution such as:
ab-document-store.ribbon.listOfServers: localhost:8025/ab-document-store
But this doesn't work :(
Introduction
I found a solution for this using a proxy that detects a header.
So, I have a feign interceptor on the java-side that attaches a header x-service-name to every feign-request.
I also have a NodeJS proxy, that analyzes requests, finds x-service-name, and re-writes the requests to become: x-service-name/originalRequestPath.
This allows me to have all the microservices behind a zuul gateway but also access them using a eureka-over-ride.
Java-Feign-Interceptor
Feign.Builder builder = Feign.builder()
.client(client)
.encoder(usedEncoder)
.decoder(decoder)
.contract(feignContract)
.logger(new Slf4Logger())
.logLevel(Logger.Level.HEADERS);
builder.requestInterceptor(new RequestInterceptor() {
#Override
public void apply(RequestTemplate template) {
template.header("X-Service-Name", serviceName);
}
});
NodeJS proxy
In the example, my zuul gateway ( or another proxy ) is on localhost:9001.
I'm listening on localhost:1200 .
let enableProxyForJava = process.env.ENABLE_PROXY_FOR_JAVA;
if (enableProxyForJava != undefined && enableProxyForJava.toLowerCase() === 'true') {
var httpProxyJava = require('http-proxy');
var proxyJava = httpProxyJava.createProxy();
gutil.log( gutil.colors.green('Enabling Proxy for Java. Set your Eureka overrides to localhost:1200.') );
require('http').createServer(function(req, res) {
console.log("req.headers['x-service-name'] = " + req.headers['x-service-name']);
console.log("Before req.url:"+ req.url);
if( req.headers['x-service-name'] != undefined){
let change = req.headers['x-service-name'] +req.url;
console.log("After req.url:"+ change);
req.url = change;
}
proxyJava.web(req, res, {
target: 'http://localhost:9001/'
});
}).listen(1200);
}
Property file inside Java Application that has feign clients
mbak-microservice1.ribbon.NIWSServerListClassName:com.netflix.loadbalancer.ConfigurationBasedServerList
mbak-microservice1.ribbon.listOfServers: localhost:1200
mbak-microservice2.ribbon.NIWSServerListClassName:com.netflix.loadbalancer.ConfigurationBasedServerList
mbak-microservice2.ribbon.listOfServers: localhost:1200
mbak-document-store.ribbon.NIWSServerListClassName:com.netflix.loadbalancer.ConfigurationBasedServerList
mbak-document-store.ribbon.listOfServers: localhost:1200
I am trying to add Swagger support to my REST API but I am confused how to add Swagger related static content (HTML, JS) files to my Spring Boot application.
I use the following dependencies:
spring-boot-starter-parent:2.0.1.RELEASE
spring-boot-starter-jersey:2.0.1.RELEASE
swagger-jersey2-jaxrs:1.5.18
This is my swagger configuration:
#Configuration
public class SwaggerConfig {
#Bean
public BeanConfig swaggerConfiguration() {
final BeanConfig beanConfig = new BeanConfig();
beanConfig.setResourcePackage("a.b.c");
beanConfig.setScan(true);
beanConfig.setPrettyPrint(true);
return beanConfig;
}
}
And the jersey configuration:
#Component
public class JerseyConfig extends ResourceConfig {
public JerseyConfig() {
register(ImageResource.class);
register(io.swagger.jaxrs.listing.ApiListingResource.class);
register(io.swagger.jaxrs.listing.SwaggerSerializers.class);
}
}
This part works like a charm, when I open http://localhost:8090/swagger.json then I can see the expected Swagger JSON content.
But I do not know, how to add the Swagger related static HTML content to my application. I can see that this content is in the springfox-swagger-ui.jar and I can add it to my project as a maven dependency, but how I can unpack the content from this jar?
And what is the proper way to overwrite the default swagger.json URL with my URL in the static Swagger file in order to Swagger show my REST API immediately when I open swagger-ui.html.
<dependency>
<groupId>org.webjars</groupId>
<artifactId>swagger-ui</artifactId>
<version>${swagger-ui.version}</version>
</dependency>
Please, do not include springfox-swagger-ui.jar, it's meant to work with Spring's RestController.
You must have solved it now but it might help others so here's the complete procedure as I was also looking for a tutorial.
I am using Swagger V2 with Spring Boot 2 and it's straightforward 3 step process.
Step 1: Add required dependencies in pom.xml file. The second dependency is optional use it only if you need Swagger UI.
<!-- https://mvnrepository.com/artifact/io.springfox/springfox-swagger2 -->
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger2</artifactId>
<version>2.9.2</version>
</dependency>
<!-- https://mvnrepository.com/artifact/io.springfox/springfox-swagger-ui -->
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger-ui</artifactId>
<version>2.9.2</version>
</dependency>
Step 2: Add configuration class
#Configuration
#EnableSwagger2
public class SwaggerConfig {
public static final Contact DEFAULT_CONTACT = new Contact("Usama Amjad", "https://stackoverflow.com/users/4704510/usamaamjad", "hello#email.com");
public static final ApiInfo DEFAULT_API_INFO = new ApiInfo("Article API", "Article API documentation sample", "1.0", "urn:tos",
DEFAULT_CONTACT, "Apache 2.0", "http://www.apache.org/licenses/LICENSE-2.0", new ArrayList<VendorExtension>());
#Bean
public Docket api() {
Set<String> producesAndConsumes = new HashSet<>();
producesAndConsumes.add("application/json");
return new Docket(DocumentationType.SWAGGER_2)
.apiInfo(DEFAULT_API_INFO)
.produces(producesAndConsumes)
.consumes(producesAndConsumes);
}
}
Step 3: Setup complete and now you need to document APIs in controllers
#ApiOperation(value = "Returns a list Articles for a given Author", response = Article.class, responseContainer = "List")
#ApiResponses(value = { #ApiResponse(code = 200, message = "Success"),
#ApiResponse(code = 404, message = "The resource you were trying to reach is not found") })
#GetMapping(path = "/articles/users/{userId}")
public List<Article> getArticlesByUser() {
// Do your code
}
Usage:
Swagger UI: You can access it via http://localhost:8080/swagger-ui.html
Postman: You can also access your Documentation JSON from http://localhost:8080/v2/api-docs and just copy paste it in Postman to use with it.
is it possible to list all REST services when using cxf with spring-boot? I've created ApplicationListener<ContextRefreshedEvent> and in there I would like to list all REST service urls which were registered for my cxf servlet. I've tried to poke around CXFServlet, ServletContext, cxf Endpoint and cxf Server classes but I can't figure it out. I've also tried to review wadl generator (feature) and swagger2 feature but they create url and content when request comes. Is it possible?
Thanks.
I would scan the #WebService annotations on the classpath, maybe it will help you:
#Autowired
private ClassPathScanningCandidateComponentProvider annotationScanner;
public List<ClassDocument> generate(String basePackage) throws ClassNotFoundException {
Set<BeanDefinition> candidateComponents = annotationScanner.findCandidateComponents(basePackage);
List<ClassDocument> classDocuments = new ArrayList<>();
for (BeanDefinition component : candidateComponents) {
ClassDocument classDocument = new ClassDocument();
Class<?> beanClass = Class.forName(component.getBeanClassName());
classDocument.setClassName(beanClass.getName());
String[] baseUrl = beanClass.getAnnotation(javax.jws.WebService.class).value();
addMethods(classDocument, beanClass, baseUrl);
classDocuments.add(classDocument);
}
return classDocuments;
}
I'm having a bit of trouble making Swagger display API docs using Restlet. What Swagger shows is just these stuff:
And checking the api-docs it only shows this:
I wonder what is wrong with my code:
public class MyApplication extends SwaggerApplication {
private static final String ROOT_URI = "/";
public Restlet createInboundRoot() {
Router router = new Router(getContext());
router.attach(ROOT_URI, RootServerResource.class);
router.attach(ROOT_URI + "ping", PingServerResource.class);
router.attach(ROOT_URI + "ping/", PingServerResource.class);
// Some code omitted for simplicity
return router;
}
}
You could have a look at this article:
What can APISpark bring to your existing Web APIs (Part 2) -http://restlet.com/blog/2016/01/04/what-can-apispark-bring-to-your-existing-web-apis-part-2/
Both Swagger1 and 2 are supported by the Swagger extension of Restlet:
Swagger v1
public class ContactsApplication extends SwaggerApplication {
public Restlet createInboundRoot() {
Router router = new Router();
(...)
attachSwaggerSpecificationRestlet(router, "/docs");
return router;
}
}
Swagger v2
public class ContactsApplication extends Application {
public Restlet createInboundRoot() {
Router router = new Router();
(...)
Swagger2SpecificationRestlet swagger2SpecificationRestlet
= new Swagger2SpecificationRestlet(this);
swagger2SpecificationRestlet.setBasePath("http://myapp.org/");
swagger2SpecificationRestlet.attach(router, "/docs");
return router;
}
}
The solution is to add this code:
// Configuring Swagger 2 support
Swagger2SpecificationRestlet swagger2SpecificationRestlet
= new Swagger2SpecificationRestlet(this);
swagger2SpecificationRestlet.setBasePath("http://localhost:8080/api-docs");
swagger2SpecificationRestlet.attach(router);
And point the Swagger UI to /swagger.json
Swagger needs to find your API operations. I'm not sure about Restlet, in Jersey you annotate your REST resource classes with #Api and your methods with #ApiOperation. Read more here in the swagger docs.