why can't spring boot angularjs gateway app read from ui app? - java

I am learning about spring's scalability features using the tutorial at the following link. Specifically, part 6 of the tutorial uses a gateway app to regulate access to apps running on other servers.
I have followed the steps below precisely, but when I start all three apps and then type in localhost:8080/ui into my web browser, all I get is the word "Greeting" with no id or hello world, and with no css.
When I open the developer tools for the request in Firefox, I see that the GET requests for css and js resources are getting 404 errors pointing to urls like http://localhost:8080/js/hello.js instead of pointing to http://localhost:8080/ui/js/hello.js, as the test section of the tutorial suggests. How can I change this so that the greeting displays in the browser?
Here is what I have done step by step, following the tutorial's step six by first recreating the ui starting point from part one and the resource starting point from part three:
Create the UI sample starter app
# mkdir ui
# chmod -R 777 ui
# cd ui
# curl https://start.spring.io/starter.tgz -d style=web -d style=security -d name=ui | tar -xzvf -
Eclipse > File > Import > Existing Maven Projects > Navigate to ui folder > Finish
Create index.html in src/main/resources/static and add the following:
<!doctype html>
<html>
<head>
<title>Hello AngularJS</title>
<link href="css/angular-bootstrap.css" rel="stylesheet">
<style type="text/css">
[ng\:cloak], [ng-cloak], .ng-cloak {
display: none !important;
}
</style>
</head>
<body ng-app="hello">
<div class="container">
<h1>Greeting</h1>
<div ng-controller="home" ng-cloak class="ng-cloak">
<p>The ID is {{greeting.id}}</p>
<p>The content is {{greeting.content}}</p>
</div>
</div>
<script src="js/angular-bootstrap.js" type="text/javascript"></script>
<script src="js/hello.js"></script>
</body>
</html>
Add the following lines to src/main/resources/application.properties:
security.user.password=some.password
server.port: 8081
security.sessions: NEVER // The "security.sessions" setting means that Spring Security will accept cookies as authentication tokens but won’t create them unless they already exist.
Add the following to pom.xml in order to download and integrate angular and bootstrap etc using wro4j:
<build>
<resources>
<resource>
<directory>${project.basedir}/src/main/resources</directory>
</resource>
<resource>
<directory>${project.build.directory}/generated-resources</directory>
</resource>
</resources>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
<plugin>
<artifactId>maven-resources-plugin</artifactId>
<executions>
<execution>
<!-- Serves *only* to filter the wro.xml so it can get an absolute
path for the project -->
<id>copy-resources</id>
<phase>validate</phase>
<goals>
<goal>copy-resources</goal>
</goals>
<configuration>
<outputDirectory>${basedir}/target/wro</outputDirectory>
<resources>
<resource>
<directory>src/main/wro</directory>
<filtering>true</filtering>
</resource>
</resources>
</configuration>
</execution>
</executions>
</plugin>
<plugin>
<groupId>ro.isdc.wro4j</groupId>
<artifactId>wro4j-maven-plugin</artifactId>
<version>1.7.6</version>
<executions>
<execution>
<phase>generate-resources</phase>
<goals>
<goal>run</goal>
</goals>
</execution>
</executions>
<configuration>
<wroManagerFactory>ro.isdc.wro.maven.plugin.manager.factory.ConfigurableWroManagerFactory</wroManagerFactory>
<cssDestinationFolder>${project.build.directory}/generated-resources/static/css</cssDestinationFolder>
<jsDestinationFolder>${project.build.directory}/generated-resources/static/js</jsDestinationFolder>
<wroFile>${project.build.directory}/wro/wro.xml</wroFile>
<extraConfigFile>${basedir}/src/main/wro/wro.properties</extraConfigFile>
<contextFolder>${basedir}/src/main/wro</contextFolder>
</configuration>
<dependencies>
<dependency>
<groupId>org.webjars</groupId>
<artifactId>jquery</artifactId>
<version>2.1.1</version>
</dependency>
<dependency>
<groupId>org.webjars</groupId>
<artifactId>angularjs</artifactId>
<version>1.3.8</version>
</dependency>
<dependency>
<groupId>org.webjars</groupId>
<artifactId>bootstrap</artifactId>
<version>3.2.0</version>
</dependency>
</dependencies>
</plugin>
</plugins>
</build>
Eclipse > File > New > Source Folder > (create src/main/wro)
Add the following to src/main/wro/wro.properties (will compile css from less and minify javascript):
preProcessors=lessCssImport
postProcessors=less4j,jsMin
Add the following to src/main/wro/wro.xml (declares a single group angular-bootstrap with references to css, js, and main.less):
<groups xmlns="http://www.isdc.ro/wro">
<group name="angular-bootstrap">
<css>webjar:bootstrap/3.2.0/less/bootstrap.less</css>
<css>file:${project.basedir}/src/main/wro/main.less</css>
<js>webjar:jquery/2.1.1/jquery.min.js</js>
<js>webjar:angularjs/1.3.8/angular.min.js</js>
</group>
</groups>
Create src/main/wro/main.less and leave it empty for now. main.less can be used to customize bootstrap defaults.
Create src/main/resources/static/js/hello.js and add the following:
angular.module('hello', [])
.controller('home', function($scope, $http) {
$http.get('/resource/').success(function(data) {
$scope.greeting = data;
})
});
Change com.example.UiApplication.java to:
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.session.data.redis.config.annotation.web.http.EnableRedisHttpSession;
#SpringBootApplication
#EnableRedisHttpSession
public class UiApplication {
public static void main(String[] args) {
SpringApplication.run(UiApplication.class, args);
}
}
// now add the following to the ui app's pom.xml:
<dependency>
<groupId>org.springframework.session</groupId>
<artifactId>spring-session</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-redis</artifactId>
</dependency>
Create separate resource server
// navigate to root of workspace
# cd /home/username/someworkspace
# mkdir resource
# chmod -R 777 resource
# cd resource
# curl https://start.spring.io/starter.tgz -d style=web -d name=resource -d language=java | tar -xzvf -
Eclipse > Import > Existing Maven Project (navigate to resource folder just created)
// add the following to pom.xml in the resource app:
<dependency>
<groupId>org.springframework.session</groupId>
<artifactId>spring-session</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-redis</artifactId>
</dependency>
// change com.example.ResourceApplication.java to the following:
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.session.data.redis.config.annotation.web.http.EnableRedisHttpSession;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
#SpringBootApplication
#RestController
#EnableRedisHttpSession
public class ResourceApplication {
#RequestMapping("/resource")
public Map<String,Object> home() {
Map<String,Object> model = new HashMap<String,Object>();
model.put("id", UUID.randomUUID().toString());
model.put("content", "Hello World");
return model;
}
public static void main(String[] args) {
SpringApplication.run(ResourceApplication.class, args);
}
}
// Add the following to src/main/resources/application.properties:
server.port: 9000
security.sessions: NEVER
create the gateway app
navigate terminal to root of workspace, then
# cd /home/username/someworkspace
# mkdir gateway
# chmod -R 777 gateway
# cd gateway
# curl https://cloud-start.spring.io/starter.tgz -d style=web -d style=security -d style=cloud-zuul -d name=gateway -d style=redis | tar -xzvf -
Eclipse > File > Import > Existing Maven Project > (navigate to the gateway directory)
// change GatewayApplication.java to:
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.zuul.EnableZuulProxy;
import org.springframework.session.data.redis.config.annotation.web.http.EnableRedisHttpSession;
#SpringBootApplication
#EnableRedisHttpSession
#EnableZuulProxy
public class GatewayApplication {
public static void main(String[] args) {
SpringApplication.run(GatewayApplication.class, args);
}
}
// change src/main/resources/application.properties to:
zuul.routes.ui.url: http://localhost:8081
zuul.routes.resource.url: http://localhost:9000
security.user.password: password
security.sessions: ALWAYS
.
Start and test the apps:
# cd /home/username/someworkspace/ui
# mvn spring-boot:run
Then open a second terminal and type
# cd /home/username/someworkspace/resource
# mvn spring-boot:run
Then open a third terminal and type:
# cd /home/username/someworkspace/gateway
# mvn spring-boot: run
// test the app by putting localhost:8080/ui in the browser
Note that only the word "Greeting" shows up in the browser at localhost:8080/ui, and that there is no id and no content. Also, the firefox developer tools show 404 errors for resources like http://localhost:8080/js/hello.js which should instead be http://localhost:8080/ui/js/hello.js
However, when I type localhost:8081 in the browser, I get the css-styled "Greeting" followed by "The ID is" and "The content is", but no dynamic content from the resource server. The firefox developer tools for the localhost:8081 request give a 404 for http://localhost:8081/resource/.
Note that, to test any changes to the above, you just type control C in the appropriate console, then type kill $(lsof -t -i:8080) or 8081 or 9000, and then mvn spring-boot:run
So what changes to I make to the code above to get the id and greeting to load through the gateway?

