代码之家  ›  专栏  ›  技术社区  ›  Vincent F

由于request.getSession()为null,随机NullPointerException/onErrorDroped使用webClient

  •  0
  • Vincent F  · 技术社区  · 4 年前

    我有一个Spring Boot(2.5)应用程序,我需要在其中对远程系统(一个Solr实例,我在其中存储一个非规范化视图)进行REST调用,我可以在其中创建或更新记录。

    我真的不在乎我得到的响应(有时远程系统响应很慢),所以我在中进行了这样的异步调用 createIndexForTicket / updateIndexForTicket :

    public MyService(WebClient webClient, String solrUpdateUrl) {
        this.webClient = webClient;
        this.solrUpdateUrl = solrUpdateUrl;
    }
    
      public void createIndexForTicket(TicketIndex ticketIndex) {
        // build the request
        var createRequest = webClient.post()
            .uri(solrUpdateUrl);
    
        triggerRequest(createRequest, ticketIndex,"creation");
    
        log.info("payload sent, creating index for ticket {} : {}",ticketIndex.getUserFriendlyTicketId(),ticketIndex);
      }
    
      
      public void updateIndexForTicket(TicketIndex ticketIndex) {
        // build the request
        var updateRequest = webClient.put()
            .uri(solrUpdateUrl + "/" + ticketIndex.getInternalTicketId());
    
        triggerRequest(updateRequest, ticketIndex,"update");
    
        log.info("payload sent, updating index for ticket {} : {}",ticketIndex.getUserFriendlyTicketId(),ticketIndex);
      }
    
      private static void triggerRequest(RequestBodySpec requestToSolr,
                                                                     TicketIndex ticketIndex,
                                                                      String action) {
    
        requestToSolr.bodyValue(ticketIndex)
            .retrieve()
            .onStatus(HttpStatus::is2xxSuccessful,
                      resp -> logSuccess(ticketIndex,action))
            .bodyToMono(String.class)
            .doOnError(t ->
                log.error("problem while performing a "+action+", "
                    + "calling Solr for ticket "+ticketIndex.getUserFriendlyTicketId(),t))
            .subscribe();
      }
    

    它大多数时候都很好用。但我注意到我有时会 Operator called default onErrorDropped 错误,堆栈跟踪如下:

    reactor.core.Exceptions$ErrorCallbackNotImplemented: java.lang.NullPointerException
    Caused by: java.lang.NullPointerException: null
        at org.springframework.security.oauth2.client.web.HttpSessionOAuth2AuthorizedClientRepository.saveAuthorizedClient(HttpSessionOAuth2AuthorizedClientRepository.java:63)
        Suppressed: reactor.core.publisher.FluxOnAssembly$OnAssemblyException: 
    Error has been observed at the following site(s):
        |_ checkpoint ⇢ Request to PUT https://myRemoteSolrSystem/services/v2/tickets/dGlja2V0aW5nLXNlcnZpY2UxNDEzNzM1 [DefaultWebClient]
    Stack trace:
            at org.springframework.security.oauth2.client.web.HttpSessionOAuth2AuthorizedClientRepository.saveAuthorizedClient(HttpSessionOAuth2AuthorizedClientRepository.java:63)
            at org.springframework.security.oauth2.client.web.AuthenticatedPrincipalOAuth2AuthorizedClientRepository.saveAuthorizedClient(AuthenticatedPrincipalOAuth2AuthorizedClientRepository.java:92)
            at org.springframework.security.oauth2.client.web.DefaultOAuth2AuthorizedClientManager.lambda$new$0(DefaultOAuth2AuthorizedClientManager.java:126)
            at org.springframework.security.oauth2.client.web.DefaultOAuth2AuthorizedClientManager.authorize(DefaultOAuth2AuthorizedClientManager.java:184)
            at org.springframework.security.oauth2.client.web.reactive.function.client.ServletOAuth2AuthorizedClientExchangeFilterFunction.lambda$authorizeClient$24(ServletOAuth2AuthorizedClientExchangeFilterFunction.java:552)
            at reactor.core.publisher.MonoSupplier.call(MonoSupplier.java:86)
            at reactor.core.publisher.FluxSubscribeOnCallable$CallableSubscribeOnSubscription.run(FluxSubscribeOnCallable.java:227)
            at reactor.core.scheduler.SchedulerTask.call(SchedulerTask.java:68)
            at reactor.core.scheduler.SchedulerTask.call(SchedulerTask.java:28)
            at java.base/java.util.concurrent.FutureTask.run(FutureTask.java:264)
            at java.base/java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(ScheduledThreadPoolExecutor.java:304)
            at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1128)
            at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:628)
            at java.base/java.lang.Thread.run(Thread.java:834)
    

    查看源代码,我发现 spring-security-oauth2-client 5.5.1,英寸 HttpSessionOAuth2AuthorizedClientRepository.saveAuthorizedClient

    @Override
    public void saveAuthorizedClient(OAuth2AuthorizedClient authorizedClient, Authentication principal,
            HttpServletRequest request, HttpServletResponse response) {
        Assert.notNull(authorizedClient, "authorizedClient cannot be null");
        Assert.notNull(request, "request cannot be null");
        Assert.notNull(response, "response cannot be null");
        Map<String, OAuth2AuthorizedClient> authorizedClients = this.getAuthorizedClients(request);
        authorizedClients.put(authorizedClient.getClientRegistration().getRegistrationId(), authorizedClient);
        request.getSession().setAttribute(this.sessionAttributeName, authorizedClients);
    }
    

    l.63,发生异常的是最后一个:

    request.getSession().setAttribute(this.sessionAttributeName, authorizedClients);
    

    所以看起来 request.getSession() 返回null。。。但我不知道为什么,我也找不到模式。有时我从同一个线程连续调用两个,一个成功,另一个不成功。。有时两者都失败了,有时两者都成功了。其他时候,我只触发一个调用,但它失败了,而另一个线程或多或少同时执行类似的操作,它就工作了。

    被注入的webClient是这样构建的:

    @Bean
    @Primary
    WebClient servletWebClient(ClientRegistrationRepository clientRegistrations,
                             OAuth2AuthorizedClientRepository authorizedClients) {
    
    var oauth = new ServletOAuth2AuthorizedClientExchangeFilterFunction(clientRegistrations, authorizedClients);
    
    oauth.setDefaultClientRegistrationId("keycloak");
    
    return WebClient.builder()
        .defaultHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE)
        .apply(oauth.oauth2Configuration())
        .build();
    }
    

    关于我做得不对的地方,或者我可以尝试更好地理解正在发生的事情,有什么提示吗?

    谢谢

    0 回复  |  直到 4 年前
        1
  •  0
  •   Vincent F    4 年前

    以下是似乎有效的变通方法:

    声明一个threadExecutor:

     private final ExecutorService solrRequestExecutor = Executors.newSingleThreadExecutor();
    

    然后,通过它进行异步调用:

    private void triggerRequest(RequestBodySpec requestToSolr,
                                                                 TicketIndex ticketIndex,
                                                                  String action) {
    
    // performing calls to Solr asynchronously
    solrRequestExecutor.submit(
        () ->
    requestToSolr.bodyValue(ticketIndex)
        .retrieve()
        .onStatus(HttpStatus::is2xxSuccessful,
                  resp -> logSuccess(ticketIndex,action))
        .bodyToMono(String.class)
        .doOnError(t ->
            log.error("problem while performing a "+action+", "
                + "calling Solr for ticket "+ticketIndex.getUserFriendlyTicketId(),t))
        .block());
    }
    

    由于这不再在同一线程中执行,因此必须正确配置webClient,否则我们将获得 servletRequest cannot be null 错误看见

    test a Spring Boot WebClient outside of a HttpServletRequest context

    我仍然不确定为什么原始代码会随机失败。。。只有当远程端点也是反应式端点时,它才能工作吗?