Spring-MVC 3.1: How to map URLs with a trailing slash? - spring

I'm converting a legacy servlet application over to Spring 3.1. In the process some URLs are now obsolete. We've had some problems with our network that will not be resolved anytime soon. My boss doesn't want to trust that their redirects will always be operational. So, she asked me to put my own redirects into the webapp.
All works great, except that if a URL has a trailing slash Spring 3.1 will not find the Controller class function that handles it.
http://blah.blah.blah/acme/makedonation gets found, mapped and handled
http://blah.blah.blah/acme/makedonation/ does not
Here is the controller class I am using to handle my legacy URLs
import org.springframework.stereotype.Controller;
import org.springframework.validation.*;
import org.springframework.ui.ModelMap;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.SessionAttributes;
import org.apache.log4j.Logger;
#Controller
public class LegacyServletController {
private static final Logger logger = Logger.getLogger(LegacyServletController.class);
// Redirect these legacy screns "home", the login screen via the logout process
#RequestMapping({"makeadonation","contact","complain"})
public String home() {
logger.debug("started...");
return "redirect:logout";
}// end home()
}// end class LegacyServletController
I Googled around and found this Stack Overflow post that offers several suggestions, but I am new to Spring and do not understand enough of it to implement some of those suggestions. This one in particular sounds like it would be good fit with my needs:
spring 3.1 RequestMappingHandlerMapping allows you to set a
"useTrailingSlashMatch" property. By default it is true. I think
switching it to false would solve your issue,
Would anyone give me a basic example of how to that, quote me a URL that has such an example ( I had no luck with Google ) or point me to a better idea?
Thanks much in advance
Steve

you should configure your bean in context.xml,and set property.
or you can refer to link or spring doc section 16.4
example configuration
<bean class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping">
<property name="useTrailingSlashMatch" value="true">
</property>
</bean>

If you are using spring's Java #Configuration you can also just declare a #Bean like this:
#Bean
public RequestMappingHandlerMapping useTrailingSlash() {
return new RequestMappingHandlerMapping() {{ setUseTrailingSlashMatch(true); }};
}

Related

Registering a resteasy service in a Quarkus extension

I am currently attempting to write my first Quarkus extension, which needs to deploy a rest service. For that purpose I have been looking at the Quarkus tutorial for creating extensions here, and it is pretty close to what I need to do. The problem is that it shows a Servlet, and I would very much like to deploy a RestEasy(Jackson) service instead.
I have created an extension project and added the following service in the runtime module
package my.test.quarkus.extension;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.ws.rs.*;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
#Path("/mytest")
public class TestEndpoint {
private static Logger LOGGER = LoggerFactory.getLogger(TestEndpoint.class);
#POST
#Path("/service")
public void testService(){
LOGGER.info("Logit!");
}
}
But the service is not being picked up it seems, as my test returns 404.
package my.test.quarkus.extension.test;
import io.quarkus.test.QuarkusUnitTest;
import io.restassured.RestAssured;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension;
public class ExtensionTest {
#RegisterExtension
static final QuarkusUnitTest config = new QuarkusUnitTest()
.withEmptyApplication();
#Test
public void testGreeting() {
RestAssured.when().post("/mytest/service").then().statusCode(200);
}
}
I assume i need a BuildItem registering the service inside the deployment project of my extension, but my question is, which BuildItem do i use? Looking at the list of BuildItems there are loads of them, and even filtering those out that has nothing to do with RestEasy, still leaves over 50, most of them without JavaDoc.
If someone can help me with the problem at hand, simply registering a RestEasy service, as well as an interceptor, from an extension, that would be appreciated. And you will get bonus gratitude if you can explain exactly how to navigate through the Quarkus BuildItems when building extensions, because to me it just seems like a jungle of code, but I am guessing I am missing something.

Spring redirect happening to "http://...../login" instead of "https://...../login"