Looks like an issue in your html page. You should make sure that links are correct. What Zuul in essence does is mapping urls from the gateway to the backend. To make your example work you have to either change the url in your angular app to /resource/resource or change the controller in the resource app to /. Also make sure that all of your apps have spring-boot-starter-security as a dependency to allow sharing of the sec context. Or disable security completely to debug your issue first.

Related

How to enable JSP in a Jetty server in a JAR file?

tl;dr: How can I enable JSP support for this project (which you can also download as a zip file)?
I'm trying to create a simple "hello world" web application using Jetty, and I'm pretty happy with what I have so far. The important files are:
pom.xml
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>io.happycoding</groupId>
<artifactId>app-engine-hello-world</artifactId>
<version>1</version>
<properties>
<!-- App Engine currently supports Java 11 -->
<maven.compiler.source>11</maven.compiler.source>
<maven.compiler.target>11</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<jetty.version>9.4.31.v20200723</jetty.version>
<!-- Project-specific properties -->
<exec.mainClass>io.happycoding.ServerMain</exec.mainClass>
<googleCloudProjectId>YOUR_PROJECT_ID_HERE</googleCloudProjectId>
</properties>
<dependencies>
<!-- Java Servlets API -->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>4.0.1</version>
</dependency>
<!-- Jetty -->
<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-server</artifactId>
<version>${jetty.version}</version>
</dependency>
<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-annotations</artifactId>
<version>${jetty.version}</version>
</dependency>
</dependencies>
<build>
<plugins>
<!-- Copy static resources like html files into the output jar file. -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-resources-plugin</artifactId>
<version>2.7</version>
<executions>
<execution>
<id>copy-web-resources</id>
<phase>compile</phase>
<goals><goal>copy-resources</goal></goals>
<configuration>
<outputDirectory>
${project.build.directory}/classes/META-INF/resources
</outputDirectory>
<resources>
<resource><directory>./src/main/webapp</directory></resource>
</resources>
</configuration>
</execution>
</executions>
</plugin>
<!-- Package everything into a single executable jar file. -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>3.2.4</version>
<executions>
<execution>
<phase>package</phase>
<goals><goal>shade</goal></goals>
<configuration>
<createDependencyReducedPom>false</createDependencyReducedPom>
<transformers>
<transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
<mainClass>${exec.mainClass}</mainClass>
</transformer>
</transformers>
</configuration>
</execution>
</executions>
</plugin>
<!-- App Engine plugin for deploying to the live site. -->
<plugin>
<groupId>com.google.cloud.tools</groupId>
<artifactId>appengine-maven-plugin</artifactId>
<version>2.2.0</version>
<configuration>
<projectId>${googleCloudProjectId}</projectId>
<version>1</version>
</configuration>
</plugin>
</plugins>
</build>
</project>
ServerMain.java
package io.happycoding;
import java.net.URL;
import org.eclipse.jetty.annotations.AnnotationConfiguration;
import org.eclipse.jetty.server.Handler;
import org.eclipse.jetty.server.handler.DefaultHandler;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.servlet.DefaultServlet;
import org.eclipse.jetty.webapp.Configuration;
import org.eclipse.jetty.webapp.WebAppContext;
import org.eclipse.jetty.webapp.WebInfConfiguration;
/**
* Starts up the server, including a DefaultServlet that handles static files,
* and any servlet classes annotated with the #WebServlet annotation.
*/
public class ServerMain {
public static void main(String[] args) throws Exception {
// Create a server that listens on port 8080.
Server server = new Server(8080);
WebAppContext webAppContext = new WebAppContext();
server.setHandler(webAppContext);
// Load static content from inside the jar file.
URL webAppDir =
ServerMain.class.getClassLoader().getResource("META-INF/resources");
webAppContext.setResourceBase(webAppDir.toURI().toString());
// Enable annotations so the server sees classes annotated with #WebServlet.
webAppContext.setConfigurations(new Configuration[]{
new AnnotationConfiguration(),
new WebInfConfiguration(),
});
// Look for annotations in the classes directory (dev server) and in the
// jar file (live server)
webAppContext.setAttribute(
"org.eclipse.jetty.server.webapp.ContainerIncludeJarPattern",
".*/target/classes/|.*\\.jar");
// Handle static resources, e.g. html files.
webAppContext.addServlet(DefaultServlet.class, "/");
// Start the server! 🚀
server.start();
System.out.println("Server started!");
// Keep the main thread alive while the server is running.
server.join();
}
}
index.jsp
<%# page import="java.util.Date" %>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Google Cloud Hello World</title>
</head>
<body>
<h1>Google Cloud Hello World</h1>
<p>This is a sample HTML file. Click here to see content served from a servlet.</p>
<p>Learn more at HappyCoding.io.</p>
<p>The current time is: <%= new Date().toString() %></p>
</body>
</html>
The server starts up and renders HTML perfectly. Servlets also work. But when I try to use JSP like above, I see the JSP code rendered in the HTML instead of being parsed as Java.
I've tried googling, but every tutorial I've found works by creating a separate WAR file. My server is launched from a JAR file, and I'm trying to keep the code as simple as possible, so I'm trying to avoid using a separate WAR file if possible.
Is there a small change I can make to my pom.xml file and my ServerMain.java file to enable JSP?
To enable JSP support in the embedded Jetty server you need to do two things, modify your project pom.xml file to include the necessary dependencies, and configure the JettyJspServlet and related Jetty stuff in your ServerMain class.
First, include the following dependencies on your project pom.xml. They will provide support for JSP an JSTL:
<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>apache-jsp</artifactId>
<version>${jetty.version}</version>
</dependency>
<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>apache-jstl</artifactId>
<version>${jetty.version}</version>
<type>pom</type>
</dependency>
Then, use this modified version of the ServerMain class:
package io.happycoding;
import java.io.File;
import java.io.IOException;
import java.net.URL;
import java.net.URLClassLoader;
import org.apache.tomcat.util.scan.StandardJarScanner;
import org.eclipse.jetty.annotations.AnnotationConfiguration;
import org.eclipse.jetty.apache.jsp.JettyJasperInitializer;
import org.eclipse.jetty.jsp.JettyJspServlet;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.servlet.DefaultServlet;
import org.eclipse.jetty.servlet.ServletContextHandler;
import org.eclipse.jetty.servlet.ServletHolder;
import org.eclipse.jetty.util.component.AbstractLifeCycle;
import org.eclipse.jetty.webapp.Configuration;
import org.eclipse.jetty.webapp.WebAppContext;
import org.eclipse.jetty.webapp.WebInfConfiguration;
/**
* Starts up the server, including a DefaultServlet that handles static files,
* and any servlet classes annotated with the #WebServlet annotation.
*/
public class ServerMain {
public static void main(String[] args) throws Exception {
// Create a server that listens on port 8080.
Server server = new Server(8080);
WebAppContext webAppContext = new WebAppContext();
server.setHandler(webAppContext);
// Load static content from inside the jar file.
URL webAppDir =
ServerMain.class.getClassLoader().getResource("META-INF/resources");
webAppContext.setResourceBase(webAppDir.toURI().toString());
// Enable annotations so the server sees classes annotated with #WebServlet.
webAppContext.setConfigurations(new Configuration[]{
new AnnotationConfiguration(),
new WebInfConfiguration(),
});
// Look for annotations in the classes directory (dev server) and in the
// jar file (live server)
webAppContext.setAttribute(
"org.eclipse.jetty.server.webapp.ContainerIncludeJarPattern",
".*/target/classes/|.*\\.jar");
// Handle static resources, e.g. html files.
webAppContext.addServlet(DefaultServlet.class, "/");
// Configure JSP support.
enableEmbeddedJspSupport(webAppContext);
// Start the server! 🚀
server.start();
System.out.println("Server started!");
// Keep the main thread alive while the server is running.
server.join();
}
/**
* Setup JSP Support for ServletContextHandlers.
* <p>
* NOTE: This is not required or appropriate if using a WebAppContext.
* </p>
*
* #param servletContextHandler the ServletContextHandler to configure
* #throws IOException if unable to configure
*/
private static void enableEmbeddedJspSupport(ServletContextHandler servletContextHandler) throws IOException
{
// Establish Scratch directory for the servlet context (used by JSP compilation)
File tempDir = new File(System.getProperty("java.io.tmpdir"));
File scratchDir = new File(tempDir.toString(), "embedded-jetty-jsp");
if (!scratchDir.exists())
{
if (!scratchDir.mkdirs())
{
throw new IOException("Unable to create scratch directory: " + scratchDir);
}
}
servletContextHandler.setAttribute("javax.servlet.context.tempdir", scratchDir);
// Set Classloader of Context to be sane (needed for JSTL)
// JSP requires a non-System classloader, this simply wraps the
// embedded System classloader in a way that makes it suitable
// for JSP to use
ClassLoader jspClassLoader = new URLClassLoader(new URL[0], ServerMain.class.getClassLoader());
servletContextHandler.setClassLoader(jspClassLoader);
// Manually call JettyJasperInitializer on context startup
servletContextHandler.addBean(new JspStarter(servletContextHandler));
// Create / Register JSP Servlet (must be named "jsp" per spec)
ServletHolder holderJsp = new ServletHolder("jsp", JettyJspServlet.class);
holderJsp.setInitOrder(0);
holderJsp.setInitParameter("logVerbosityLevel", "DEBUG");
holderJsp.setInitParameter("fork", "false");
holderJsp.setInitParameter("xpoweredBy", "false");
holderJsp.setInitParameter("compilerTargetVM", "1.8");
holderJsp.setInitParameter("compilerSourceVM", "1.8");
holderJsp.setInitParameter("keepgenerated", "true");
servletContextHandler.addServlet(holderJsp, "*.jsp");
}
/**
* JspStarter for embedded ServletContextHandlers
*
* This is added as a bean that is a jetty LifeCycle on the ServletContextHandler.
* This bean's doStart method will be called as the ServletContextHandler starts,
* and will call the ServletContainerInitializer for the jsp engine.
*
*/
public static class JspStarter extends AbstractLifeCycle implements ServletContextHandler.ServletContainerInitializerCaller
{
JettyJasperInitializer sci;
ServletContextHandler context;
public JspStarter (ServletContextHandler context)
{
this.sci = new JettyJasperInitializer();
this.context = context;
this.context.setAttribute("org.apache.tomcat.JarScanner", new StandardJarScanner());
}
#Override
protected void doStart() throws Exception
{
ClassLoader old = Thread.currentThread().getContextClassLoader();
Thread.currentThread().setContextClassLoader(context.getClassLoader());
try
{
sci.onStartup(null, context.getServletContext());
super.doStart();
}
finally
{
Thread.currentThread().setContextClassLoader(old);
}
}
}
}
As you can see, the principal difference with the previous code is the inclusion of the method call to a new method enableEmbeddedJspSupport while configuring the embedded Jetty server in main.
The enableEmbeddedJspSupport method and the companion class JspStarter are an adapted version of the code you can find in the Main class in this Github repository.

