Spring 集成错误的主机 Header

Spring Integration Wrong Host Header

我们遇到了这个奇怪的问题,请求主机 header 被设置为应用程序的 URL。这导致外部系统无法处理我们的请求。

这是 localhost:8082 上托管的 Spring Integration application 的 apache 的 DEBUG 日志。暂时不要介意内容,但 Content-Type 将是随后的问题:

org.apache.http.wire - http-outgoing-0 >> "POST /health HTTP/1.1[\r][\n]"
org.apache.http.wire - http-outgoing-0 >> "Accept: */*[\r][\n]"
org.apache.http.wire - http-outgoing-0 >> "cookie: PHPSESSID=ost897ibh0j7rovf2rudr33c22[\r][\n]"
org.apache.http.wire - http-outgoing-0 >> "host: localhost:8082[\r][\n]"
org.apache.http.wire - http-outgoing-0 >> "connection: keep-alive[\r][\n]"
org.apache.http.wire - http-outgoing-0 >> "Cache-Control: no-cache[\r][\n]"
org.apache.http.wire - http-outgoing-0 >> "Content-Type: application/xml[\r][\n]"
org.apache.http.wire - http-outgoing-0 >> "accept-encoding: gzip, deflate[\r][\n]"
org.apache.http.wire - http-outgoing-0 >> "user-agent: PostmanRuntime/6.1.6[\r][\n]"
org.apache.http.wire - http-outgoing-0 >> "Content-Length: 4[\r][\n]"
org.apache.http.wire - http-outgoing-0 >> "[\r][\n]"
org.apache.http.wire - http-outgoing-0 >> "test"
org.apache.http.wire - http-outgoing-0 << "HTTP/1.1 200 OK[\r][\n]"
org.apache.http.wire - http-outgoing-0 << "Content-Type: application/json[\r][\n]"
org.apache.http.wire - http-outgoing-0 << "Transfer-Encoding: chunked[\r][\n]"
org.apache.http.wire - http-outgoing-0 << "Server: Jetty(9.2.z-SNAPSHOT)[\r][\n]"
org.apache.http.wire - http-outgoing-0 << "[\r][\n]"
org.apache.http.wire - http-outgoing-0 << "27[\r][\n]"
org.apache.http.wire - http-outgoing-0 << "{"success":true,"message":"Service up"}"
org.apache.http.headers - http-outgoing-0 << HTTP/1.1 200 OK
org.apache.http.headers - http-outgoing-0 << Content-Type: application/json
org.apache.http.headers - http-outgoing-0 << Transfer-Encoding: chunked
org.apache.http.headers - http-outgoing-0 << Server: Jetty(9.2.z-SNAPSHOT)
org.apache.http.wire - http-outgoing-0 << "[\r][\n]"
org.apache.http.wire - http-outgoing-0 << "0[\r][\n]"
org.apache.http.wire - http-outgoing-0 << "[\r][\n]"

这是用 curl 完成的请求:

curl -v -X POST http://localhost:9292/health
*   Trying ::1...
* TCP_NODELAY set
* Connected to localhost (::1) port 9292 (#0)
> POST /health HTTP/1.1
> Host: localhost:9292
> User-Agent: curl/7.54.0
> Accept: */*
> 
< HTTP/1.1 200 OK
< Content-Type: application/json
< Transfer-Encoding: chunked
< Server: Jetty(9.2.z-SNAPSHOT)

因此与 Spring 集成 org.apache.http.wire - http-outgoing-0 >> "host: localhost:8082[\r][\n]"

并且卷曲 > Host: localhost:9292

我们可以通过设置outgate的header映射器轻松解决Spring集成中的host问题: handler.setHeaderMapper(new DefaultHttpHeaderMapper());

org.apache.http.wire - http-outgoing-0 >> "POST /health HTTP/1.1[\r][\n]"
org.apache.http.wire - http-outgoing-0 >> "Accept: application/json, application/*+json[\r][\n]"
org.apache.http.wire - http-outgoing-0 >> "Content-Type: text/plain;charset=UTF-8[\r][\n]"
org.apache.http.wire - http-outgoing-0 >> "Content-Length: 4[\r][\n]"
org.apache.http.wire - http-outgoing-0 >> "Host: localhost:9292[\r][\n]"
org.apache.http.wire - http-outgoing-0 >> "Connection: Keep-Alive[\r][\n]"
org.apache.http.wire - http-outgoing-0 >> "User-Agent: Apache-HttpClient/4.5.2 (Java/1.8.0_112)[\r][\n]"
org.apache.http.wire - http-outgoing-0 >> "Accept-Encoding: gzip,deflate[\r][\n]"
org.apache.http.wire - http-outgoing-0 >> "[\r][\n]"
org.apache.http.wire - http-outgoing-0 >> "test"

所以我们能够用 Host header 解决问题,不幸的是,似乎通过 enricher 设置的 Content-Type 被忽略并被设置为 text/plain 而不是 application/xml.

这里似乎有什么问题?我假设这与仅使用基本的 DefaultHttpHeaderMapper 有关。

这是流程配置片段:

public class BasicIntegrationConfig {

    @Bean
    public MessageChannel basicRequestChannel() {
        return MessageChannels.publishSubscribe().get();
    }

    @Bean
    public MessageChannel basicResponseChannel() {
        return MessageChannels.publishSubscribe().get();
    }

    @Bean
    public MessagingGatewaySupport basicInGate() {
        HttpRequestHandlingMessagingGateway handler = new HttpRequestHandlingMessagingGateway();

        RequestMapping mapping = new RequestMapping();
        mapping.setPathPatterns("api/v1/basic");
        mapping.setMethods(HttpMethod.GET);
        mapping.setProduces(MediaType.APPLICATION_JSON_UTF8_VALUE);
        handler.setRequestMapping(mapping);

        handler.setRequestChannel(basicRequestChannel());
        handler.setReplyChannel(basicResponseChannel());

        return handler;
    }

    @Bean
    public MessageHandler basicOutGate() {
        HttpRequestExecutingMessageHandler handler =
                new HttpRequestExecutingMessageHandler("http://localhost:9292/health");
        handler.setHttpMethod(HttpMethod.POST);
        handler.setExpectedResponseType(BaseResponse.class);
        handler.setRequestFactory(new HttpComponentsClientHttpRequestFactory());
        handler.setHeaderMapper(new DefaultHttpHeaderMapper());
        return handler;
    }

    @Bean
    public IntegrationFlow basicFlow() {
        return (IntegrationFlowDefinition<?> f) -> f
                .channel(basicRequestChannel())
                .log("basic")
                .handle((GenericHandler<Object>) (o, map) -> "test")
                .enrichHeaders(e -> e.header(MessageHeaders.CONTENT_TYPE, MediaType.APPLICATION_XML_VALUE, true))
                .log("basic")
                .handle(basicOutGate())
                .log("basic")
                .enrichHeaders(e -> e.header(MessageHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_UTF8_VALUE, true))
                .channel(basicResponseChannel());
    }
}

使用

DefaultHttpHeaderMapper mapper = DefaultHttpHeaderMapper.outboundMapper();
mapper.setExcludedOutboundStandardRequestHeaderNames(new String[] { "Host" });
handler.setHeaderMapper(mapper);