Apache httpd + Spring Boot

# はじめに
Apache httpdとSpring Boot (Tomcat)連携のための設定、必要なクラスなどのメモ。

## バージョン
Apache httpd 2.4.39

```
$ /usr/local/bin/apachectl -v
Server version: Apache/2.4.39 (Unix)
Server built:   Apr 19 2019 17:53:55
```

Spring Boot v2.1.6.RELEASE

```
  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::        (v2.1.6.RELEASE)
```

# AjpによるApache httpdとTomcatの連携
## httpd.conf

```
LoadModule proxy_module lib/httpd/modules/mod_proxy.so
LoadModule proxy_ajp_module lib/httpd/modules/mod_proxy_ajp.so

 <IfModule proxy_ajp_module>
 Include /usr/local/etc/httpd/extra/httpd-proxy.conf
 </IfModule>

LoadModule unique_id_module lib/httpd/modules/mod_unique_id.so

<IfModule unique_id_module>
 RequestHeader set unique_id %{UNIQUE_ID}e
 </IfModule>
```

`unique_id_module`はリクエストを識別するIDを生成する。これをHTTPヘッダ`unique_id`で共有。

## httpd-proxy.conf

```
<Location /api/>
    ProxyPass ajp://XXXXX:8090/
    ProxyPassReverse ajp://XXXXX:8090/
</Location>
```

## Tomcat Ajp設定

```TomcatCustomizer.java
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory;
import org.springframework.boot.web.server.WebServerFactoryCustomizer;
import org.springframework.stereotype.Component;

@Component
public class TomcatCustomizer implements WebServerFactoryCustomizer<TomcatServletWebServerFactory> {

    @Value("${tomcat.ajp.port:0}")
    private int ajpPort;

    @Override
    public void customize(TomcatServletWebServerFactory factory) {
        if (ajpPort != 0) {
            factory.setProtocol("AJP/1.3");
            factory.setPort(ajpPort);
        }
    }
}
```

## SLF4J MDC設定
Apache httpdからHTTPヘッダで渡されたリクエストIDをMDCに格納。
もしなければ、代わりにランダムな文字列を生成する。

```MdcConfig.java
import java.util.Base64;
import java.util.Random;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.slf4j.MDC;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;

@Configuration
public class MdcConfig implements WebMvcConfigurer {
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new HandlerInterceptorAdapter() {
            private Random random = new Random();
            private static final String MDC_KEY_REQUEST_ID = "request_id";
            @Override
            public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
                    throws Exception {
                String requestId = request.getHeader("unique_id");
                if (requestId == null) {
                    requestId = generateRequestId();
                }
                MDC.put(MDC_KEY_REQUEST_ID, requestId);
                return true;
            }

            @Override
            public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler,
                    ModelAndView modelAndView) throws Exception {
                MDC.remove(MDC_KEY_REQUEST_ID);
            }

            private String generateRequestId() {
                byte[] bytes = new byte[18];
                random.nextBytes(bytes);
                return Base64.getEncoder().encodeToString(bytes);
            }
        }).addPathPatterns("/*");
    }
}
```

リクエストIDをログに含めるため、ログメッセージパターンに`%X{request_id}`を指定する。

```logback-spring.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE logback>

<configuration>

    <include resource="org/springframework/boot/logging/logback/base.xml"/>
    
    <appender name="MDC" class="ch.qos.logback.core.ConsoleAppender">
      <encoder>
        <pattern>%date{yyMMdd HH:mm:ss.SSS},%X{request_id},%m%n</pattern>
      </encoder>
    </appender>
    
    <logger name="MDC" level="INFO">
        <appender-ref ref="MDC" />
    </logger>
    
</configuration>
```

Apache httpdアクセスログに含めるには、`%{unique_id}e`

```httpd.conf
LogFormat "%h %l %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-Agent}i\" \"%{unique_id}e\"" combined
CustomLog "/usr/local/var/log/httpd/access_log" combined
```