diff --git a/httpclient5/src/main/java/org/apache/hc/client5/http/impl/async/H2AsyncClientBuilder.java b/httpclient5/src/main/java/org/apache/hc/client5/http/impl/async/H2AsyncClientBuilder.java index 41c6e5f0e..4cb691c8f 100644 --- a/httpclient5/src/main/java/org/apache/hc/client5/http/impl/async/H2AsyncClientBuilder.java +++ b/httpclient5/src/main/java/org/apache/hc/client5/http/impl/async/H2AsyncClientBuilder.java @@ -752,7 +752,9 @@ public class H2AsyncClientBuilder { execChainDefinition.addFirst(entry.interceptor, entry.name); break; case LAST: - execChainDefinition.addLast(entry.interceptor, entry.name); + // Don't add last, after H2AsyncMainClientExec, as that does not delegate to the chain + // Instead, add the interceptor just before it, making it effectively the last interceptor + execChainDefinition.addBefore(ChainElement.MAIN_TRANSPORT.name(), entry.interceptor, entry.name); break; } } diff --git a/httpclient5/src/main/java/org/apache/hc/client5/http/impl/async/HttpAsyncClientBuilder.java b/httpclient5/src/main/java/org/apache/hc/client5/http/impl/async/HttpAsyncClientBuilder.java index adb341d22..2b9ca48ab 100644 --- a/httpclient5/src/main/java/org/apache/hc/client5/http/impl/async/HttpAsyncClientBuilder.java +++ b/httpclient5/src/main/java/org/apache/hc/client5/http/impl/async/HttpAsyncClientBuilder.java @@ -959,7 +959,9 @@ public class HttpAsyncClientBuilder { execChainDefinition.addFirst(entry.interceptor, entry.name); break; case LAST: - execChainDefinition.addLast(entry.interceptor, entry.name); + // Don't add last, after HttpAsyncMainClientExec, as that does not delegate to the chain + // Instead, add the interceptor just before it, making it effectively the last interceptor + execChainDefinition.addBefore(ChainElement.MAIN_TRANSPORT.name(), entry.interceptor, entry.name); break; } } diff --git a/httpclient5/src/main/java/org/apache/hc/client5/http/impl/classic/HttpClientBuilder.java b/httpclient5/src/main/java/org/apache/hc/client5/http/impl/classic/HttpClientBuilder.java index 42120b319..2a6ad0ec9 100644 --- a/httpclient5/src/main/java/org/apache/hc/client5/http/impl/classic/HttpClientBuilder.java +++ b/httpclient5/src/main/java/org/apache/hc/client5/http/impl/classic/HttpClientBuilder.java @@ -930,7 +930,9 @@ public class HttpClientBuilder { execChainDefinition.addFirst(entry.interceptor, entry.name); break; case LAST: - execChainDefinition.addLast(entry.interceptor, entry.name); + // Don't add last, after MainClientExec, as that does not delegate to the chain + // Instead, add the interceptor just before it, making it effectively the last interceptor + execChainDefinition.addBefore(ChainElement.MAIN_TRANSPORT.name(), entry.interceptor, entry.name); break; } } diff --git a/httpclient5/src/test/java/org/apache/hc/client5/http/impl/classic/TestHttpClientBuilderInterceptors.java b/httpclient5/src/test/java/org/apache/hc/client5/http/impl/classic/TestHttpClientBuilderInterceptors.java new file mode 100644 index 000000000..83f6928d1 --- /dev/null +++ b/httpclient5/src/test/java/org/apache/hc/client5/http/impl/classic/TestHttpClientBuilderInterceptors.java @@ -0,0 +1,112 @@ +/* + * ==================================================================== + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * . + * + */ +package org.apache.hc.client5.http.impl.classic; + +import java.io.IOException; + +import org.apache.hc.client5.http.classic.ExecChain; +import org.apache.hc.client5.http.classic.ExecChainHandler; +import org.apache.hc.client5.http.classic.ExecChain.Scope; +import org.apache.hc.client5.http.classic.methods.HttpPost; +import org.apache.hc.client5.http.impl.io.PoolingHttpClientConnectionManagerBuilder; +import org.apache.hc.client5.http.io.HttpClientConnectionManager; +import org.apache.hc.core5.http.ClassicHttpRequest; +import org.apache.hc.core5.http.ClassicHttpResponse; +import org.apache.hc.core5.http.Header; +import org.apache.hc.core5.http.HttpException; +import org.apache.hc.core5.http.impl.bootstrap.HttpServer; +import org.apache.hc.core5.http.impl.bootstrap.ServerBootstrap; +import org.apache.hc.core5.http.io.HttpRequestHandler; +import org.apache.hc.core5.http.protocol.HttpContext; +import org.apache.hc.core5.io.CloseMode; +import org.junit.After; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; + +@SuppressWarnings("boxing") // test code +public class TestHttpClientBuilderInterceptors { + + private HttpServer localServer; + private String uri; + private CloseableHttpClient httpClient; + + @Before + public void before() throws Exception { + this.localServer = ServerBootstrap.bootstrap() + .register("/test", new HttpRequestHandler() { + + @Override + public void handle( + final ClassicHttpRequest request, + final ClassicHttpResponse response, + final HttpContext context) throws HttpException, IOException { + final Header testInterceptorHeader = request.getHeader("X-Test-Interceptor"); + if (testInterceptorHeader != null) { + response.setHeader(testInterceptorHeader); + } + response.setCode(200); + } + }).create(); + + this.localServer.start(); + uri = "http://localhost:" + this.localServer.getLocalPort() + "/test"; + final HttpClientConnectionManager cm = PoolingHttpClientConnectionManagerBuilder.create() + .setMaxConnPerRoute(5) + .build(); + httpClient = HttpClientBuilder.create() + .setConnectionManager(cm) + .addExecInterceptorLast("test-interceptor", new ExecChainHandler() { + + @Override + public ClassicHttpResponse execute( + final ClassicHttpRequest request, + final Scope scope, + final ExecChain chain) throws IOException, HttpException { + request.setHeader("X-Test-Interceptor", "active"); + return chain.proceed(request, scope); + } + }) + .build(); + } + + @After + public void after() throws Exception { + this.httpClient.close(CloseMode.IMMEDIATE); + this.localServer.stop(); + } + + @Test + public void testAddExecInterceptorLastShouldBeExecuted() throws IOException, HttpException { + final ClassicHttpRequest request = new HttpPost(uri); + final ClassicHttpResponse response = httpClient.execute(request); + Assert.assertEquals(200, response.getCode()); + final Header testFilterHeader = response.getHeader("X-Test-Interceptor"); + Assert.assertNotNull(testFilterHeader); + } + +}