Access Maven property "developers" in application.yml (Spring Boot)

I want to have the developer, which is defined in the pom.xml with the tag to appear in the application.yml after the build process. Somehow it is working with all attributes but developers.
This is in a Spring Boot project, I want the attributes to be filled during the build process.
This is an excerpt of the pom.xml
<description>my description</description>
<developers>
<developer>
<id>12345</id>
<name>John Doe</name>
<email>john#doe.com</email>
</developer>
</developers>
This is in application.yml
info:
description: "#project.description#"
developer: "#project.developers[0].id#"
It works for description, but not for developer. I tried many variations, e.g. ${..}, "#project.developers.0.id". Nothing seems to be working.
If anybody has an idea, I would be very grateful.
You can read the developer id or email address or any values from the pom.xml with this elegant way:
Generate the build-info.properties with the default data plus your additional data
Read values from this file easily with Spring via the BuildProperties.
pom.xml:
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<executions>
<execution>
<goals>
<goal>build-info</goal>
</goals>
<configuration>
<additionalProperties>
<developer>${project.developers[0].email}</developer>
</additionalProperties>
</configuration>
</execution>
</executions>
</plugin>
This will produce a build-info.properties file under the META-INF directory with the following content:
build.artifact=<artifactId-from-pom>
build.group=<groupId-from-pom>
build.name=<name-from-pom>
build.time=<build-time>
build.version=<version-from-pom>
build.developer=<email-of-the-first-developer-from-pom>
Then you can read values with Spring:
#Configuration
public class OpenApiConfiguration {
#Autowired
private BuildProperties buildProperties;
#Bean
public OpenAPI customOpenAPI()) {
return new OpenAPI().info(new Info()
.title(...)
.version(buildProperties.getVersion())
.contact(new Contact().email(buildProperties.get("developer"))));
}
}
To read:
Spring Boot Maven Plugin
BuildProperties
I hope that this will help you.
Add it on the property and use on pom and property file, example:
<properties>
<team.name>John Doe</team.name>
</properties>
Use on developer data:
<developers>
<developer>
<name>${team.name}</name>
...
And in the application use the property:
description: "#team.name#"

