I'm creating an application with spring boot and I want to authenticate my application using JWT but it is in microservice. In the JWT service it runs normally but I want to authenticate all the microservice services so I put the JWT service code for the api-gateway but because it uses spring-cloud-starter-gateway I can't use spring-boot-starter-web so far everything well. My big problem is that when I run some service, a login page appears. I wanted to remove this page does anyone have any idea how to do this?
Here is my application code below:
pom.xml:
<?xml version="1.0" encoding="UTF-8"?>
<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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.7.4</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.example</groupId>
<artifactId>routing</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>routing</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>17</java.version>
<spring-cloud.version>2021.0.4</spring-cloud.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
<version>3.1.4</version>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
<version>3.1.4</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-oauth2-resource-server</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
application.properties:
server.port=8080
spring.application.name=routing
eureka.client.serviceUrl.defaultZone=http://localhost:8761/eureka/
eureka.instance.hostname=localhost
spring.cloud.gateway.discovery.locator.enabled=true
rsa.publickey=classpath:carts/public.pem
rsa.privatekey=classpath:carts/private.pem
#spring.main.web-application-type=reactive
spring.cloud.gateway.enabled=true
spring.cloud.gateway.routes[0].id=user
spring.cloud.gateway.routes[0].uri=lb://USER
spring.cloud.gateway.routes[0].predicates=Path=/user/**
spring.cloud.gateway.routes[1].id=testes
spring.cloud.gateway.routes[1].uri=lb://TESTES
spring.cloud.gateway.routes[1].predicates=Path=/testes/**
spring.cloud.gateway.routes[2].id=user-create
spring.cloud.gateway.routes[2].uri=lb://USER-CREATE
spring.cloud.gateway.routes[2].predicates=Path=/user-create/**
spring.cloud.gateway.routes[3].id=jwt
spring.cloud.gateway.routes[3].uri=lb://JWT
spring.cloud.gateway.routes[3].predicates=Path=/**
RsaKeyPropreties.java:
package com.example.routing.config;
import java.security.interfaces.RSAPrivateKey;
import java.security.interfaces.RSAPublicKey;
import org.springframework.boot.context.properties.ConfigurationProperties;
#ConfigurationProperties(prefix = "rsa")
public record RsaKeyPropreties(RSAPublicKey publickey,RSAPrivateKey privatekey) {
}
SecurityConfig.java:
package com.example.routing.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configurers.oauth2.server.resource.OAuth2ResourceServerConfigurer;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.oauth2.jwt.JwtDecoder;
import org.springframework.security.oauth2.jwt.JwtEncoder;
import org.springframework.security.oauth2.jwt.NimbusJwtDecoder;
import org.springframework.security.oauth2.jwt.NimbusJwtEncoder;
import org.springframework.security.provisioning.InMemoryUserDetailsManager;
import org.springframework.security.web.SecurityFilterChain;
import com.nimbusds.jose.proc.SecurityContext;
import com.nimbusds.jose.jwk.JWK;
import com.nimbusds.jose.jwk.JWKSet;
import com.nimbusds.jose.jwk.RSAKey;
import com.nimbusds.jose.jwk.source.ImmutableJWKSet;
import com.nimbusds.jose.jwk.source.JWKSource;
import static org.springframework.security.config.Customizer.withDefaults;
import org.springframework.boot.autoconfigure.AutoConfiguration;
#Configuration
#AutoConfiguration
#EnableWebSecurity
public class SecurityConfig {
private final RsaKeyPropreties Rsakeys;
public SecurityConfig(RsaKeyPropreties Rsakeys) {
this.Rsakeys = Rsakeys;
}
#Bean
public InMemoryUserDetailsManager user(){
return new InMemoryUserDetailsManager(
User.withUsername("username")
.password("{noop}password")
.authorities("read")
.build()
);
}
#Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception{
return http
.csrf(csrf->csrf.disable())
//.authorizeRequests(auth->auth.antMatchers("/user**").authenticated())
.authorizeRequests(auth->auth.anyRequest().authenticated())
.oauth2ResourceServer(OAuth2ResourceServerConfigurer::jwt)
.sessionManagement(session->session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
.httpBasic(withDefaults()).build();
}
#Bean
JwtDecoder jwtDecoder() {
return NimbusJwtDecoder.withPublicKey(Rsakeys.publickey()).build();
}
#Bean
JwtEncoder jwtEncoder() {
JWK jwk = new RSAKey.Builder(Rsakeys.publickey()).privateKey(Rsakeys.privatekey()).build();
JWKSource<SecurityContext> jws = new ImmutableJWKSet<>(new JWKSet(jwk));
return new NimbusJwtEncoder(jws);
}
}
RoutingApplication.java:
package com.example.routing;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import com.example.routing.config.RsaKeyPropreties;
#EnableConfigurationProperties(RsaKeyPropreties.class)
#SpringBootApplication
#EnableDiscoveryClient
public class RoutingApplication {
public static void main(String[] args) {
SpringApplication.run(RoutingApplication.class, args);
}
}
Thanks!
For Servlet app, this will return 401 (unauthorized) instead of 302 (redirect to login) when authorization is missing or invalid:
http.exceptionHandling().authenticationEntryPoint((request, response, authException) -> {
response.addHeader(HttpHeaders.WWW_AUTHENTICATE, "Basic realm=\"Restricted Content\"");
response.sendError(HttpStatus.UNAUTHORIZED.value(), HttpStatus.UNAUTHORIZED.getReasonPhrase());
});
For a reactive app, you can provide a ServerAccessDeniedHandler bean instead:
#Bean
ServerAccessDeniedHandler serverAccessDeniedHandler() {
return (var exchange, var ex) -> exchange.getPrincipal().flatMap(principal -> {
final var response = exchange.getResponse();
response.setStatusCode(principal instanceof AnonymousAuthenticationToken ? HttpStatus.UNAUTHORIZED : HttpStatus.FORBIDDEN);
response.getHeaders().setContentType(MediaType.TEXT_PLAIN);
final var dataBufferFactory = response.bufferFactory();
final var buffer = dataBufferFactory.wrap(ex.getMessage().getBytes(Charset.defaultCharset()));
return response.writeWith(Mono.just(buffer)).doOnError(error -> DataBufferUtils.release(buffer));
});
}
Spring-boot starters I maintain here (which are thin wrappers arround spring-boot-starter-oauth2-resource-server) are doing that by default plus a few other usefull things:
map authorities from a list of claims of your choice (giving you hand on case and prefix)
stateless session-management (like you do)
disabled CSRF (only if session-management is left stateless)
fine grained CORS config from properties
multi-tenancy (accept more than just one JWT issuer)
As a side note, what about having your gateway being a pass-through for OAuth2 (just forward requests authorization header and responses HTTP status) and implement resources access-control (spring-security .authorizeRequests() and #PreAuthorize rules) on resource-server, where you can unit-test it?
I have a test project where I'm trying to setup e2e api tests using rest-assured. Tests run fine if I run them from the feature files, however, when I try to run them with maven, 0 tests run. I believe there is something funky with my pom.xml but I can't figure it out...
My project structure looks like:this
My pom.xml:
<?xml version="1.0" encoding="UTF-8"?>
<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>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.6.0</version>
</parent>
<artifactId>qa-automation-example</artifactId>
<version>0.0.1-SNAPSHOT</version>
<groupId>com</groupId>
<packaging>jar</packaging>
<properties>
<java.version>17</java.version>
<maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target>
</properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>io.cucumber</groupId>
<artifactId>cucumber-bom</artifactId>
<version>7.2.3</version>
<scope>import</scope>
<type>pom</type>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<dependency>
<groupId>org.junit.platform</groupId>
<artifactId>junit-platform-suite</artifactId>
<version>1.8.2</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>io.cucumber</groupId>
<artifactId>cucumber-java</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.cucumber</groupId>
<artifactId>cucumber-junit-platform-engine</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.cucumber</groupId>
<artifactId>cucumber-spring</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.rest-assured</groupId>
<artifactId>rest-assured</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.rest-assured</groupId>
<artifactId>json-path</artifactId>
</dependency>
<dependency>
<groupId>io.rest-assured</groupId>
<artifactId>xml-path</artifactId>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
</dependency>
</dependencies>
<build>
<pluginManagement>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<configuration>
<properties>
<configurationParameters>
cucumber.junit-platform.naming-strategy=long
</configurationParameters>
</properties>
</configuration>
</plugin>
</plugins>
</pluginManagement>
</build>
</project>
My Application.java
package com;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.PropertySource;
import org.springframework.context.annotation.PropertySources;
#PropertySources({
#PropertySource("classpath:application.properties")
})
#SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
My CucumberSpringConfiguration.class
import io.cucumber.spring.CucumberContextConfiguration;
import org.springframework.boot.test.context.SpringBootTest;
import com.Application;
#CucumberContextConfiguration
#SpringBootTest(classes = Application.class)
public class CucumberSpringConfiguration {
}
My CucumberTest.java
import org.junit.platform.suite.api.ConfigurationParameter;
import org.junit.platform.suite.api.IncludeEngines;
import org.junit.platform.suite.api.SelectClasspathResource;
import org.junit.platform.suite.api.Suite;
import static io.cucumber.junit.platform.engine.Constants.GLUE_PROPERTY_NAME;
#Suite
#IncludeEngines("cucumber")
#SelectClasspathResource("src/test/resources/example")
#ConfigurationParameter(key = GLUE_PROPERTY_NAME, value = "com.example")
public class CucumberTest {
}
I'm not really familiar with Spring though so I'm pretty sure I'm not using it correctly in my ApiTestStepDef.java
package com.example;
import io.cucumber.java.en.Then;
import io.cucumber.java.en.When;
import io.cucumber.java.en.Given;
import io.restassured.response.Response;
import io.restassured.specification.RequestSpecification;
import org.junit.jupiter.api.Assertions;
import org.springframework.beans.factory.annotation.Autowired;
import com.client.RestAssuredClient;
import com.model.User;
import com.utils.Helper;
import static io.restassured.RestAssured.given;
public class ApiTestStepDef {
private Response response;
private RequestSpecification request;
private User user;
private User responseBody;
#Autowired
private RestAssuredClient restAssuredClient;
#Given("{string} endpoint")
public void setBaseUsersURI(String url){
request =
given().log().all().
spec(restAssuredClient.createReqSpec(url));
}
#When("user posts request with details {string} {string} {string}")
public void sendRequest(String name, String gender, String status){
user = new User(name, gender, Helper.createRandomEmail(), status);
response =
request.given().log().all().
body(user).
when().
post().
then().log().all().
extract().response();
}
#Then("response status code is {int} and response contains correct user details")
public void checkResponseStatusCode(int statusCode){
response.then().spec(restAssuredClient.createResSpec(statusCode));
responseBody = response.getBody().as(User.class);
Assertions.assertEquals(user.getGender(), responseBody.getGender());
Assertions.assertEquals(user.getStatus(), responseBody.getStatus());
Assertions.assertEquals(user.getEmail(), responseBody.getEmail());
Assertions.assertEquals(user.getName(), responseBody.getName());
}
}
And RestAssuredClient.java
package com.client;
import io.restassured.builder.RequestSpecBuilder;
import io.restassured.builder.ResponseSpecBuilder;
import io.restassured.http.ContentType;
import io.restassured.specification.RequestSpecification;
import io.restassured.specification.ResponseSpecification;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import static io.restassured.RestAssured.oauth2;
#Component
public class RestAssuredClient {
#Value("${access.token}")
private String accessToken;
#Value("${base.uri}")
private String baseUri;
public ResponseSpecification createResSpec(int statusCode){
return
new ResponseSpecBuilder()
.expectStatusCode(statusCode)
.expectContentType(ContentType.JSON)
.build();
}
public RequestSpecification createReqSpec(String url){
return new RequestSpecBuilder()
.setBaseUri(baseUri)
.setContentType(ContentType.JSON)
.setAuth(oauth2(accessToken))
.setBasePath(url)
.build();
}
}
#SelectClasspathResource("src/test/resources/example")
Typically src/test/resources is not part of the classpath.
After running mvn test have a look at target/test-classes to understand the structure of what is on the classpath.
i'm having an issue when setting up the MockServerClient for multiple responses with the exact same request.
I read that with expectations with "Times" this might be done, but i coulnd't make it work with my scenario.
If you call the service with this JSON (twice):
{
"id": 1
}
The first response should be "passed true", the second "passed false"
Response 1:
{
"passed":true
}
Response 2:
{
"passed":false
}
I set up the first request, but how do i set the second one?
import com.nice.project.MyService;
import com.nice.project.MyPojo;
import org.mockito.Mock;
import org.mockserver.integration.ClientAndServer;
import org.mockserver.matchers.TimeToLive;
import org.mockserver.matchers.Times;
import org.mockserver.model.Header;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.test.context.TestPropertySource;
import java.io.File;
import java.nio.charset.StandardCharsets;
import java.time.Instant;
import java.util.Optional;
import java.util.concurrent.TimeUnit;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.ArgumentMatchers.contains;
import static org.mockito.Mockito.when;
import static org.mockserver.integration.ClientAndServer.startClientAndServer;
import static org.mockserver.model.HttpRequest.request;
import static org.mockserver.model.HttpResponse.response;
#SpringBootTest
public class Tests{
private static final int PORT = 9998;
private static ClientAndServer mockServer;
#Autowired
private MyService myService;
#BeforeAll
public void init(){
mockServer = startClientAndServer(PORT);
mockServer
.when(
request()
.withPath(testUrlValidateTransactionOk).withMethod(HttpMethod.POST.name())
.withHeaders(
new Header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON.toString())
)
.withBody(contains("\"id\":\"1\""))
).respond(
response().withStatusCode(HttpStatus.OK.value())
.withHeaders(
new Header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON.toString())
)
.withBody("{\"passed\":true}"));
// What do i set here? Or in the snippet before by chaining?
// mockServer.when()...
}
#Test
void t1{
//myService will internally call the MockServer
//FIRST CALL -> Pass
MyPojo p = myService.call(1);
assertThat(p.isPassed()).isEqualTo(Boolean.TRUE);
//SECOND CALL -> No Pass
MyPojo p2 = myService.call(1);
assertThat(p2.isPassed()).isEqualTo(Boolean.FALSE);
}
}
Dependencies (relevant):
<?xml version="1.0" encoding="UTF-8"?>
<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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.4.4</version>
</parent>
<groupId>com.nice.project</groupId>
<artifactId>testing</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>test</name>
<description>Testing</description>
<properties>
<java.version>1.8</java.version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<httpclient.version>4.5.13</httpclient.version>
<mock-server.version>5.11.2</mock-server.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-json</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
<!--HTTP CLIENT-->
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
</dependency>
<!--TEST-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-engine</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.mock-server</groupId>
<artifactId>mockserver-netty</artifactId>
<version>${mock-server.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.mock-server</groupId>
<artifactId>mockserver-client-java</artifactId>
<version>${mock-server.version}</version>
<scope>test</scope>
</dependency>
</dependencies>
</project>
Thank you in advance.
After following and diving into the documentation and testing.
I found that you can specify a "Times" that an expectation will be match
which solves perfectly my problem.
Link: https://www.mock-server.com/mock_server/creating_expectations.html
For every expectation i used "Times.exactly(1)".
The way this works is that an expectation is added to the list, when it's matched
it will be consumed, and removed from the list, leaving the following ones.
If no expectation is found for a call it will return a 404 from the mock server.
Link for examples from documentation:
https://www.mock-server.com/mock_server/creating_expectations.html#button_match_request_by_path_exactly_twice
Correct code:
//The first call will land here, and then this expectation will be deleted, remaining the next one
mockServer
.when(
request()
.withPath(testUrlValidateTransactionOk).withMethod(HttpMethod.POST.name())
.withHeaders(
new Header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON.toString())
)
.withBody(
json("{\"id\":1}",
MatchType.ONLY_MATCHING_FIELDS)),
Times.exactly(1)
).respond(
response().withStatusCode(HttpStatus.OK.value())
.withHeaders(
new Header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON.toString())
)
.withBody("{\"passed\":true}"));
//After the first call this will be consumed and removed, leaving no expectations
mockServer
.when(
request()
.withPath(testUrlValidateTransactionOk).withMethod(HttpMethod.POST.name())
.withHeaders(
new Header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON.toString())
)
.withBody(
json("{\"id\":1}",
MatchType.ONLY_MATCHING_FIELDS)),
Times.exactly(1)
).respond(
response().withStatusCode(HttpStatus.OK.value())
.withHeaders(
new Header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON.toString())
)
.withBody("{\"passed\":false}"));
You can create a sequence of responses by wrapping the when/request/response behavior in a method and calling it multiple times, like this:
private void whenValidateTransactionReturn(boolean isPassed) {
mockServer
.when(
request()
.withPath(testUrlValidateTransactionOk)
.withMethod(HttpMethod.POST.name())
.withHeaders(
new Header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON.toString()))
.withBody(contains("\"id\":\"1\"")))
.respond(
response()
.withStatusCode(HttpStatus.OK.value())
.withHeaders(
new Header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON.toString()))
.withBody("{\"passed\":" + isPassed + "}"));
}
Then you can call this method multiple times:
#Test
void testValidationFailsSecondTime() {
whenValidateTransactionReturn(true);
whenValidateTransactionReturn(false);
//
// Test logic
//
// mockServer.verify(...);
}
I am trying to implement Bucket4J rate limiter in Netflix Zuul Api Gateway. I have added Interceptor for Rate Limiting the requests using WebMvcConfigurer.
package com.rajkumar.apiigateway;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.cloud.netflix.zuul.EnableZuulProxy;
import org.springframework.context.annotation.Lazy;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import com.rajkumar.apiigateway.ratelimit.interceptor.RateLimitInterceptor;
#SpringBootApplication
#EnableZuulProxy
public class ApiiGatewayApplication implements WebMvcConfigurer{
#Autowired
#Lazy
RateLimitInterceptor rateLimitInterceptor;
public static void main(String[] args) {
new SpringApplicationBuilder(ApiiGatewayApplication.class)
.run(args);
}
#Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(rateLimitInterceptor)
.addPathPatterns("/api/service_1/throttling/users");
}
}
And Interceptor for rate limiting looks like
package com.rajkumar.apiigateway.ratelimit.interceptor;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;
import com.rajkumar.apiigateway.ratelimit.service.RateLimitService;
import io.github.bucket4j.Bucket;
import io.github.bucket4j.ConsumptionProbe;
#Component
public class RateLimitInterceptor implements HandlerInterceptor {
private static final String HEADER_API_KEY = "X-API-KEY";
private static final String HEADER_LIMIT_REMAINING = "X-RATE-LIMIT-REMAINING";
private static final String HEADER_RETRY_AFTER = "X-RATE-LIMIT-RETRY-AFTER-SECONDS";
#Autowired
RateLimitService rateLimitService;
#Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception {
String apiKey = request.getHeader(HEADER_API_KEY);
if(apiKey == null || apiKey.isEmpty()) {
response.sendError(HttpStatus.OK.value(), HEADER_API_KEY+" request header is mandatory");
return false;
}
Bucket tokenBucket = rateLimitService.resolveBucket(request.getHeader(HEADER_API_KEY));
ConsumptionProbe probe = tokenBucket.tryConsumeAndReturnRemaining(1);
if(probe.isConsumed()) {
response.addHeader(HEADER_LIMIT_REMAINING, Long.toString(probe.getRemainingTokens()));
return true;
}
response.addHeader(HEADER_RETRY_AFTER, Long.toString(probe.getNanosToWaitForRefill()/1000000000));
response.sendError(HttpStatus.TOO_MANY_REQUESTS.value(),"You have exceeded your request limit");
return false;
}
}
and other dependent component
package com.rajkumar.apiigateway.ratelimit.service;
import java.util.concurrent.TimeUnit;
import org.springframework.stereotype.Component;
import com.github.benmanes.caffeine.cache.Caffeine;
import com.github.benmanes.caffeine.cache.LoadingCache;
import com.rajkumar.apiigateway.ratelimit.RateLimit;
import io.github.bucket4j.Bucket;
import io.github.bucket4j.Bucket4j;
#Component
public class RateLimitService {
private LoadingCache<String, Bucket> cache = Caffeine.newBuilder()
.expireAfterWrite(1, TimeUnit.MINUTES)
.build(this::newBucket);
public Bucket resolveBucket(String apiKey) {
return cache.get(apiKey);
}
private Bucket newBucket(String apiKey) {
RateLimit plan = RateLimit.resolvePlanFromApiKey(apiKey);
Bucket bucket = Bucket4j.builder()
.addLimit(plan.getLimit())
.build();
return bucket;
}
}
package com.rajkumar.apiigateway.ratelimit;
import java.time.Duration;
import io.github.bucket4j.Bandwidth;
import io.github.bucket4j.Refill;
public enum RateLimit {
FREE(2L),
BASIC(4L),
PROFESSIONAL(10L);
private Long tokens;
private RateLimit(Long tokens) {
this.tokens = tokens;
}
public static RateLimit resolvePlanFromApiKey(String apiKey) {
if(apiKey==null || apiKey.isEmpty()) {
return FREE;
}
else if(apiKey.startsWith("BAS-")) {
return BASIC;
}
else if(apiKey.startsWith("PRO-")) {
return PROFESSIONAL;
}
return FREE;
}
public Bandwidth getLimit() {
return Bandwidth.classic(tokens, Refill.intervally(tokens, Duration.ofMinutes(1)));
}
}
and pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.3.4.RELEASE</version>
<relativePath /> <!-- lookup parent from repository -->
</parent>
<groupId>com.rajkumar</groupId>
<artifactId>apii-gateway</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>apii-gateway</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
<spring-cloud.version>Hoxton.SR8</spring-cloud.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-zuul</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>com.github.ben-manes.caffeine</groupId>
<artifactId>caffeine</artifactId>
<!-- <version>2.5.5</version> -->
</dependency>
<dependency>
<groupId>com.github.vladimir-bukhtoyarov</groupId>
<artifactId>bucket4j-core</artifactId>
<version>4.10.0</version>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
and application.properties
server.port = 8080
spring.application.name = api-gateway
#routing for service 1
zuul.routes.service_1.path = /api/service_1/**
zuul.routes.service_1.url = http://localhost:8081/
#routing for service 2
zuul.routes.service_2.path = /api/service_2/**
zuul.routes.service_2.url = http://localhost:8082/
When I am trying to hit api gateway (http://localhost:8080/api/service_1/throttling/users): it is not passing through the interceptor. Any help is appreciated.
Thanks in advance.
After searching out all the possible solutions I'm posting this out.
My spring boot application was running fine few days ago. I was able to deploy it on tomcat and also export the war and deploy it on my application server. But suddenly it is showing blank page on deployment. What could be the possible reasons?
In pom.xml I've all the dependencies:
<?xml version="1.0" encoding="UTF-8"?>
<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>com.sdigital</groupId>
<artifactId>cheapTravelTicket</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>war</packaging>
<name></name>
<description>cheap Travel Ticket</description>
<!-- Default from spring boot -->
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.4.3.RELEASE</version>
</parent>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<java.version>1.8</java.version>
</properties>
<dependencies>
<!-- Web Application -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- Spring data jpa -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<!-- Spring Webservices -->
<dependency>
<groupId>org.springframework.ws</groupId>
<artifactId>spring-ws-core</artifactId>
</dependency>
<dependency>
<groupId>xml-apis</groupId>
<artifactId>xml-apis</artifactId>
<version>1.4.01</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.w3c/dom -->
<dependency>
<groupId>org.w3c</groupId>
<artifactId>dom</artifactId>
<version>2.3.0-jaxb-1.0.6</version>
</dependency>
<!-- https://mvnrepository.com/artifact/sax/sax -->
<dependency>
<groupId>sax</groupId>
<artifactId>sax</artifactId>
<version>2.0.1</version>
</dependency>
<!-- Embedded Tomcat -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
<scope>provided</scope>
</dependency>
<!-- Slug generator -->
<dependency>
<groupId>com.github.slugify</groupId>
<artifactId>slugify</artifactId>
<version>2.1.7</version>
</dependency>
<!-- Common String -->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.4</version>
</dependency>
<!-- Apache Commons IO -->
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>2.4</version>
</dependency>
<!-- JSTL for JSP -->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>jstl</artifactId>
<scope>provided</scope>
</dependency>
<!-- Need this to compile JSP -->
<dependency>
<groupId>org.apache.tomcat.embed</groupId>
<artifactId>tomcat-embed-jasper</artifactId>
<scope>provided</scope>
</dependency>
<!-- Need this to compile JSP,
tomcat-embed-jasper version is not working, no idea why -->
<dependency>
<groupId>org.eclipse.jdt.core.compiler</groupId>
<artifactId>ecj</artifactId>
<version>4.6.1</version>
<scope>provided</scope>
</dependency>
<!-- Optional, test for static content, bootstrap CSS-->
<dependency>
<groupId>org.webjars</groupId>
<artifactId>bootstrap</artifactId>
<version>3.3.7</version>
</dependency>
<dependency>
<groupId>org.postgresql</groupId>
<artifactId>postgresql</artifactId>
</dependency>
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
<version>4.3.5</version>
</dependency>
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpcore</artifactId>
<version>4.4.6</version>
</dependency>
<!-- https://mvnrepository.com/artifact/com.jcabi/jcabi-xml -->
<dependency>
<groupId>com.jcabi</groupId>
<artifactId>jcabi-xml</artifactId>
<version>0.18.1</version>
</dependency>
<!-- https://mvnrepository.com/artifact/joda-time/joda-time -->
<dependency>
<groupId>joda-time</groupId>
<artifactId>joda-time</artifactId>
<version>2.3</version>
</dependency>
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
<version>2.2.4</version>
</dependency>
</dependencies>
<build>
<plugins>
<!-- Spring boot maven support -->
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
Application class is as follows:
package com.sdigital;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import com.sdigital.cache.ExpiringMap;
/**
* The Class CTTApplication.
*/
#SpringBootApplication
public class CTTApplication {
/**
* The main method.
*boots the application
*
*/
public static void main(String[] args) {
SpringApplication.run(CTTApplication.class, args);
}
#Bean
public Map<String, Object> getCache(){
Map<String, Object> map = ExpiringMap.builder()
.maxSize(200)
.expiration(12, TimeUnit.HOURS)
.build();
return map;
}
}
ServletInitializer
package com.sdigital;
import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.boot.context.web.SpringBootServletInitializer;
// TODO: Auto-generated Javadoc
/**
* The Class ServletInitializer.
*/
public class ServletInitializer extends SpringBootServletInitializer {
/* (non-Javadoc)
* SpringApplication Builder
*/
#Override
protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
return application.sources(CTTApplication.class);
}
}
Controller
package com.sdigital.controller;
import java.net.URISyntaxException;
import java.util.Date;
import java.util.List;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.SessionAttributes;
import org.springframework.web.servlet.ModelAndView;
import com.sdigital.bus.distribution.domain.ConnectionRequest;
import com.sdigital.cache.SearchCache;
import com.sdigital.domain.JourneyRequest;
import com.sdigital.domain.TimeTableRequest;
import com.sdigital.domain.XmlUrl;
import com.sdigital.domain.XmlUrlSet;
import com.sdigital.model.BusProvider;
import com.sdigital.model.City;
import com.sdigital.model.CustomForm;
import com.sdigital.model.Route;
import com.sdigital.model.SeoConfig;
import com.sdigital.model.SiteMap;
import com.sdigital.repositories.BusProviderRepository;
import com.sdigital.repositories.CityRepository;
import com.sdigital.repositories.RouteRepository;
import com.sdigital.repositories.SeoConfigRepository;
import com.sdigital.repositories.SiteMapRepository;
import com.sdigital.repositories.StationConfigRepository;
import com.sdigital.service.AppService;
import com.sdigital.utils.CTTConstant;
import com.sdigital.utils.CommonUtils;
#Controller
#SessionAttributes({"searchRequest", "searchBusRequest"})
public class AppController {
#Autowired
private AppService appServc;
#Autowired
private StationConfigRepository strepo;
#Autowired
private RouteRepository rtrepo;
#Autowired
private CityRepository ctrepo;
#Autowired
private BusProviderRepository busrepo;
#Autowired
private SeoConfigRepository repo;
#Autowired
private SearchCache searchCache;
#Autowired
private SiteMapRepository siteMap;
#RequestMapping("/")
public ModelAndView home(){
//List<Route> cttPopular = rtrepo.findOneByPopularType(CTTConstant.CTT_POPULAR_TYPE);
List<City> cttPopular = ctrepo.findAllByOrderByPriorityAsc();
List<Route> popular = rtrepo.findByPopularType(CTTConstant.POPULAR_TYPE);
ModelAndView model = new ModelAndView("index");
model.addObject("cttPopular", cttPopular);
model.addObject("popular", popular);
SeoConfig seoCon = repo.findByPageType(3);
model.addObject("title", seoCon.getPageTitle());
model.addObject("meta_title", seoCon.getMetaTitle());
model.addObject("meta_description", seoCon.getMetaDescription());
model.addObject("meta_keywords", seoCon.getMetaKeywords());
model.addObject("seo_url", seoCon.getSeoUrl());
model.addObject("description", seoCon.getDescription());
model.addObject("desc1", seoCon.getDesc1());
model.addObject("desc2", seoCon.getDesc2());
model.addObject("desc3", seoCon.getDesc3());
List<City> topCities = ctrepo.findTop10ByOrderByPriorityAsc();
List<Route> topRoutes = rtrepo.findTop10ByOrderByPriorityAsc();
model.addObject("topCities", topCities);
model.addObject("topRoutes", topRoutes);
List<BusProvider> topBusProviders = busrepo.findTop10ByPublishOrderByPriorityAsc(true);
model.addObject("topBusProviders", topBusProviders);
return model;
}
#RequestMapping(value = "/book")
public ModelAndView portalBook(#ModelAttribute("journeyRequest") JourneyRequest journeyRequest) throws URISyntaxException {
ModelAndView m = null;
m = appServc.getView(journeyRequest);
return m;
}
#RequestMapping(value="/search")
public ModelAndView portalSearch(#ModelAttribute("searchRequest") TimeTableRequest searchRequest,
#RequestParam(name="sort_by", required = false) String sortBy,
#RequestParam(name="s", required = false) String start,
#RequestParam(name="orig", required = false) String originuic,
#RequestParam(name="dest", required = false) String destinationuic, HttpSession session){
/*System.out.println("*** Session data ***");
Enumeration<String> e = session.getAttributeNames();
while (e.hasMoreElements()){
String s = e.nextElement();
System.out.println(s);
System.out.println("**" + session.getAttribute(s));
}*/
if(originuic!=null && destinationuic!=null){
searchRequest.setChildren(0);
searchRequest.setRailcards(null);
searchRequest.setTravelclass("standard");
searchRequest.setRetdatetime(null);
searchRequest.setAdults(1);
searchRequest.setDestinationuic(destinationuic);
searchRequest.setOriginuic(originuic);
searchRequest.setReturnjry(false);
searchRequest.setOutdatetime(CommonUtils.getFormatedDate(CTTConstant.DEFAULT_SEARCH_FORMAT, new Date()));
searchRequest.setOutsearchTime(CommonUtils.getCurrentUTCTimeSearchable(false));
}
ModelAndView m = appServc.getView(searchRequest, sortBy, start);
List<City> topCities = ctrepo.findTop10ByOrderByPriorityAsc();
List<Route> topRoutes = rtrepo.findTop10ByOrderByPriorityAsc();
m.addObject("topCities", topCities);
m.addObject("topRoutes", topRoutes);
List<BusProvider> topBusProviders = busrepo.findTop10ByPublishOrderByPriorityAsc(true);
m.addObject("topBusProviders", topBusProviders);
m.addObject("currentDate", CommonUtils.getFormatedDate(CTTConstant.DEFAULT_SEARCH_FORMAT, new Date()));
return m;
}
#ModelAttribute("searchRequest")
public TimeTableRequest createFormModelAttribute(HttpSession session) {
TimeTableRequest req = (TimeTableRequest)session.getAttribute("searchRequest");
if(req!=null)
return req;
TimeTableRequest req1 = new TimeTableRequest();
req1.setAdults(1);
req1.setOutdatetime(CommonUtils.getFormatedDate(CTTConstant.DEFAULT_SEARCH_FORMAT, new Date()));
req1.setOutsearchTime(CommonUtils.getCurrentUTCTimeSearchable(false));
req1.setRetsearchTime(CommonUtils.getCurrentUTCTimeSearchable(true));
return req1;
}
#RequestMapping(value="/bus-search")
public ModelAndView portalSearch(#ModelAttribute("searchBusRequest") ConnectionRequest searchRequest,
#RequestParam(name="sort_by", required = false) String sortBy,
#RequestParam(name="s", required = false) String start,
#RequestParam(name="orig", required = false) String originuic,
#RequestParam(name="dest", required = false) String destinationuic, HttpSession session){
if(originuic!=null && destinationuic!=null){
searchRequest.setChildren1(0);
searchRequest.setRetdatetime(null);
searchRequest.setAdults1(1);
searchRequest.setDestinationuic1(destinationuic);
searchRequest.setOriginuic1(originuic);
searchRequest.setReturnjry(false);
searchRequest.setCurrency("EUR");
searchRequest.setDatetime(CommonUtils.getFormatedDate(CTTConstant.DEFAULT_SEARCH_FORMAT, new Date()));
}
ModelAndView m = appServc.getView(searchRequest, sortBy, start);
List<City> topCities = ctrepo.findTop10ByOrderByPriorityAsc();
List<Route> topRoutes = rtrepo.findTop10ByOrderByPriorityAsc();
m.addObject("topCities", topCities);
m.addObject("topRoutes", topRoutes);
List<BusProvider> topBusProviders = busrepo.findTop10ByPublishOrderByPriorityAsc(true);
m.addObject("topBusProviders", topBusProviders);
m.addObject("currentDate", CommonUtils.getFormatedDate(CTTConstant.DEFAULT_SEARCH_FORMAT, new Date()));
return m;
}
#ModelAttribute("searchBusRequest")
public ConnectionRequest createFormModelAttribute() {
return new ConnectionRequest();
}
#ModelAttribute("journeyRequest")
public JourneyRequest createFormModelAttribute1() {
return new JourneyRequest();
}
#RequestMapping(value="/{seo:.+}")
public ModelAndView portalLocation(#PathVariable("seo") String seo, HttpServletResponse response, #RequestParam(name="status", required = false) String status){
ModelAndView m = appServc.getView(seo, response, status);
List<City> topCities = ctrepo.findTop10ByOrderByPriorityAsc();
List<Route> topRoutes = rtrepo.findTop10ByOrderByPriorityAsc();
m.addObject("topCities", topCities);
m.addObject("topRoutes", topRoutes);
List<BusProvider> topBusProviders = busrepo.findTop10ByPublishOrderByPriorityAsc(true);
m.addObject("topBusProviders", topBusProviders);
return m;
}
#RequestMapping(value="/{seo1}/{seo2:.+}")
public ModelAndView portalChildLocation(#PathVariable("seo1") String seo1, #PathVariable("seo2") String seo2, HttpServletResponse response, #RequestParam(name="status", required = false) String status){
ModelAndView m = appServc.getView(seo1+"/"+seo2, response, status);
List<City> topCities = ctrepo.findTop10ByOrderByPriorityAsc();
List<Route> topRoutes = rtrepo.findTop10ByOrderByPriorityAsc();
m.addObject("topCities", topCities);
m.addObject("topRoutes", topRoutes);
List<BusProvider> topBusProviders = busrepo.findTop10ByPublishOrderByPriorityAsc(true);
m.addObject("topBusProviders", topBusProviders);
return m;
}
#ResponseBody
#RequestMapping(value="/schedular/trains")
public boolean portalSchedularToUpdateTrains(){
appServc.updateTrainsForCityToCity();
return true;
}
#RequestMapping(value = "/robots.txt")
#ResponseBody
public String getRobots(HttpServletRequest request) {
return "User-agent: * \n Disallow: /";
}
#RequestMapping(value = "/sitemap.xml", method = RequestMethod.GET)
#ResponseBody
public XmlUrlSet getSiteMap(HttpServletRequest request) {
XmlUrlSet xmlUrlSet = new XmlUrlSet();
create(xmlUrlSet, "", "1.0", request);
List<SiteMap> seoUrls = siteMap.findByPublish(1);
for(SiteMap sUrl : seoUrls){
create(xmlUrlSet, "/"+sUrl.getSeoUrl(), null, request);
}
return xmlUrlSet;
}
private void create(XmlUrlSet xmlUrlSet, String link, String priority, HttpServletRequest request) {
String rootUrl = request.getScheme() + "://" + request.getServerName() + request.getContextPath();
xmlUrlSet.addUrl(new XmlUrl(rootUrl + link, priority));
}
#RequestMapping(value="/information/rail-cards")
public ModelAndView railCardInfo(){
ModelAndView model = new ModelAndView("railinfo");
return model;
}
#RequestMapping(value="/form/post")
public String formPost(#ModelAttribute("addRequest") CustomForm addRequest){
String r = appServc.addData(addRequest);
return r;
}
#ExceptionHandler(Exception.class)
public ModelAndView handleError(HttpServletRequest req, Exception ex) {
ModelAndView m = new ModelAndView("exception");
List<City> topCities = ctrepo.findTop10ByOrderByPriorityAsc();
List<Route> topRoutes = rtrepo.findTop10ByOrderByPriorityAsc();
m.addObject("topCities", topCities);
m.addObject("topRoutes", topRoutes);
List<BusProvider> topBusProviders = busrepo.findTop10ByPublishOrderByPriorityAsc(true);
m.addObject("topBusProviders", topBusProviders);
m.addObject("searchRequest", new TimeTableRequest());
return m;
}
}
Adding my answer in case anyone else faces same issue.
Problem was with the context path. Updated the context path in project properties in eclipse. then ran clean and build. Most important part was to remove the server configuration and again ran it on tomcat, it worked.
Since I was not clearing the server configurations from eclipse updated context path was not picking up.