

## 概要

StreamingResponseBodyでレスポンスを返そうとするとエラー発生。

[code]

    java.lang.NullPointerException: null
            at org.apache.tomcat.util.http.MimeHeaders.getValue(MimeHeaders.java:341) ~[tomcat-embed-core-9.0.21.jar:9.0.21]
            at org.apache.tomcat.util.http.MimeHeaders.getHeader(MimeHeaders.java:373) ~[tomcat-embed-core-9.0.21.jar:9.0.21]
            at org.apache.catalina.connector.Response.getHeader(Response.java:863) ~[tomcat-embed-core-9.0.21.jar:9.0.21]
            at org.apache.catalina.connector.ResponseFacade.getHeader(ResponseFacade.java:633) ~[tomcat-embed-core-9.0.21.jar:9.0.21]
            at javax.servlet.http.HttpServletResponseWrapper.getHeader(HttpServletResponseWrapper.java:241) ~[tomcat-embed-core-9.0.21.jar:9.0.21]
            at javax.servlet.http.HttpServletResponseWrapper.getHeader(HttpServletResponseWrapper.java:241) ~[tomcat-embed-core-9.0.21.jar:9.0.21]
            at javax.servlet.http.HttpServletResponseWrapper.getHeader(HttpServletResponseWrapper.java:241) ~[tomcat-embed-core-9.0.21.jar:9.0.21]
            at sun.reflect.GeneratedMethodAccessor34.invoke(Unknown Source) ~[na:na]
            at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:1.8.0_212]
            at java.lang.reflect.Method.invoke(Method.java:498) ~[na:1.8.0_212]
            at org.springframework.util.ReflectionUtils.invokeMethod(ReflectionUtils.java:282) ~[spring-core-5.1.8.RELEASE.jar:5.1.8.RELEASE]
            at org.springframework.security.web.header.writers.CacheControlHeadersWriter.hasHeader(CacheControlHeadersWriter.java:73) ~[spring-security-web-5.1.5.RELEASE.jar:5.1.5.RELEASE]
            at org.springframework.security.web.header.writers.CacheControlHeadersWriter.writeHeaders(CacheControlHeadersWriter.java:62) ~[spring-security-web-5.1.5.RELEASE.jar:5.1.5.RELEASE]
            at org.springframework.security.web.header.HeaderWriterFilter$HeaderWriterResponse.writeHeaders(HeaderWriterFilter.java:109) ~[spring-security-web-5.1.5.RELEASE.jar:5.1.5.RELEASE]
            at org.springframework.security.web.header.HeaderWriterFilter.doFilterInternal(HeaderWriterFilter.java:77) ~[spring-security-web-5.1.5.RELEASE.jar:5.1.5.RELEASE]
            at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:109) ~[spring-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.5.RELEASE.jar:5.1.5.RELEASE]
            at org.springframework.security.web.context.SecurityContextPersistenceFilter.doFilter(SecurityContextPersistenceFilter.java:105) ~[spring-security-web-5.1.5.RELEASE.jar:5.1.5.RELEASE]
            at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334) ~[spring-security-web-5.1.5.RELEASE.jar:5.1.5.RELEASE]
            at org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter.doFilterInternal(WebAsyncManagerIntegrationFilter.java:56) ~[spring-security-web-5.1.5.RELEASE.jar:5.1.5.RELEASE]
            at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:109) ~[spring-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.5.RELEASE.jar:5.1.5.RELEASE]
            at org.springframework.security.web.FilterChainProxy.doFilterInternal(FilterChainProxy.java:215) ~[spring-security-web-5.1.5.RELEASE.jar:5.1.5.RELEASE]
            at org.springframework.security.web.FilterChainProxy.doFilter(FilterChainProxy.java:178) ~[spring-security-web-5.1.5.RELEASE.jar:5.1.5.RELEASE]
            at org.springframework.web.filter.DelegatingFilterProxy.invokeDelegate(DelegatingFilterProxy.java:357) ~[spring-web-5.1.8.RELEASE.jar:5.1.8.RELEASE]
            at org.springframework.web.filter.DelegatingFilterProxy.doFilter(DelegatingFilterProxy.java:270) ~[spring-web-5.1.8.RELEASE.jar:5.1.8.RELEASE]
            at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193) ~[tomcat-embed-core-9.0.21.jar:9.0.21]
            at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) ~[tomcat-embed-core-9.0.21.jar:9.0.21]
            at org.springframework.web.filter.RequestContextFilter.doFilterInternal(RequestContextFilter.java:99) ~[spring-web-5.1.8.RELEASE.jar:5.1.8.RELEASE]
            at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:109) ~[spring-web-5.1.8.RELEASE.jar:5.1.8.RELEASE]
            at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193) ~[tomcat-embed-core-9.0.21.jar:9.0.21]
            at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) ~[tomcat-embed-core-9.0.21.jar:9.0.21]
            at org.springframework.web.filter.FormContentFilter.doFilterInternal(FormContentFilter.java:92) ~[spring-web-5.1.8.RELEASE.jar:5.1.8.RELEASE]
            at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:109) ~[spring-web-5.1.8.RELEASE.jar:5.1.8.RELEASE]
            at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193) ~[tomcat-embed-core-9.0.21.jar:9.0.21]
            at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) ~[tomcat-embed-core-9.0.21.jar:9.0.21]
            at org.springframework.web.filter.HiddenHttpMethodFilter.doFilterInternal(HiddenHttpMethodFilter.java:93) ~[spring-web-5.1.8.RELEASE.jar:5.1.8.RELEASE]
            at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:109) ~[spring-web-5.1.8.RELEASE.jar:5.1.8.RELEASE]
            at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193) ~[tomcat-embed-core-9.0.21.jar:9.0.21]
            at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) ~[tomcat-embed-core-9.0.21.jar:9.0.21]
            at org.zalando.logbook.servlet.SecurityStrategy.doFilter(SecurityStrategy.java:33) ~[logbook-servlet-1.13.0.jar:na]
            at org.zalando.logbook.servlet.LogbookFilter.doFilter(LogbookFilter.java:39) ~[logbook-servlet-1.13.0.jar:na]
            at org.zalando.logbook.servlet.HttpFilter.doFilter(HttpFilter.java:31) ~[logbook-servlet-1.13.0.jar:na]
            at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193) ~[tomcat-embed-core-9.0.21.jar:9.0.21]
            at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) ~[tomcat-embed-core-9.0.21.jar:9.0.21]
            at org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:200) ~[spring-web-5.1.8.RELEASE.jar:5.1.8.RELEASE]
            at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:109) ~[spring-web-5.1.8.RELEASE.jar:5.1.8.RELEASE]
            at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193) ~[tomcat-embed-core-9.0.21.jar:9.0.21]
            at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) ~[tomcat-embed-core-9.0.21.jar:9.0.21]
            at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:202) ~[tomcat-embed-core-9.0.21.jar:9.0.21]
            at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:96) [tomcat-embed-core-9.0.21.jar:9.0.21]
            at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:490) [tomcat-embed-core-9.0.21.jar:9.0.21]
            at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:139) [tomcat-embed-core-9.0.21.jar:9.0.21]
            at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:92) [tomcat-embed-core-9.0.21.jar:9.0.21]
            at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:74) [tomcat-embed-core-9.0.21.jar:9.0.21]
            at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:343) [tomcat-embed-core-9.0.21.jar:9.0.21]
            at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:408) [tomcat-embed-core-9.0.21.jar:9.0.21]
            at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:66) [tomcat-embed-core-9.0.21.jar:9.0.21]
            at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:853) [tomcat-embed-core-9.0.21.jar:9.0.21]
            at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1587) [tomcat-embed-core-9.0.21.jar:9.0.21]
            at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:49) [tomcat-embed-core-9.0.21.jar:9.0.21]
            at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149) [na:1.8.0_212]
            at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624) [na:1.8.0_212]
            at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61) [tomcat-embed-core-9.0.21.jar:9.0.21]
            at java.lang.Thread.run(Thread.java:748) [na:1.8.0_212]
    
    