How to fully disable swagger-ui in spring-boot?(/swagger-ui.html should return 404)

I have read following topic:
Disabling Swagger with Spring MVC
and I wrote:
#Bean
public Docket api() {
return new Docket(DocumentationType.SWAGGER_2)
.select()
.apis(RequestHandlerSelectors.basePackage("com.project.name.controller"))
.paths(PathSelectors.ant("/api/**"))
.build()
.apiInfo(apiInfo())
.enable(false);
}
But in case if I try to access swagger ui: localhost:8080/swagger-ui.html
I see
It looks not accurate. Can I fully disabled this URL ? 404 for example or something like this.
My answer is similar to the answer provided earlier with a slight difference. I usually create a separate spring profile named swagger. When I want to enable Swagger, l pass the following VM flag while starting my application, -Dspring.profiles.active=swagger. Here is an example of my Swagger configuration,
#Profile(value = {"swagger"})
#Configuration
#EnableSwagger2
public class SwaggerConfiguration {
...
}
Next time when you try to access swagger-ui.html without swagger profile, you will get an empty Swagger screen but not 404.
If you don't want to load the static Swagger UI page at all, you can write a simple controller as shown below,
#Profile("!swagger")
#RestController
#Slf4j
public class DisableSwaggerUiController {
#RequestMapping(value = "swagger-ui.html", method = RequestMethod.GET)
public void getSwagger(HttpServletResponse httpResponse) throws IOException {
httpResponse.setStatus(HttpStatus.NOT_FOUND.value());
}
}
Now if you try to access swagger-ui.html without swagger profile, you will get a 404.
with swagger 3.0.0 version you can add springfox.documentation.enabled=false in corresponding environment profile application.properties file.
For example, I have added this to application-prod.properties to disable in production (while running the app you must specify the profile using VM args like -Dspring.profiles.active=prod)
You can externalize the #EnableSwagger2 to its own #Configruation and load it conditionally via a property or profile. e.g.
#Profile("!production")
#Configuration
#EnableSwagger2
public class SwaggerConfiguration{
//Additional Swagger Beans
}
this would activate swagger for any profile that isn't production.
Adding onto #Hayden's answer (I don't have enough points to comment..)
According to the springdoc documentation, you can disable both the springdoc api endpoints and swagger-ui using the following properties:
https://springdoc.org/#disabling-the-springdoc-openapi-endpoints
# Disabling the /v3/api-docs endpoint
springdoc.api-docs.enabled=false
https://springdoc.org/#disabling-the-swagger-ui
# Disabling the swagger-ui
springdoc.swagger-ui.enabled=false
If you dont have Swagger annotations inside controllers... just exclude SwaggerConfig.class and swagger dependencies on build
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<excludes>
<exclude>com/company/app/SwaggerConfig.java</exclude>
</excludes>
</configuration>
</plugin>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<excludes>
<exclude>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger-ui</artifactId>
</exclude>
<exclude>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger2</artifactId>
</exclude>
</excludes>
</configuration>
<executions>
<execution>
<goals>
<goal>repackage</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
For SpringDoc users, add this to your application.properties
springdoc.api-docs.enabled=false
To disable Swagger only when the prod profile is active, add it to your application-prod.properties instead
For those that use the code gen:
#Controller
#Profile({"dev", "staging"})
public class HomeController {
#RequestMapping(value = "/")
public String index() {
System.out.println("swagger-ui.html");
return "redirect:swagger-ui.html";
}
}
And add the file to you .swagger-codegen-ignore else your changes are overwritten on the next maven build
When using springdoc-openapi-ui dependency one can disable swagger-ui through the property:
springdoc.swagger-ui.enabled=false
as stated in Spring Doc FAQ.
In latest version of spring boot you can add this in yout application.yml :
springdoc:
swagger-ui:
enabled: false
api-docs:
enabled: false
So that swagger-ui key is used to disable the swagger interface and api-docs one is used to disable the route on which the JSON describing your API is served.
In my config I have a prod profile wich reads an application-prod.yml containing those lines.
Just remove dependency.
<!--<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger-ui</artifactId>
<version>2.9.2</version>
</dependency>-->
It does not affect compiling.

