При запуске приложения Spring Boot на AWS Lambda я получаю сообщение об ошибке java.lang.IllegalArgumentException: ServletContext must not be null
. Я не уверен, почему это происходит, и хотел бы получить дополнительную информацию.
Вот трассировка стека:
2020-03-25 19:52:09.444 ERROR 8 --- [ main] c.a.s.p.internal.LambdaContainerHandler : Error while handling request
java.lang.IllegalArgumentException: ServletContext must not be null
at org.springframework.util.Assert.notNull(Assert.java:198) ~[spring-core-5.1.14.RELEASE.jar:5.1.14.RELEASE]
at org.springframework.web.context.support.WebApplicationContextUtils.getWebApplicationContext(WebApplicationContextUtils.java:112) ~[spring-web-5.1.14.RELEASE.jar:5.1.14.RELEASE]
at org.springframework.web.context.support.WebApplicationContextUtils.getWebApplicationContext(WebApplicationContextUtils.java:101) ~[spring-web-5.1.14.RELEASE.jar:5.1.14.RELEASE]
at com.lukeshay.restapi.security.JwtAuthorizationFilter.doFilterInternal(JwtAuthorizationFilter.java:49) ~[task/:na]
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119) ~[spring-web-5.1.14.RELEASE.jar:5.1.14.RELEASE]
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334) ~[spring-security-web-5.1.8.RELEASE.jar:5.1.8.RELEASE]
at org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter.doFilter(AbstractAuthenticationProcessingFilter.java:200) ~[spring-security-web-5.1.8.RELEASE.jar:5.1.8.RELEASE]
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334) ~[spring-security-web-5.1.8.RELEASE.jar:5.1.8.RELEASE]
at org.springframework.security.web.authentication.logout.LogoutFilter.doFilter(LogoutFilter.java:116) ~[spring-security-web-5.1.8.RELEASE.jar:5.1.8.RELEASE]
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334) ~[spring-security-web-5.1.8.RELEASE.jar:5.1.8.RELEASE]
at org.springframework.web.filter.CorsFilter.doFilterInternal(CorsFilter.java:97) ~[spring-web-5.1.14.RELEASE.jar:5.1.14.RELEASE]
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119) ~[spring-web-5.1.14.RELEASE.jar:5.1.14.RELEASE]
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334) ~[spring-security-web-5.1.8.RELEASE.jar:5.1.8.RELEASE]
at org.springframework.security.web.header.HeaderWriterFilter.doFilterInternal(HeaderWriterFilter.java:74) ~[spring-security-web-5.1.8.RELEASE.jar:5.1.8.RELEASE]
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119) ~[spring-web-5.1.14.RELEASE.jar:5.1.14.RELEASE]
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334) ~[spring-security-web-5.1.8.RELEASE.jar:5.1.8.RELEASE]
at org.springframework.security.web.context.SecurityContextPersistenceFilter.doFilter(SecurityContextPersistenceFilter.java:105) ~[spring-security-web-5.1.8.RELEASE.jar:5.1.8.RELEASE]
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334) ~[spring-security-web-5.1.8.RELEASE.jar:5.1.8.RELEASE]
at org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter.doFilterInternal(WebAsyncManagerIntegrationFilter.java:56) ~[spring-security-web-5.1.8.RELEASE.jar:5.1.8.RELEASE]
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119) ~[spring-web-5.1.14.RELEASE.jar:5.1.14.RELEASE]
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334) ~[spring-security-web-5.1.8.RELEASE.jar:5.1.8.RELEASE]
at org.springframework.security.web.FilterChainProxy.doFilterInternal(FilterChainProxy.java:215) ~[spring-security-web-5.1.8.RELEASE.jar:5.1.8.RELEASE]
at org.springframework.security.web.FilterChainProxy.doFilter(FilterChainProxy.java:178) ~[spring-security-web-5.1.8.RELEASE.jar:5.1.8.RELEASE]
at org.springframework.web.filter.DelegatingFilterProxy.invokeDelegate(DelegatingFilterProxy.java:358) ~[spring-web-5.1.14.RELEASE.jar:5.1.14.RELEASE]
at org.springframework.web.filter.DelegatingFilterProxy.doFilter(DelegatingFilterProxy.java:271) ~[spring-web-5.1.14.RELEASE.jar:5.1.14.RELEASE]
at com.amazonaws.serverless.proxy.internal.servlet.FilterChainHolder.doFilter(FilterChainHolder.java:90) ~[aws-serverless-java-container-core-1.4.jar:na]
at org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:201) ~[spring-web-5.1.14.RELEASE.jar:5.1.14.RELEASE]
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119) ~[spring-web-5.1.14.RELEASE.jar:5.1.14.RELEASE]
at com.amazonaws.serverless.proxy.internal.servlet.FilterChainHolder.doFilter(FilterChainHolder.java:90) ~[aws-serverless-java-container-core-1.4.jar:na]
at com.lukeshay.restapi.config.CORSConfiguration.doFilter(CORSConfiguration.java:43) ~[task/:na]
at com.amazonaws.serverless.proxy.internal.servlet.FilterChainHolder.doFilter(FilterChainHolder.java:90) ~[aws-serverless-java-container-core-1.4.jar:na]
at com.amazonaws.serverless.proxy.internal.servlet.AwsLambdaServletContainerHandler.doFilter(AwsLambdaServletContainerHandler.java:150) ~[aws-serverless-java-container-core-1.4.jar:na]
at com.amazonaws.serverless.proxy.spring.SpringBootLambdaContainerHandler.handleRequest(SpringBootLambdaContainerHandler.java:143) ~[aws-serverless-java-container-springboot2-1.4.jar:na]
at com.amazonaws.serverless.proxy.spring.SpringBootLambdaContainerHandler.handleRequest(SpringBootLambdaContainerHandler.java:43) ~[aws-serverless-java-container-springboot2-1.4.jar:na]
at com.amazonaws.serverless.proxy.internal.LambdaContainerHandler.proxy(LambdaContainerHandler.java:211) ~[aws-serverless-java-container-core-1.4.jar:na]
at com.amazonaws.serverless.proxy.internal.LambdaContainerHandler.proxyStream(LambdaContainerHandler.java:246) ~[aws-serverless-java-container-core-1.4.jar:na]
at com.lukeshay.restapi.StreamLambdaHandler.handleRequest(StreamLambdaHandler.java:56) ~[task/:na]
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:na]
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(Unknown Source) ~[na:na]
at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source) ~[na:na]
at java.base/java.lang.reflect.Method.invoke(Unknown Source) ~[na:na]
at lambdainternal.EventHandlerLoader$StreamMethodRequestHandler.handleRequest(EventHandlerLoader.java:373) ~[aws-lambda-java-runtime-0.2.0.jar:na]
at lambdainternal.EventHandlerLoader$2.call(EventHandlerLoader.java:897) ~[aws-lambda-java-runtime-0.2.0.jar:na]
at lambdainternal.AWSLambda.startRuntime(AWSLambda.java:228) ~[aws-lambda-java-runtime-0.2.0.jar:na]
at lambdainternal.AWSLambda.startRuntime(AWSLambda.java:162) ~[aws-lambda-java-runtime-0.2.0.jar:na]
at lambdainternal.AWSLambda.main(AWSLambda.java:157) ~[aws-lambda-java-runtime-0.2.0.jar:na]
Мой исходный код можно найти здесь: https://github.com/LukeShay/route-rating-rest-api/tree/switch-to-lambda
Да, я знаю, что запуск Spring Boot на лямбде глуп, но это временно. Итак, мне не нужно платить за экземпляр EC2. Это также еще не уточнено, так что я знаю, что будут другие вещи, которые я должен изменить.
РЕДАКТИРОВАТЬ: Вот соответствующий код
Этот первый - обработчик лямбды. Первоначально у меня был закомментированный код, но я переключился на другой, потому что время запуска было значительно быстрее. Оба метода выдают одну и ту же ошибку.
Второй - это класс, где встречается ошибка, это мой фильтр авторизации. Я добавил комментарий // ERROR HERE
ко второму фрагменту, чтобы было легче увидеть точное местоположение того, куда выдается исключение.
package com.lukeshay.restapi;
import com.amazonaws.serverless.exceptions.ContainerInitializationException;
import com.amazonaws.serverless.proxy.internal.LambdaContainerHandler;
import com.amazonaws.serverless.proxy.model.AwsProxyRequest;
import com.amazonaws.serverless.proxy.model.AwsProxyResponse;
import com.amazonaws.serverless.proxy.spring.SpringBootLambdaContainerHandler;
import com.amazonaws.serverless.proxy.spring.SpringBootProxyHandlerBuilder;
import com.amazonaws.services.lambda.runtime.Context;
import com.amazonaws.services.lambda.runtime.RequestStreamHandler;
import com.google.common.base.Splitter;
import com.lukeshay.restapi.config.CORSConfiguration;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.time.Instant;
import java.util.EnumSet;
import javax.servlet.DispatcherType;
import javax.servlet.FilterRegistration;
import javax.ws.rs.core.Application;
public class StreamLambdaHandler implements RequestStreamHandler {
private static final Splitter SPLITTER = Splitter.on(',')
.trimResults()
.omitEmptyStrings();
private static SpringBootLambdaContainerHandler<AwsProxyRequest, AwsProxyResponse> handler;
// static {
// try {
// long startTime = Instant.now().toEpochMilli();
//
// LambdaContainerHandler.getContainerConfig().setInitializationTimeout(20_000);
//
// // handler =
// // SpringBootLambdaContainerHandler.getAwsProxyHandler(RestApiApplication.class);
//
// handler =
// new SpringBootProxyHandlerBuilder()
// .defaultProxy()
// .asyncInit(startTime)
// .springBootApplication(RestApiApplication.class)
// .profiles("lambda")
// .buildAndInitialize();
//
// // we use the onStartup method of the handler to register our custom filter
// handler.onStartup(
// servletContext -> {
// FilterRegistration.Dynamic registration =
// servletContext.addFilter("CORSConfiguration", CORSConfiguration.class);
// registration.addMappingForUrlPatterns(EnumSet.of(DispatcherType.REQUEST), true, "/*");
// });
// } catch (ContainerInitializationException e) {
// e.printStackTrace();
// throw new RuntimeException("Could not initialize Spring Boot application", e);
// }
// }
@Override
public void handleRequest(InputStream inputStream, OutputStream outputStream, Context context)
throws IOException {
if (handler == null) {
try {
String listOfActiveSpringProfiles = System.getenv("SPRING_PROFILES_ACTIVE");
if (listOfActiveSpringProfiles != null) {
handler = SpringBootLambdaContainerHandler.getAwsProxyHandler(Application.class,
splitToArray(listOfActiveSpringProfiles));
} else {
long startTime = Instant.now().toEpochMilli();
LambdaContainerHandler.getContainerConfig().setInitializationTimeout(20_000);
handler =
new SpringBootProxyHandlerBuilder()
.defaultProxy()
.asyncInit(startTime)
.springBootApplication(RestApiApplication.class)
.profiles("lambda")
.buildAndInitialize();
}
} catch (ContainerInitializationException e) {
// if we fail here. We re-throw the exception to force another cold start
e.printStackTrace();
throw new RuntimeException("Could not initialize Spring Boot application", e);
}
}
handler.proxyStream(inputStream, outputStream, context);
outputStream.flush();
outputStream.close();
}
private static String[] splitToArray(String activeProfiles) {
return SPLITTER.splitToList(activeProfiles).toArray(new String[0]);
}
}
package com.lukeshay.restapi.security;
import com.lukeshay.restapi.jwt.JwtService;
import com.lukeshay.restapi.jwt.JwtServiceImpl;
import com.lukeshay.restapi.session.SessionService;
import com.lukeshay.restapi.session.SessionServiceImpl;
import com.lukeshay.restapi.user.User;
import com.lukeshay.restapi.user.UserRepository;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.ExpiredJwtException;
import java.io.IOException;
import javax.servlet.FilterChain;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.web.authentication.www.BasicAuthenticationFilter;
import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.context.support.WebApplicationContextUtils;
public class JwtAuthorizationFilter extends BasicAuthenticationFilter {
private static Logger LOG = LoggerFactory.getLogger(JwtAuthorizationFilter.class.getName());
private UserRepository userRepository;
private JwtService jwtService;
private SessionService sessionService;
public JwtAuthorizationFilter(
AuthenticationManager authenticationManager, UserRepository userRepository) {
super(authenticationManager);
this.userRepository = userRepository;
}
@Override
protected void doFilterInternal(
HttpServletRequest request, HttpServletResponse response, FilterChain chain)
throws IOException, ServletException {
if (jwtService == null) {
ServletContext servletContext = request.getSession().getServletContext();
WebApplicationContext webApplicationContext =
WebApplicationContextUtils.getWebApplicationContext(servletContext); // ERROR HERE
jwtService = webApplicationContext.getBean(JwtServiceImpl.class);
sessionService = webApplicationContext.getBean(SessionServiceImpl.class);
}
String header = request.getHeader(SecurityProperties.JWT_HEADER_STRING);
if (header == null || !header.startsWith(SecurityProperties.TOKEN_PREFIX)) {
chain.doFilter(request, response);
return;
}
Authentication authentication = getUsernamePasswordAuthentication(request, response);
SecurityContextHolder.getContext().setAuthentication(authentication);
chain.doFilter(request, response);
}
private Authentication getUsernamePasswordAuthentication(
HttpServletRequest request, HttpServletResponse response) {
String jwtToken =
request
.getHeader(SecurityProperties.JWT_HEADER_STRING)
.replace(SecurityProperties.TOKEN_PREFIX, "");
if (jwtToken.trim().length() == 0) {
return null;
}
Claims jwtClaims;
try {
jwtClaims = jwtService.parseJwtToken(jwtToken);
} catch (ExpiredJwtException expiredJwtException) {
Claims refreshClaims;
try {
String refreshToken =
request
.getHeader(SecurityProperties.REFRESH_HEADER_STRING)
.replace(SecurityProperties.TOKEN_PREFIX, "");
refreshClaims = jwtService.parseJwtToken(refreshToken);
jwtClaims = expiredJwtException.getClaims();
if (refreshClaims.getId().equals(jwtClaims.getId())
&& refreshClaims.getSubject().equals(SecurityProperties.REFRESH_HEADER_STRING)) {
String newJwtToken = jwtService.buildToken(jwtClaims);
response.addHeader(
SecurityProperties.JWT_HEADER_STRING, SecurityProperties.TOKEN_PREFIX + newJwtToken);
}
} catch (ExpiredJwtException | NullPointerException ignored) {
LOG.debug("No refresh token present or refresh token is expired.");
jwtClaims = null;
}
}
UsernamePasswordAuthenticationToken authentication = null;
if (jwtClaims != null && jwtClaims.getSubject().equals(SecurityProperties.JWT_HEADER_STRING)) {
User user = userRepository.findById(jwtClaims.getId()).orElse(null);
if (user == null) {
return null;
}
UserPrincipal principal = new UserPrincipal(user);
LOG.debug("This user token is being created.");
authentication =
new UsernamePasswordAuthenticationToken(principal, null, principal.getAuthorities());
}
return authentication;
}
}
Для получения дополнительной информации мой исходный код можно найти здесь: https://github.com/LukeShay/route-rating-rest-api/tree/switch-to-lambda