[/code]

## 発生条件

  * Spring Securityでheaderにデフォルトで追加される設定は有効であること。
  * Spring boot 2系

## 現状分かっている事

  1. うまくいったりいかなかったり、確率で失敗する。
  2. HttpServletResponseでMimeHeaderのgetHeadersでheader(Map型)のキー名がnullで登録されてエラーになる。（なぜnullで登録される！？）
  3. StreamingResponseBody.writeToの処理でTread.sleep（1）をするとエラーを回避できる。
  4. log.infoとかログを仕込むだけでも回避できる。
  5. Spring SecurityのHttpSecurity httpの設定で `http.headers().disable();`を追加すると回避できる。

## 現状

Thread.sleepで回避できるがなんか気持ち悪いので原因の特定したい気持ちです。

## コード

都合上、エラーを発生させるための最低の内容だけ。

Controller

[code]

    @RestController
    public class StreamController {
        @GetMapping("/_streaming")
        public ResponseEntity<StreamingResponseBody> streaming(){
    
            return ResponseEntity.ok().contentType(MediaType.APPLICATION_JSON_UTF8).body(outputStream -> {
    // ここにThread.sleep(1)でエラーにならない。
                outputStream.flush();
    // ここで色々処理
            });
        }
    }
    
            // エラー確認用。この処理を実行するとエラーログが出力される。
            @GetMapping("rest")
            public void rest() {
    
                HttpEntity<String> requestEntity = new HttpEntity<>("");
                List<Runnable> rList = new ArrayList<>();
                for (int i = 0; i < 100; i++) {
                    Runnable runnable = () -> {
                        ResponseEntity<String> actual = restTemplate.exchange("http://localhost:8080/_streaming", HttpMethod.GET, requestEntity, String.class);
                    };
                    rList.add(runnable);
                }
                rList.parallelStream().forEach(r -> r.run());
            }
    