Spring routing gets 404 error because of invalid routing

For an assignment i'm practising Java's Spring MVC for creating a web application. I've build this whole project in the IDE Intellij Ultimate 2016.2.5.
I've created a Maven project for this, imported the correct and asked dependencies for this and build it.
The IDE build the following directory structure:
├───src
│ └───bas
│ └───animalkingdom
│ ├───config
│ ├───controllers
├───test
│ └───bas
│ └───animalkingdom
└───web
├───META-INF
├───resources
└───WEB-INF
└───pages
The config package is where my configuration class is, extending from WebMvcConfigurerAdapter:
package bas.animalkingdom.config;
import ...
#Configuration
#ComponentScan("bas.animalkingdom")
#EnableWebMvc
public class Config extends WebMvcConfigurerAdapter {
#Bean
public UrlBasedViewResolver setupViewResolver() {
UrlBasedViewResolver resolver = new UrlBasedViewResolver();
resolver.setPrefix("/WEB-INF/pages/");
resolver.setSuffix(".jsp");
resolver.setViewClass(JstlView.class);
return resolver;
}
#Override
public void addResourceHandlers(ResourceHandlerRegistry registry) { registry.addResourceHandler("/resources/**").addResourceLocations("/resources/");
}
}
I thought the #ComponentScan had to point to the main source directory where all your source files are.
I was told that I also need a class extending from the WebApplicationInitializer. I got this one from my school
package bas.animalkingdom.config;
import ...
public class WebInitializer implements WebApplicationInitializer {
#Override
public void onStartup(ServletContext servletContext) throws ServletException {
AnnotationConfigWebApplicationContext ctx = new AnnotationConfigWebApplicationContext();
ctx.register(Config.class);
ctx.setServletContext(servletContext);
ServletRegistration.Dynamic servlet = servletContext.addServlet("dispatcher", new DispatcherServlet(ctx));
servlet.addMapping("/");
servlet.setLoadOnStartup(1);
}
}
This one is also in the config package.
The Config class is set as the Spring Application Context in the project structure settings in my IDE.
In the root directory is the web folder. In the folder WEB-INF is a empty web.xml file, which I was told I didn't need because the settings would be loaded via the configuration class. It looks like this:
<web-app id="WebApp_ID" version="2.4"
xmlns="http://java.sun.com/xml/ns/j2ee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee
http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd">
</web-app>
In the root directory of the web folder is a index.jsp file.
In the bas.animalkingdom.controllers package are my controllers. For testing purposing, I only created one:
package bas.animalkingdom.controllers;
import ...
#Controller("AnimalC")
public class AnimalController {
#RequestMapping(value = "/animals", method = RequestMethod.GET)
public String getAnimals(ModelMap modelMap) {
Animal animal = new AfricanElephant(new Male(), "Body Covering", "Ename", " acolor", 123, 321);
modelMap.put("animal", animal);
return "animals";
}
}
With this controller I expected that I can go to the localhost/animals URL, and that it would load up the animals.jsp file located in my web\WEB-INF\pages\ package.
My code has no compile errors in it.
When I run my TomCat server, and open my browser to go to the localhost with the corresponding host, the index.jsp file just loads with no problem. This file is located in the web\ package.
When I go to the localhost:(port)/animals, I just get a 404 page, with the message that the page could not be found.
What does cause this? I've defined the controller which sets that route right?
Also, when looking up other Spring MVC tutorials, they all use a different packaging, does this work as well?
All your java classes and jsps are fine, problem is your module structure. There are basically 2 approaches to your problem, the conventional (recommended) and unconventional way. Let's start with the unconventional way, which is quicker and not recommended:
A. The Unconventional:
Add to your maven-war-plugin, the warSourceDirectory tag as shown below:
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-war-plugin</artifactId>
<version>3.0.0</version>
<configuration>
<warSourceDirectory>${basedir}/web</warSourceDirectory>
<failOnMissingWebXml>false</failOnMissingWebXml>
</configuration>
</plugin>
The disadvantage of using this approach is that other plugins may give you unexpected problems down the road, for example the maven-jetty-plugin will not run out of the box with this approach. It defaults to looking in src/main/webapp, although it is configurable. Maven life is easier if you follow the conventional approach.
B. Conventional Approach:
The solution lies in having a conventional maven structure for your module, make sure to do the following:
Delete Your web.xml file. If maven package failed because of missing web.xml file, add the below plugin in the build section of your pom.xml :
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-war-plugin</artifactId>
<version>3.0.0</version>
<configuration>
<failOnMissingWebXml>false</failOnMissingWebXml>
</configuration>
</plugin>
Create folder structure /src/main/java and put all your java packages into this java directory. If your packages are not formatted properly, you can right click folder java and go to Mark Directory as -> Source Root.
Create folder structure /src/test/java and put all your test packages in the java directory.
Create a folder structure /src/main/webapp and put all the contents of folder web into the webapp directory.
After doing this you can test your application. You can try using jetty plugin to deploy your web app with the below configuration:
<build>
<finalName>spring-mvc</finalName>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-war-plugin</artifactId>
<version>3.0.0</version>
<configuration>
<failOnMissingWebXml>false</failOnMissingWebXml>
</configuration>
</plugin>
<plugin>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-maven-plugin</artifactId>
<version>9.2.11.v20150529</version>
<configuration>
<scanIntervalSeconds>10</scanIntervalSeconds>
<webApp>
<contextPath>/</contextPath>
</webApp>
<httpConnector>
<port>8080</port>
</httpConnector>
</configuration>
</plugin>
</plugins>
</build>

