AWS Лямбда-обработчик для Spring Boot не передает ServletContext - PullRequest
0 голосов
/ 25 марта 2020

При запуске приложения 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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...