I have deployed a war file generated by spring boot application using oauth2 for single sign on using Azure App service (https only).
When I browse to the home page, the home page loads with a login button.
On clicking the login button a redirect is happening to "http://..../login" (/login is the default sso login path) Since my app service is https only, the http url does not work.
I have tried the redirect_uri settings in the application.property file, but it is not helping.
Has anybody faced this problem?
How can it solved?
I found a similar issue mentioned here
This problem happens when your Tomcat server is behind a proxy. The HTTPS requests terminate at the proxy and the proxy then uses HTTP protocol to communicate to your Tomcat server. You will face this if you deploy your code on cloud providers like Azure (App Service), etc.
For anyone facing this problem, here is the solution:
in application.properties file, add the following. Note: some of the properties have different names in Spring Boot 2.* versions.
security.oauth2.client.pre-established-redirect-uri=https://yourappurl.net/login
security.oauth2.client.registered-redirect-uri=https://yourappurl.net/login
security.oauth2.client.use-current-uri=false
server.tomcat.remote-ip-header=x-forwarded-for
server.tomcat.protocol-header=x-forwarded-proto
server.tomcat.use-relative-redirects=true
server.use-forward-headers=true
server.tomcat.internal-proxies=.*
In your SpringBootApplication class, add the following bean. With Spring Boot <= 2.1.x you had to provide a ForwardedHeaderFilter-Bean. Since Spring Boot 2.2.0 you don't have to do this anymore.
import org.springframework.core.Ordered;
import org.springframework.web.filter.ForwardedHeaderFilter;
#Bean
FilterRegistrationBean<ForwardedHeaderFilter> forwardedHeaderFilter() {
final FilterRegistrationBean<ForwardedHeaderFilter> filterRegistrationBean = new FilterRegistrationBean<ForwardedHeaderFilter>();
filterRegistrationBean.setFilter(new ForwardedHeaderFilter());
filterRegistrationBean.setOrder(Ordered.HIGHEST_PRECEDENCE);
return filterRegistrationBean;
}
Add the following line in configure method of your AppConfiguration class:
http.requiresChannel().anyRequest().requiresSecure();
For official info visit this page.
Ranbir has well explained the usecase where someone will face this problem.
I didnt have to add any additional properties to my springboot project. I just had to add ForwardedHeaderFilter as part of spring configuration and mentions the urls where I want this filter to act on x-forwarded header:
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import javax.servlet.DispatcherType;
import org.springframework.core.Ordered;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.web.filter.ForwardedHeaderFilter;
import java.security.Security;
import java.util.Arrays;
#SpringBootApplication(scanBasePackages = {"com.abc.xyz"})
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
log.info("--- Security Overrides ---");
log.info("networkaddress.cache.ttl =" + Security.getProperty("networkaddress.cache.ttl"));
}
#Bean
public FilterRegistrationBean<ForwardedHeaderFilter> forwardedHeaderFilter() {
ForwardedHeaderFilter filter = new ForwardedHeaderFilter();
FilterRegistrationBean<ForwardedHeaderFilter> registration = new FilterRegistrationBean<>(filter);
registration.setDispatcherTypes(javax.servlet.DispatcherType.REQUEST, DispatcherType.ASYNC, DispatcherType.ERROR);
registration.setOrder(Ordered.HIGHEST_PRECEDENCE);
registration.setUrlPatterns(Arrays.asList("/app/*", "/ping", "/oauth2/authorization/callback", "/oauth/callback"));
return registration;
}
}
Here are some useful references to follow:
https://tomgregory.com/spring-boot-behind-load-balancer-using-x-forwarded-headers/
https://docs.spring.io/spring-framework/docs/current/reference/html/web.html#filters-forwarded-headers
https://docs.spring.io/spring-boot/docs/2.0.1.RELEASE/reference/htmlsingle/#howto-enable-https

How to determine which default http client was injected by Spring container for #FeignClient?

