I want to develop my first AppEngine application, that will also use GWT. Since I don't have any experience with GWT and AppEngine, I started with tutorials on GWT site, and after succefully completing Getting Started, I started working on http://code.google.com/webtoolkit/doc/latest/tutorial/appengine.html
But I ran into a problem, and I don't have a clue why :)
I am trying to check if user is logged in, like in "Personalize the application with the User Service" section of tutorial.
But when I run the code itself, I get an error:
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1"/>
<title>Error 404 NOT_FOUND</title>
</head>
<body><h2>HTTP ERROR 404</h2>
<p>Problem accessing /parkmeweb/login. Reason:
<pre> NOT_FOUND</pre></p><hr /><i><small>Powered by Jetty://</small></i><br/>
</body>
</html>
And here are my files:
LoginService
#RemoteServiceRelativePath("login")
public interface LoginService extends RemoteService {
public LoginInfo login(String requestUri);
}
LoginServiceAsync
public interface LoginServiceAsync {
public void login(String requestUri, AsyncCallback<LoginInfo> async);
}
LoginServiceImpl
public class LoginServiceImpl extends RemoteServiceServlet implements
LoginService {
public LoginInfo login(String requestUri) {
UserService userService = UserServiceFactory.getUserService();
User user = userService.getCurrentUser();
LoginInfo loginInfo = new LoginInfo();
if (user != null) {
loginInfo.setLoggedIn(true);
loginInfo.setEmailAddress(user.getEmail());
loginInfo.setNickname(user.getNickname());
loginInfo.setLogoutUrl(userService.createLogoutURL(requestUri));
} else {
loginInfo.setLoggedIn(false);
loginInfo.setLoginUrl(userService.createLoginURL(requestUri));
}
return loginInfo;
}
}
web.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE web-app
PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
"http://java.sun.com/dtd/web-app_2_3.dtd">
<web-app>
<!-- Servlets -->
<servlet>
<servlet-name>loginService</servlet-name>
<servlet-class>com.parkme.parkmeweb.server.LoginServiceImpl</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>loginService</servlet-name>
<url-pattern>/parkmeweb/login/</url-pattern>
</servlet-mapping>
<!-- Default page to serve -->
<welcome-file-list>
<welcome-file>ParkmeWeb.html</welcome-file>
</welcome-file-list>
</web-app>
All this I getting called from onModuleLoad:
public void onModuleLoad() {
LoginServiceAsync loginService = GWT.create(LoginService.class);
loginService.login(GWT.getHostPageBaseURL(), new AsyncCallback<LoginInfo>() {
public void onFailure(Throwable error) {
//this is where error is thrown
Window.alert(error.getMessage());
}
public void onSuccess(LoginInfo result) {
loginInfo = result;
if(loginInfo.isLoggedIn()) {
return;
} else {
loadLogin();
}
}
});
}
Just by looking at this, I can't see any problems, and I should probably looking for problems elsewhere, but I would like to hear some ideas what went wrong.
The handler is for /parkmweweb/login/, but you're visiting /parkmeweb/login - without the trailing slash.
Facing the same problem. But I tried to deploy it to google. The servlet is accessible and no problem. It looks like being a problem with GWT + Eclipse, not sure exactly where. Hope they can fix it, other wise testing is difficult.
I just restarted Eclipse and that fixed the problem.
Problem started when I switched from jre1.7 to jre1.6 both x64.
Related
I am trying to understand how the Session scoped bean work and have tried the example from here.
HelloMessageGenerator.java
public class HelloMessageGenerator {
private String message;
public HelloMessageGenerator() {
this.message = message;
}
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
}
HelloMessageBean.java
#Configuration
public class HelloMessageBean {
#Bean
#Scope(value = WebApplicationContext.SCOPE_SESSION, proxyMode = ScopedProxyMode.TARGET_CLASS)
public HelloMessageGenerator requestScopedBean() {
System.out.println("bean created");
return new HelloMessageGenerator();
}
}
HelloMessageController.java
#Controller
public class HelloMessageController {
#Resource(name = "sessionScopedBean")
HelloMessageGenerator sessionScopedBean;
#RequestMapping("/scopes/session")
public String getSessionScopeMessage(final Model model) {
model.addAttribute("previousMessage", sessionScopedBean.getMessage());
sessionScopedBean.setMessage("Good afternoon!");
model.addAttribute("currentMessage", sessionScopedBean.getMessage());
return "scopesExample";
}
}
When I go to http://localhost:8080/scopes/session I get an error.
scopesExample.html
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="https://www.thymeleaf.org"
xmlns:sec="https://www.thymeleaf.org/thymeleaf-extras-springsecurity3">
<head>
<title>Hello World!</title>
</head>
<body>
<p th:text="${previousMessage}">previous message</p>
<p th:text="${currentMessage}">current message</p>
</body>
</html>
The error I am getting is as if the mapping would not exist:
This application has no explicit mapping for /error, so you are seeing this as a fallback. There was an unexpected error (type=Not Found, status=404).
I was missing the thymeleaf dependency in the pom.
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
This is why I was getting a 404 when going to localhost:8080/scopes/request
After adding the thymeleaf dependency the bean scope was working as expected. For a session scope bean the bean is created only once per session or after the configured session timeout. For a request scope bean it is created for every request (ie. each time one hits the endpoint).
I am integrating Spring Security into my Spring MVC and Angular App.
Versions:
Spring Security: 4.1.5.RELEASE
Spring MVC: 4.2.0.RELEASE
Angular: 4
After integration, I see that browser is showing pop up for user name and password. Even after following changes, I am not able to get rid of the pop up and proceed further to my custom login form in angular.
To start with I am using http basic authentication mechanism.
For this created following SecurityConfig.java.
[Updated]
#Configuration
#EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
// #formatter:on
auth.inMemoryAuthentication().withUser("user1").password("user1").roles("USER").and().withUser("user2")
.password("user2").roles("USER").and().withUser("admin").password("admin").roles("ADMIN");
// #formatter:off
}
#Override
protected void configure(HttpSecurity http) throws Exception {
// #formatter:on
http
.authorizeRequests().antMatchers("/index.html", "/", "/login", "*.*", "/*.bundle.*", "/*.ico")
.permitAll().anyRequest().authenticated().and().csrf()
.csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse());
// #formatter:off
}
}
For resource and path resolution, created following ServletContextConfiguration.java .
#Configuration
#EnableWebMvc
public class ServletContextConfiguration extends WebMvcConfigurerAdapter {
#Override
public void configureContentNegotiation(ContentNegotiationConfigurer configurer) {
configurer.favorPathExtension(true).ignoreAcceptHeader(true).useJaf(false)
.defaultContentType(MediaType.APPLICATION_JSON).mediaType("html",
MediaType.TEXT_HTML)
.mediaType("xml", MediaType.APPLICATION_XML).mediaType("json",
MediaType.APPLICATION_JSON);
}
#Override
public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) {
configurer.enable();
}
#Bean(name = "contentNegotiatingViewResolver")
public ViewResolver getContentNegotiatingViewResolver(ContentNegotiationManager manager) {
ContentNegotiatingViewResolver resolver = new ContentNegotiatingViewResolver();
resolver.setContentNegotiationManager(manager);
return resolver;
}
#Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/**").addResourceLocations("/dist/").resourceChain(true)
.addResolver(new PathResourceResolver());
}
}
Following is my web.xml:
<?xml version="1.0" encoding="ISO-8859-1"?>
<web-app xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
version="3.0">
<display-name>Archetype Created Web Application</display-name>
<!-- Creates the Spring Container shared by all Servlets and Filters -->
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<!-- Processes application requests -->
<servlet>
<servlet-name>appServlet</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/applicationContext.xml</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>appServlet</servlet-name>
<url-pattern>/</url-pattern>
<url-pattern>*.html</url-pattern>
<url-pattern>/index.html</url-pattern>
</servlet-mapping>
<welcome-file-list>
<welcome-file>index.html</welcome-file>
</welcome-file-list>
<!-- Spring Security -->
<filter>
<filter-name>springSecurityFilterChain</filter-name>
<filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
</filter>
<filter-mapping>
<filter-name>springSecurityFilterChain</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
</web-app>
I am using weblogic server to deploy war file.
weblogic.xml
<?xml version="1.0" encoding="UTF-8"?>
<wls:weblogic-web-app
xmlns:wls="http://xmlns.oracle.com/weblogic/weblogic-web-app"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.oracle.com/weblogic/weblogic-web-app
http://xmlns.oracle.com/weblogic/weblogic-web-app/1.4/weblogic-web-app.xsd">
<wls:context-root>/</wls:context-root>
<wls:container-descriptor>
<wls:prefer-application-packages>
<wls:package-name>org.slf4j.*</wls:package-name>
<wls:package-name>org.springframework.*</wls:package-name>
<wls:package-name>org.springframework.web.servlet.view.*</wls:package-name>
<wls:package-name>oracle.core.*</wls:package-name>
<wls:package-name>oracle.jdbc.*</wls:package-name>
<wls:package-name>oracle.net.*</wls:package-name>
<wls:package-name>oracle.sql.*</wls:package-name>
<wls:package-name>oracle.security.*</wls:package-name>
<wls:package-name>org.hibernate.*</wls:package-name>
<wls:package-name>com.fasterxml.*</wls:package-name>
</wls:prefer-application-packages>
</wls:container-descriptor>
</wls:weblogic-web-app>
Angular Changes:
In app.module.ts introduced http interceptor to inject http header X-Requested-With as suggested here.
import { BrowserModule } from '#angular/platform-browser';
import { FormsModule } from '#angular/forms';
import { HttpModule } from '#angular/http';
import { RouterModule, Routes } from '#angular/router';
import { NgModule } from '#angular/core';
import { Injectable } from '#angular/core';
import {
HttpEvent, HttpInterceptor, HttpHandler, HttpRequest, HTTP_INTERCEPTORS
} from '#angular/common/http';
import { Observable } from 'rxjs/Observable';
import { AppComponent } from './app.component';
import { StoreComponent } from './components/store/store.component';
import { StoreService } from './services/store.service';
import { LoginComponent } from './components/login/login.component';
import { LogoutComponent } from './components/logout/logout.component';
#Injectable()
export class XhrInterceptor implements HttpInterceptor {
intercept(req: HttpRequest<any>, next: HttpHandler) {
const xhr = req.clone({
headers: req.headers.set('X-Requested-With', 'XMLHttpRequest')
});
console.log(">>>>>>>>>>HttpRequest intercepted...");
return next.handle(xhr);
}
}
const appRoutes: Routes = [
{ path:'', pathMatch: 'full', redirectTo: 'login' },
{ path: 'login', component: LoginComponent},
{ path: 'logout', component: LogoutComponent },
{ path: 'store', component: StoreComponent }
]
#NgModule({
declarations: [
AppComponent,
StoreComponent,
LoginComponent,
LogoutComponent
],
imports: [
BrowserModule,
FormsModule,
HttpModule,
RouterModule.forRoot(appRoutes)
],
providers: [ StoreService, { provide: HTTP_INTERCEPTORS, useClass: XhrInterceptor, multi: true } ],
bootstrap: [ AppComponent ]
})
export class AppModule { }
By some reason, still I am able to see the default user name and password pop up from browser.
In web console, I don't see the X-Requested-With header. Also, I don't see my console log which should have been printed while http request is intercepted.
I think, request might not be reaching angular client and something wrong happened in server side.
What could be missing here?
Update:
As suggested, I have replaced http.basic() request with http.authorizeRequests(). Now, pop up is not displayed for /login request. But after once I try to submit user credentials in my custom login page by calling /authenticate, again I see the pop up :(
What could be missing here.
Please clarify.
Thanks.
this is due to http.httpBasic() in your config, it should be http.authorizeRequests()
You are still using spring security's http basic authentication, that is why is it showing basic authentication pop up, dont use it , use http.autherizeRequest() and using machers permit your login url.
And in routemodule define same path for login component. It will work.
I have static resources' structure like this in my spring application :
\src
\main
\webapp
\resouces
\css\..
\js\..
and have configured VersionResourceResolver like this :
#Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/resources/**").addResourceLocations("/resources/")
.setCacheControl(CacheControl.maxAge(365, TimeUnit.DAYS)).resourceChain(true)
.addResolver(new VersionResourceResolver().addContentVersionStrategy("/**"));
}
I included the resources using jstl in my jsp:
<script type="text/javascript" src="<c:url value="/resources/js/my.js"/>"></script>
But when i run the application I cannot see any kind of versioning.
What am I missing ?
Also I have tried :
https://stackoverflow.com/a/38407644/3603806
But no luck
UPDATE
I am including resources like this :
<link rel="stylesheet" href="<c:url value="/resources/css/mycss.css?v=2" />">
<script type="text/javascript" src="<c:url value="/resources/js/myjs.js?v=1"/>"></script>
and this is what is getting generated :
<link rel="stylesheet" href="/app/resources/css/mycss.css?v=2">
<script type="text/javascript" src="/app/resources/js/myjs.js?v=1"></script>
index page using :
<welcome-file-list>
<welcome-file>/WEB-INF/views/index.jsp</welcome-file>
</welcome-file-list>
Controller : for home etc...
#RequestMapping(value = "/home", method = RequestMethod.GET)
public ModelAndView home(HttpServletRequest request, ModelMap model) {
...
}
Might be ResourceUrlProvider is null for you in encodeURL of ResourceUrlEncodingFilter.ResourceUrlEncodingResponseWrapper
#Override
public String encodeURL(String url) {
ResourceUrlProvider resourceUrlProvider = getResourceUrlProvider();
if (resourceUrlProvider == null) {
logger.debug("Request attribute exposing ResourceUrlProvider not found");
return super.encodeURL(url);
}
initIndexLookupPath(resourceUrlProvider);
if (url.length() >= this.indexLookupPath) {
String prefix = url.substring(0, this.indexLookupPath);
int suffixIndex = getQueryParamsIndex(url);
String suffix = url.substring(suffixIndex);
String lookupPath = url.substring(this.indexLookupPath, suffixIndex);
lookupPath = resourceUrlProvider.getForLookupPath(lookupPath);
if (lookupPath != null) {
return super.encodeURL(prefix + lookupPath + suffix);
}
}
return super.encodeURL(url);
}
To fix it, register ResourceUrlEncodingFilter in web.xml or in WebApplicationInitializer, as follows:
<filter>
<filter-name>resourceUrlEncodingFilter</filter-name>
<filter-class>
org.springframework.web.servlet.resource.ResourceUrlEncodingFilter
</filter-class>
</filter>
<filter-mapping>
<filter-name>resourceUrlEncodingFilter</filter-name>
<servlet-name>spring-dispatcher</servlet-name>
</filter-mapping>
where spring-dispatcher is DispatcherServlet, as:
<servlet>
<servlet-name>spring-dispatcher</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
And make sure your my jsp is rendered through spring-dispatcher, for example:
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
#Controller
public class HomeController {
#RequestMapping(value = "/my-jsp", method = RequestMethod.GET)
public String home() {
return "my";
}
}
I created a sample app available on GitHub - spring-resource-versioning, which can answer your further questions.
Hope it help!
I ran into same issue like 1 hour ago. I solved it by overriding this method:
#Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
VersionResourceResolver versionResolver = new VersionResourceResolver()
.addFixedVersionStrategy(version, "/**/*.js")
.addContentVersionStrategy(version, "/**/*.css", "/**/*.png"); //adjust those paths according to the webapp's folder names
registry.addResourceHandler("/res/**")
.addResourceLocations("/resources/")
.setCachePeriod(CACHE_SECONDS)
.resourceChain(true)
.addResolver(versionResolver);
}
As Arpit Aggarwal said, you need ResourceUrlEncodingFilter that corresponds to your main DispacherServlet. But adding it in java config didn't help me.
I solved this issue by replacing
#Bean
public ResourceUrlEncodingFilter resourceUrlEncodingFilter() {
return new ResourceUrlEncodingFilter();
}
with
#Bean
public FilterRegistrationBean<?> resourceUrlEncodingFilterRegistration() {
FilterRegistrationBean<ResourceUrlEncodingFilter> registration = new FilterRegistrationBean<>();
registration.setFilter(new ResourceUrlEncodingFilter());
registration.addUrlPatterns("/*");
return registration;
}
I am following the Atmosphere tutorial to get a basic chat application setup. Currently when running the javascript, it appears to be able to establish a connection to the server using web sockets. I get the following in the Chrome console:
Invoking executeWebSocket core.js:2298
Using URL: ws://localhost:8080/web-transport/chat?X-Atmosphere-tracking-id=0&X-Atmosphere-Framework=1.1&X-Atmosphere-Transport=websocket&X-Cache-Date=0&Content-Type=application/json core.js:2298
Websocket successfully opened
However, when I attempt to publish data (subSocket.push(...)) nothing seems to happen. I have tried debugging the AtmosphereHandlerService but it never seems to be hit (and no logs are being generated.
What am I missing that would cause this?
So far I have the following:
web.xml
<?xml version="1.0" encoding="UTF-8"?>
<web-app version="2.5" xmlns="http://java.sun.com/xml/ns/javaee" xmlns:j2ee="http://java.sun.com/xml/ns/javaee" 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.5.xsd">
<description>AtmosphereServlet</description>
<display-name>AtmosphereServlet</display-name>
<servlet>
<description>AtmosphereServlet</description>
<servlet-name>AtmosphereServlet</servlet-name>
<servlet-class>org.atmosphere.cpr.AtmosphereServlet</servlet-class>
<!-- If you want to use Servlet 3.0 -->
<!-- <async-supported>true</async-supported> -->
<!-- List of init-param -->
</servlet>
<servlet-mapping>
<servlet-name>AtmosphereServlet</servlet-name>
<!-- Any mapping -->
<url-pattern>/chat/*</url-pattern>
</servlet-mapping>
</web-app>
ChatAtmosphereHandler.java
#AtmosphereHandlerService(path = "/chat")
public class ChatAtmosphereHandler implements AtmosphereHandler {
public void onRequest(AtmosphereResource resource) throws IOException {
AtmosphereRequest request = resource.getRequest();
if (request.getMethod().equalsIgnoreCase("GET")) {
resource.suspend();
} else if (request.getMethod().equalsIgnoreCase("POST")) {
resource.getBroadcaster().broadcast(request.getReader().readLine().trim());
}
}
public void onStateChange(AtmosphereResourceEvent event) throws IOException {
AtmosphereResource resource = event.getResource();
AtmosphereResponse response = resource.getResponse();
if (resource.isSuspended()) {
response.getWriter().write("Hello");
switch (resource.transport()) {
case JSONP:
case LONG_POLLING:
event.getResource().resume();
break;
case WEBSOCKET:
case STREAMING:
response.getWriter().flush();
break;
}
} else if (!event.isResuming()) {
event.broadcaster().broadcast("bye bye");
}
}
public void destroy() {
}
}
Maven dependencies:
<dependency>
<groupId>org.atmosphere</groupId>
<artifactId>atmosphere-runtime</artifactId>
<version>1.0.2</version>
</dependency>
<dependency>
<groupId>org.atmosphere</groupId>
<artifactId>atmosphere-jersey</artifactId>
<version>1.0.2</version>
</dependency>
<dependency>
<groupId>eu.infomas</groupId>
<artifactId>annotation-detector</artifactId>
<version>3.0.1</version>
</dependency>
and finally the javascript:
$(document).ready(function() {
var socket = $.atmosphere;
var subSocket = null;
var request = {
url: 'http://localhost:8080/web-transport/' + 'chat',
contentType : "application/json",
logLevel : 'debug',
transport : 'websocket' ,
fallbackTransport: 'long-polling'
};
request.onOpen = function(response) {
console.log('onopen', response);
};
request.onReconnect = function (request, response) {
console.log('onreconnect', request, response);
};
request.onMessage = function (response) {
console.log('onmessage', response);
};
request.onError = function(response) {
console.log('onerror', response);
};
$('#send').click(function(){
subSocket.push(JSON.stringify({ author: 'me', message: 'hello' }));
});
$('#subscribe').click(function(){
subSocket = socket.subscribe(request);
});
});
I finally figured out that the problem wasn't with the code or atmosphere, but with Glassfish.
The solution is to enable web sockets using the following command:
asadmin set configs.config.server-config.network-config.protocols.protocol.http-listener-1.http.websockets-support-enabled=true
Make sure that you run the command using the command prompt. I previously enabled web sockets via the admin gui and somehow it seems to not have been applied correctly. After I ran the above command and tried the above code it worked fine.
I have a Spring Controller with several RequestMappings for different URIs. My servlet is "ui". The servlet's base URI only works with a trailing slash. I would like my users to not have to enter the trailing slash.
This URI works:
http://localhost/myapp/ui/
This one does not:
http://localhost/myapp/ui
It gives me a HTTP Status 404 message.
The servlet and mapping from my web.xml are:
<servlet>
<servlet-name>ui</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>ui</servlet-name>
<url-pattern>/ui/*</url-pattern>
</servlet-mapping>
My Controller:
#Controller
public class UiRootController {
#RequestMapping(value={"","/"})
public ModelAndView mainPage() {
DataModel model = initModel();
model.setView("intro");
return new ModelAndView("main", "model", model);
}
#RequestMapping(value={"/other"})
public ModelAndView otherPage() {
DataModel model = initModel();
model.setView("otherPage");
return new ModelAndView("other", "model", model);
}
}
Using Springboot, my app could reply both with and without trailing slash by setting #RequestMapping's "value" option to the empty string:
#RestController
#RequestMapping("/some")
public class SomeController {
// value = "/" (default) ,
// would limit valid url to that with trailing slash.
#RequestMapping(value = "", method = RequestMethod.GET)
public Collection<Student> getAllStudents() {
String msg = "getting all Students";
out.println(msg);
return StudentService.getAllStudents();
}
}
If your web application exists in the web server's webapps directory, for example webapps/myapp/ then the root of this application context can be accessed at http://localhost:8080/myapp/ assuming the default Tomcat port. This should work with or without the trailing slash, I think by default - certainly that is the case in Jetty v8.1.5
Once you hit /myapp the Spring DispatcherServlet takes over, routing requests to the <servlet-name> as configured in your web.xml, which in your case is /ui/*.
The DispatcherServlet then routes all requests from http://localhost/myapp/ui/ to the #Controllers.
In the Controller itself you can use #RequestMapping(value = "/*") for the mainPage() method, which will result in both http://localhost/myapp/ui/ and http://localhost/myapp/ui being routed to mainPage().
Note: you should also be using Spring >= v3.0.3 due to SPR-7064
For completeness, here are the files I tested this with:
src/main/java/controllers/UIRootController.java
package controllers;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.servlet.ModelAndView;
#Controller
public class UiRootController {
#RequestMapping(value = "/*")
public ModelAndView mainPage() {
return new ModelAndView("index");
}
#RequestMapping(value={"/other"})
public ModelAndView otherPage() {
return new ModelAndView("other");
}
}
WEB-INF/web.xml
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
version="3.0" metadata-complete="false">
<servlet>
<servlet-name>ui</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<load-on-startup>1</load-on-startup>
<!-- spring automatically discovers /WEB-INF/<servlet-name>-servlet.xml -->
</servlet>
<servlet-mapping>
<servlet-name>ui</servlet-name>
<url-pattern>/ui/*</url-pattern>
</servlet-mapping>
</web-app>
WEB-INF/ui-servlet.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:p="http://www.springframework.org/schema/p"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-3.0.xsd">
<context:component-scan base-package="controllers" />
<bean id="viewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver"
p:order="2"
p:viewClass="org.springframework.web.servlet.view.JstlView"
p:prefix="/WEB-INF/views/"
p:suffix=".jsp"/>
</beans>
And also 2 JSP files at WEB-INF/views/index.jsp and WEB-INF/views/other.jsp.
Result:
http://localhost/myapp/ -> directory listing
http://localhost/myapp/ui and http://localhost/myapp/ui/ -> index.jsp
http://localhost/myapp/ui/other and http://localhost/myapp/ui/other/ -> other.jsp
Hope this helps!
PathMatchConfigurer api allows you to configure various settings
related to URL mapping and path matching. As per the latest version of spring, trail path matching is enabled by default. For customization, check the below example.
For Java-based configuration
#Configuration
#EnableWebMvc
public class AppConfig extends WebMvcConfigurerAdapter {
#Override
public void configurePathMatch(PathMatchConfigurer configurer) {
configurer.setUseTrailingSlashMatch(true);
}
}
For XML-based configuration
<mvc:annotation-driven>
<mvc:path-matching trailing-slash="true"/>
</mvc:annotation-driven>
For #RequestMapping("/foo"), if trailing slash match set to false, example.com/foo/ != example.com/foo and if it's set to true (default), example.com/foo/ == example.com/foo
Cheers!
I eventually added a new RequestMapping to redirect the /ui requests to /ui/.
Also removed the empty string mapping from the mainPage's RequestMapping.
No edit required to web.xml.
Ended up with something like this in my controller:
#RequestMapping(value="/ui")
public ModelAndView redirectToMainPage() {
return new ModelAndView("redirect:/ui/");
}
#RequestMapping(value="/")
public ModelAndView mainPage() {
DataModel model = initModel();
model.setView("intro");
return new ModelAndView("main", "model", model);
}
#RequestMapping(value={"/other"})
public ModelAndView otherPage() {
DataModel model = initModel();
model.setView("otherPage");
return new ModelAndView("other", "model", model);
}
Now the URL http://myhost/myapp/ui redirects to http://myhost/myapp/ui/ and then my controller displays the introductory page.
Another solution I found is to not give the request mapping for mainPage() a value:
#RequestMapping
public ModelAndView mainPage() {
DataModel model = initModel();
model.setView("intro");
return new ModelAndView("main", "model", model);
}
try adding
#RequestMapping(method = RequestMethod.GET)
public String list() {
return "redirect:/strategy/list";
}
the result:
#RequestMapping(value = "/strategy")
public class StrategyController {
static Logger logger = LoggerFactory.getLogger(StrategyController.class);
#Autowired
private StrategyService strategyService;
#Autowired
private MessageSource messageSource;
#RequestMapping(method = RequestMethod.GET)
public String list() {
return "redirect:/strategy/list";
}
#RequestMapping(value = {"/", "/list"}, method = RequestMethod.GET)
public String listOfStrategies(Model model) {
logger.info("IN: Strategy/list-GET");
List<Strategy> strategies = strategyService.getStrategies();
model.addAttribute("strategies", strategies);
// if there was an error in /add, we do not want to overwrite
// the existing strategy object containing the errors.
if (!model.containsAttribute("strategy")) {
logger.info("Adding Strategy object to model");
Strategy strategy = new Strategy();
model.addAttribute("strategy", strategy);
}
return "strategy-list";
}
** credits:
Advanced #RequestMapping tricks – Controller root and URI Template
Not sure if this is the ideal approach, but what worked for me was to treat them as if they were two different paths and make them both accepted by each of my endpoints, such as.
#RestController
#RequestMapping("/api/mb/actor")
public class ActorController {
#GetMapping({"", "/"})
public ResponseEntity<Object> getAllActors() {
...
}
#GetMapping({"/{actorId}", "/{actorId}/"})
public ResponseEntity<Object> getActor(#PathVariable UUID actorId) {
...
}
There may be best ways to do this and to avoid this duplication, and I'd love to know that. However, what I found when I tried using configurer.setUseTrailingSlashMatch(true); is that broken paths also start becoming accepted, such as /api/mb////actor (with many slashs), and that's why I ended up going the multiple paths instead.