Possible to run two webapps at once when developing with Maven/Eclipse?

Here's the problem: we build webapps for clients. We also have an "admin" webapp that modifies some client data structures. Because of the nature of the data, both webapps have to run in the same JVM.
This is no problem in production; you just put two webapps in the same app server.
We've recently switched to a Mavenish way of laying out webapps, though, and Maven wants one webapp per project. In Eclipse it's a problem, because if you run the different webapps independently, they'll be in separate JVMs.
We're trying to use the jetty-maven-plugin to do webapp testing, but could switch to something else if it would solve this problem.
Yes, you can :) It's done by identifying one WAR module as the primary, copying all the other WARs into the primary's target dir, making a jetty.xml and telling Maven Jetty Plugin to use the jetty.xml. Here's how to copy the other WARs using Maven dependency plugin:
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-dependency-plugin</artifactId>
<executions>
<execution>
<id>copy</id>
<phase>package</phase>
<goals>
<goal>copy</goal>
</goals>
<configuration>
<artifactItems>
<artifactItem>
<groupId>com.foo</groupId>
<artifactId>bar</artifactId>
<version>${project.version}</version>
<type>war</type>
<overWrite>true</overWrite>
<outputDirectory>target/</outputDirectory>
</artifactItem>
</artifactItems>
</configuration>
</execution>
</executions>
You'll need to have the com.foo:bar dependency defined in the POM as well. Here's the contents of jetty.xml:
<?xml version="1.0"?>
<!-- =========================================================== -->
<!-- Set handler Collection Structure -->
<!-- =========================================================== -->
<Set name="handler">
<New id="Handlers" class="org.eclipse.jetty.server.handler.HandlerCollection">
<Set name="handlers">
<Array type="org.eclipse.jetty.server.Handler">
<Item>
<New class="org.eclipse.jetty.server.handler.ContextHandlerCollection"
id="Contexts">
<Set name="handlers">
<Array type="org.eclipse.jetty.server.Handler">
<Item>
<New id="FooWebHandler"
class="org.eclipse.jetty.webapp.WebAppContext"/>
</Item>
</Array>
</Set>
</New>
</Item>
</Array>
</Set>
</New>
</Set>
<Ref id="FooWebHandler">
<Set name="contextPath">/foo</Set>
<Set name="war">
target/bar-${project.version}.war
</Set>
</Ref>
And here's how to tell Maven Jetty plugin to use the new jetty.xml:
<plugin>
<groupId>org.mortbay.jetty</groupId>
<artifactId>jetty-maven-plugin</artifactId>
<version>${jetty.version}</version>
<configuration>
<jettyConfig>${basedir}/jetty.xml</jettyConfig>
</configuration>
Now kick off jetty:run-war goal from Eclipse and you should see all WARs deploying in one Maven Jetty plugin instance. I run this from command line and it works there, YMMV with Eclipse.
You can do this via the jetty-maven-plugin directly, without modifying jetty.xml:
<plugin>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-maven-plugin</artifactId>
<configuration>
<scanIntervalSeconds>5</scanIntervalSeconds>
<webApp>
<contextPath>/</contextPath>
</webApp>
<contextHandlers>
<contextHandler implementation="org.eclipse.jetty.maven.plugin.JettyWebAppContext">
<war>${project.basedir}/../secondProject.war</war>
<contextPath>/cc</contextPath>
</contextHandler>
</contextHandlers>
</configuration>
<executions>
...
</executions>
</plugin>
You don't have to modify jetty.xml file(s) at all to use this successfully, and you don't need to copy the war file either. (although you may wish to build the second war as part of this build, see maven-invoker-plugin)
Documentation here: http://www.eclipse.org/jetty/documentation/9.2.2.v20140723/jetty-maven-plugin.html#running-more-than-one-webapp
Here's to give an example for ccleve's answer.
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.handler.HandlerCollection;
import org.eclipse.jetty.webapp.WebAppContext;
import org.eclipse.jetty.xml.XmlConfiguration;
...
public static void main(String[] args) throws Exception {
...
Server server = new Server(port);
HandlerCollection handlers = new HandlerCollection();
WebAppContext frontEndWebappContext = new WebAppContext(
"src/main/webapp", "/test"
);
handlers.addHandler(frontEndWebappContext);
String serviceWebappBasePath = {absolute_path_to_other_webapp};
WebAppContext serviceWebappContext = new WebAppContext(
serviceWebappBasePath + "/main/webapp", "/"
);
handlers.addHandler(serviceWebappContext);
XmlConfiguration conf = new XmlConfiguration(new File(
serviceWebappBasePath + "test/webapp/WEB-INF/jetty-web.xml")
.toURI().toURL().openStream());
conf.configure(serviceWebappContext);
server.setHandler(handlers);
server.start();
...
In this case there is front-end webapp that is mounted to "/test" and a service webapp that is mounted on "/". We also include jetty-web.xml of the service app.
In your case, that launcher that you would create should be located in the "src/test" folder since it shouldn't be included in your war because you use want it for test purposes only.
You may also have to add dependency to the service webapp in scope test in the pom file of the front end webapp :
<dependency>
<groupId>{service_group}</groupId>
<artifactId>{service_artifact_id}</artifactId>
<version>{service_version}</version>
<scope>test</scope>
</dependency>
or/and
<dependency>
<groupId>{service_group}</groupId>
<artifactId>{service_artifact_id}</artifactId>
<type>test-jar</type>
<version>{service_version}</version>
<scope>test</scope>
</dependency>
I don't have that much experience with Maven/Jetty; but I do have experience with Tomcat on Eclipse; so we're both at least doing something with servlets on Eclipse.
At any rate, I don't know if you've made projects or not using any of the project templates in Eclipse, but I made mine using the Dynamic Web Project template. I haven't done two web apps before; but if you were to make two of these projects, and then in the Eclipse servers tab, make a new server and add both projects to it, you might be able to accomplish your goals.
When you do Run As...you're basically just running the default run configuration that Eclipse has for your project setup. But if you deploy both of your projects to one server in your dev environment, and then select that server and hit start and it should just start it on the one JVM; similar to your production environment.
Like I said though; I'm not using Jetty and I've never used the server setup guide with Jetty so I wouldn't know if this would work for you or not. So I hope this is helpful - but I won't be too terribly surprised if it isn't.
Answering my own question:
It appears that this just isn't possible. The workaround that we've come up with is to write a little embedded jetty code and launch it from within our app. Jetty allows you to add multiple webapps programmatically. It also allows you to create multiple resource bases, that is, directories, per webapp, which enables overlays. So far, it works great.

Categories

Resources