I am using #FeignClient annotation. I want to know which httpclient was injected by Spring when i am running my application.
For example,
The OkHttpClient and ApacheHttpClient feign clients can be used by setting feign.okhttp.enabled or feign.httpclient.enabled to true, respectively, and having them on the classpath.
import org.springframework.cloud.netflix.feign.FeignClient;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
#FeignClient(name = "service", path = "/api/v1", configuration = ServiceConfiguration.class)
public interface ServiceClient {
#RequestMapping(method = RequestMethod.GET, value = "/test/{param1}", consumes = MediaType.APPLICATION_JSON_VALUE)
String test(#PathVariable("param1") String param);
}
I am not sure now which of these client is being injected since my application is complex with multiple httpclient libraries in the classpath.
Is there a way that i can monitor it ?
I enabled JMX and tried to view the jconsole Mbeans and there was no information about httpclients.
It's impossible to tell without seeing your Spring setup, especially if your application is complex like you said.
Since you are using Spring annotations to declare your #FeignClient you most likely are depending on spring-cloud-netflix-core. If you are enabling defaults with #EnableFeignClients take a look at FeignAutoConfiguration class. This class registers the actual HTTP client bean. If you add both feign.httpclient.enabled and feign.okhttp.enabled properties (which IMO is a weird setup) try debugging FeignAutoConfiguration to see which Client feignClient() bean will be registered in the Spring context.
Alternatively enable wire logging in all HTTP client libraries and see which one actually executes the request based on logs.
According to previous answer, now I see this row in FeignAutoConfiguration.java
#ConditionalOnProperty(value = "feign.httpclient.enabled", matchIfMissing = true)
So simple answer would be Apache Client by default if you don't add any properties

Google Remote API 404 on Springboot

I'm trying to connect a Spring Boot app running locally/not in Google app engine when deployed to my Google DataStore. I am using Objectify and Google Remote API as my understanding is that Objectify will only work if deployed in App engine(?). The problem is that the Google Remote API is throwing 404 when I try and communicate with the DataStore. I think I might have my config wrong in spring boot as the instructions on the google docs talk about setting up config in web.xml which I don't use on my JAR based spring boot app. I have created a SpringBootServletInitializer class to try and register the servlet (code at the bottom). I'm coming at this from an AWS perspective and so far I've found Google cloud to be a nightmare! Connecting to DynamoDB is so much simpler and I feel I must be missing something!
import com.google.apphosting.utils.remoteapi.RemoteApiServlet;
import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.boot.web.servlet.ServletRegistrationBean;
import org.springframework.boot.web.support.SpringBootServletInitializer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
/**
* Created by Thomas on 01/07/2017.
*/
#Configuration
public class GoogleRemoteApiConfig extends SpringBootServletInitializer {
protected SpringApplicationBuilder configure(SpringApplicationBuilder builder) {
return builder.sources(GoogleRemoteApiConfig.class, "/remote_api");
}
#Bean
public ServletRegistrationBean remoteApiRegistration() {
ServletRegistrationBean registration = new ServletRegistrationBean(new RemoteApiServlet(), "/remote_api");
Map<String,String> params = new HashMap<String,String>();
registration.setInitParameters(params);
registration.setLoadOnStartup(1);
registration.setUrlMappings(Arrays.asList("/remote_api"));
registration.setName("RemoteApiServlet");
return registration;
}
}
I'd fundamentally misunderstood how the remote-api worked, and didn't realise I needed an app running in app engine to proxy the requests on to Google DataStore. I'm still confused that there is no simple way to access DataStore from Java but this approach is at least working!

How to get Spring 3.1.1 works with App Engine datastore

Could everyone show me a tutorial on how to make Spring 3.1.1 running on Google App Engine, please?
I've followed severals tutorial and managed to get the dead simple HelloWorld example to work on App Engine. However, when I go futher, I stuck at the persistent process between Spring and Datastore. I looked at this thread Configuring JDO in Spring 3.1?, too, but it works on localhost but it doesn't work when I deploy to app engine due to the javax.naming.NamingException.
Therefore, I'm looking for a not-too-simple tutorial that covers basic aspects of a real life application such as the view, the model and the database.
Jappstart is a good place to see a working example of GAE that uses Spring and the Datastore (via JPA) and is also a good starting point for building a basic GAE/J app.
Having spent about a day trying to make this work, I thought I'd add some additional useful information here. First take a look at this project https://github.com/hleinone/spring-gae-jdo and this issue: http://code.google.com/p/googleappengine/issues/detail?id=1240 -- comment 24 is the useful one.
In case anyone wants to get this working with annotation-driven configuration, here's how I did it:
package com.domain.yourcode.configuration;
import javax.jdo.JDOHelper;
import javax.jdo.PersistenceManagerFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.orm.jdo.GAETransactionAwarePersistenceManagerFactoryProxy;
import org.springframework.orm.jdo.JdoTransactionManager;
//import org.springframework.orm.jdo.TransactionAwarePersistenceManagerFactoryProxy;
#Configuration
public class JDOConfiguration {
private static final PersistenceManagerFactory pmf = JDOHelper.getPersistenceManagerFactory("transactions-optional");
#Bean
public GAETransactionAwarePersistenceManagerFactoryProxy proxyPmf() {
GAETransactionAwarePersistenceManagerFactoryProxy proxy =
new GAETransactionAwarePersistenceManagerFactoryProxy();
proxy.setTargetPersistenceManagerFactory(pmf);
proxy.setAllowCreate(false);
return proxy;
}
#Bean
public JdoTransactionManager transactionManager() {
JdoTransactionManager mgr = new JdoTransactionManager();
mgr.setPersistenceManagerFactory(pmf);
return mgr;
}
}
You'll still want <tx:annotation-driven/> in your applicationContext.xml

Resources