[/code]

Spring security config

[code]

    @Configuration
    @EnableWebSecurity
    public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
        @Override
        protected void configure(HttpSecurity http) throws Exception {
            http.authorizeRequests()
                    .antMatchers("/**").permitAll()
                    .anyRequest().authenticated();
        }
    }
    
[/code]

WebMvcConfiguration

[code]

    @Configuration
    @RequiredArgsConstructor
    @SuppressWarnings("javadoc")
    public class WebMvcConfiguration implements WebMvcConfigurer {
        @Override
        public void configureAsyncSupport(AsyncSupportConfigurer configurer) {
            ThreadPoolTaskExecutor asyncTaskExecutor = new ThreadPoolTaskExecutor();
            asyncTaskExecutor.setCorePoolSize(1);
            asyncTaskExecutor.setMaxPoolSize(1);
            asyncTaskExecutor.setQueueCapacity(40);
            asyncTaskExecutor.initialize();
            configurer.setTaskExecutor(asyncTaskExecutor);
        }
    }
    
[/code]


CC BY-SA 4.0
2019-12-25T09:44:06.447
61762
2020-09-01T05:20:05.853
2020-09-01T05:16:57.153
36900
36900
question
java spring-boot
Spring SecurityとStreamingResponseBodyを使用するとエラー


ちゃんとした対応ではないが自己解決

## 原因

処理で子スレッドに書き込みの処理を任せているのですが、親スレッドと子スレッドで同じHttpのヘッダーの変数を更新し合っているようです。

  1. 親スレッドで配列サイズ１０個でheader変数更新
  2. 子スレッドでflushでheader変数参照して、サイズ１０あるのを確認
  3. for文で10回回そうとする
  4. 上記処理中に親スレッドでheader変数を配列サイズ２個で更新
  5. 子スレッドで３つめ参照したらNullで返ってきてエラー！

という感じっぽいです。  
なので、Sleepでよくなるのは子スレッドでflushする前に親スレッドでHeaderの更新処理が完了したあとにするからエラーにならないはず。  
とりあえずflushするまでに親スレッドの処理が終わっていれば大丈夫

## 対応策

自分の場合はStreamingResponseBodyReturnValueHandlerと似た処理を作成追加して対応。  
子スレッドが処理終了するまで親スレッドは停止させるという方法で解決しました。  
これは非同期ではなくなるはずなので注意。自分の場合はStreamを利用したいというのが一番の目的なのでこれで対応した。  
レスポンスサイズで型を変えるという方法もありかも


CC BY-SA 4.0
2020-09-01T05:20:05.853
70038
2020-09-01T05:20:05.853
36900
61762
answer