如果使用抢占式 BASIC 身份验证,为什么使用 Apache HttpClient5 发出的 http 请求会收到 http 404 错误

Why http request made with Apache HttpClient5 receives http 404 error if preemptive BASIC authentication used

我需要有关 here 中描述的 apache http 5 客户端示例的帮助。我的目标是做与 CURL 完全相同的事情:

curl --verbose --user "admin:admin" --data-binary @20200802112010-test.csv  'http://localhost:8080/api/test/write?db=test' --trace-ascii trace.txt

我修改了示例以执行 POST 而不是 GET:

private void _sendFile(File file) throws IOException, URISyntaxException {
        BasicScheme basicScheme = new BasicScheme();

        basicScheme.initPreemptive(new UsernamePasswordCredentials("admin", "admin".toCharArray()));

        HttpClientContext httpClientContext = HttpClientContext.create();

        HttpHost httpHost = new HttpHost("http", "localhost", 8080);

        httpClientContext.resetAuthExchange(httpHost, basicScheme);

        try (CloseableHttpClient httpClient = HttpClients.createDefault()) {
            FileEntity fileEntity = new FileEntity(file, ContentType.APPLICATION_FORM_URLENCODED);

            HttpPost httpPost = new HttpPost("http://localhost:8080/api/test/write?db=test");

            httpPost.setEntity(fileEntity);

            try (final CloseableHttpResponse httpResponse = httpClient.execute(httpHost, httpPost, httpClientContext)) {
                if (httpResponse.getCode() == 204) {
                    return;
                }

                _logger.warn("Unexpected return code {}", httpResponse.getCode());
            }
        }
    }

我的服务器端代码是 Spring JHipster 生成的引导应用程序和处理请求的控制器是这样的:

@Profile({"dev", "igor"})
@RequestMapping("/api/test")
@RestController
public class DBEndpointSimulatorResource {

    /**
     * {@code POST  /write} : Simulates file import on INRAE InfluxDB server.
     *
     * @param dataBase the database name
     * @param content the request body
     *
     * @return the {@link ResponseEntity} with status {@code 204 (No Content)}
     * @throws URISyntaxException if the Location URI syntax is incorrect.
     */
    @PostMapping("/write")
    public ResponseEntity<?> simulateWrite(@RequestParam("db") String dataBase, @RequestBody String content)
        throws URISyntaxException {

        _logger.debug("REST request to process file for: {}", dataBase);

        if (content == null) {
            throw new BadRequestAlertException(
                "A content must not be empty", "DB FILE", "ERROR-SIMULATOR-001");
        }

        final ResponseEntity.HeadersBuilder<?> headersBuilder = ResponseEntity.noContent();

        return headersBuilder.build();
    }

    private final Logger _logger = LoggerFactory.getLogger(DBEndpointSimulatorResource.class);

}

执行 _sendFile 代码后收到 HTTP 错误 404。我打开了 httpclient 5 的日志记录,它注销到服务器端的正确连接,服务器端以 404 响应:

     ex-00000032: target auth state: UNCHALLENGED
2020-08-03 10:01:00.009 DEBUG 95767 --- [ta-scheduling-2] o.a.h.c.http.impl.classic.ProtocolExec   : ex-00000032: proxy auth state: UNCHALLENGED
2020-08-03 10:01:00.009 DEBUG 95767 --- [ta-scheduling-2] o.a.h.c.http.impl.classic.ConnectExec    : ex-00000032: acquiring connection with route {}->http://localhost:8080
2020-08-03 10:01:00.009 DEBUG 95767 --- [ta-scheduling-2] o.a.h.c.h.i.classic.InternalHttpClient   : ex-00000032: acquiring endpoint (3 MINUTES)
2020-08-03 10:01:00.009 DEBUG 95767 --- [ta-scheduling-2] h.i.i.PoolingHttpClientConnectionManager : ex-00000032: endpoint lease request (3 MINUTES) [route: {}->http://localhost:8080][total available: 0; route allocated: 0 of 5; total allocated: 0 of 25]
2020-08-03 10:01:00.009 DEBUG 95767 --- [ta-scheduling-2] h.i.i.PoolingHttpClientConnectionManager : ex-00000032: endpoint leased [route: {}->http://localhost:8080][total available: 0; route allocated: 1 of 5; total allocated: 1 of 25]
2020-08-03 10:01:00.009 DEBUG 95767 --- [ta-scheduling-2] h.i.i.PoolingHttpClientConnectionManager : ex-00000032: acquired ep-00000031
2020-08-03 10:01:00.009 DEBUG 95767 --- [ta-scheduling-2] o.a.h.c.h.i.classic.InternalHttpClient   : ex-00000032: acquired endpoint ep-00000031
2020-08-03 10:01:00.009 DEBUG 95767 --- [ta-scheduling-2] o.a.h.c.http.impl.classic.ConnectExec    : ex-00000032: opening connection {}->http://localhost:8080
2020-08-03 10:01:00.009 DEBUG 95767 --- [ta-scheduling-2] o.a.h.c.h.i.classic.InternalHttpClient   : ep-00000031: connecting endpoint (3 MINUTES)
2020-08-03 10:01:00.009 DEBUG 95767 --- [ta-scheduling-2] h.i.i.PoolingHttpClientConnectionManager : ep-00000031: connecting endpoint to http://localhost:8080 (3 MINUTES)
2020-08-03 10:01:00.010 DEBUG 95767 --- [ta-scheduling-2] .i.i.DefaultHttpClientConnectionOperator : http-outgoing-49: connecting to localhost/127.0.0.1:8080
2020-08-03 10:01:00.010 DEBUG 95767 --- [ta-scheduling-2] .i.i.DefaultHttpClientConnectionOperator : http-outgoing-49: connection established 127.0.0.1:52473<->127.0.0.1:8080
2020-08-03 10:01:00.010 DEBUG 95767 --- [ta-scheduling-2] h.i.i.PoolingHttpClientConnectionManager : ep-00000031: connected http-outgoing-49
2020-08-03 10:01:00.010 DEBUG 95767 --- [ta-scheduling-2] o.a.h.c.h.i.classic.InternalHttpClient   : ep-00000031: endpoint connected
2020-08-03 10:01:00.010 DEBUG 95767 --- [ta-scheduling-2] o.a.h.c.h.impl.classic.MainClientExec    : ex-00000032: executing POST /api/test/write?db=test HTTP/1.1
2020-08-03 10:01:00.010 DEBUG 95767 --- [ta-scheduling-2] o.a.h.c.h.i.classic.InternalHttpClient   : ep-00000031: start execution ex-00000032
2020-08-03 10:01:00.010 DEBUG 95767 --- [ta-scheduling-2] h.i.i.PoolingHttpClientConnectionManager : ep-00000031: executing exchange ex-00000032 over http-outgoing-49
2020-08-03 10:01:00.010 DEBUG 95767 --- [ta-scheduling-2] org.apache.hc.client5.http.headers       : http-outgoing-49 >> POST /api/test/write?db=test HTTP/1.1
2020-08-03 10:01:00.011 DEBUG 95767 --- [ta-scheduling-2] org.apache.hc.client5.http.headers       : http-outgoing-49 >> Accept-Encoding: gzip, x-gzip, deflate
2020-08-03 10:01:00.011 DEBUG 95767 --- [ta-scheduling-2] org.apache.hc.client5.http.headers       : http-outgoing-49 >> Content-Length: 29423
2020-08-03 10:01:00.011 DEBUG 95767 --- [ta-scheduling-2] org.apache.hc.client5.http.headers       : http-outgoing-49 >> Content-Type: application/x-www-form-urlencoded; charset=ISO-8859-1
2020-08-03 10:01:00.011 DEBUG 95767 --- [ta-scheduling-2] org.apache.hc.client5.http.headers       : http-outgoing-49 >> Host: localhost:8080
2020-08-03 10:01:00.011 DEBUG 95767 --- [ta-scheduling-2] org.apache.hc.client5.http.headers       : http-outgoing-49 >> Connection: keep-alive
2020-08-03 10:01:00.011 DEBUG 95767 --- [ta-scheduling-2] org.apache.hc.client5.http.headers       : http-outgoing-49 >> User-Agent: Apache-HttpClient/5.0 (Java/1.8.0_202)
2020-08-03 10:01:00.011 DEBUG 95767 --- [ta-scheduling-2] org.apache.hc.client5.http.headers       : http-outgoing-49 >> Authorization: Basic YWRtaW46YWRtaW4=
2020-08-03 10:01:00.011 DEBUG 95767 --- [ta-scheduling-2] org.apache.hc.client5.http.wire          : http-outgoing-49 >> "POST /api/test/write?db=test HTTP/1.1[\r][\n]"
2020-08-03 10:01:00.011 DEBUG 95767 --- [ta-scheduling-2] org.apache.hc.client5.http.wire          : http-outgoing-49 >> "Accept-Encoding: gzip, x-gzip, deflate[\r][\n]"
2020-08-03 10:01:00.011 DEBUG 95767 --- [ta-scheduling-2] org.apache.hc.client5.http.wire          : http-outgoing-49 >> "Content-Length: 29423[\r][\n]"
2020-08-03 10:01:00.011 DEBUG 95767 --- [ta-scheduling-2] org.apache.hc.client5.http.wire          : http-outgoing-49 >> "Content-Type: application/x-www-form-urlencoded; charset=ISO-8859-1[\r][\n]"
2020-08-03 10:01:00.011 DEBUG 95767 --- [ta-scheduling-2] org.apache.hc.client5.http.wire          : http-outgoing-49 >> "Host: localhost:8080[\r][\n]"
2020-08-03 10:01:00.011 DEBUG 95767 --- [ta-scheduling-2] org.apache.hc.client5.http.wire          : http-outgoing-49 >> "Connection: keep-alive[\r][\n]"
2020-08-03 10:01:00.011 DEBUG 95767 --- [ta-scheduling-2] org.apache.hc.client5.http.wire          : http-outgoing-49 >> "User-Agent: Apache-HttpClient/5.0 (Java/1.8.0_202)[\r][\n]"
2020-08-03 10:01:00.011 DEBUG 95767 --- [ta-scheduling-2] org.apache.hc.client5.http.wire          : http-outgoing-49 >> "Authorization: Basic YWRtaW46YWRtaW4=[\r][\n]"
2020-08-03 10:01:00.011 DEBUG 95767 --- [ta-scheduling-2] org.apache.hc.client5.http.wire          : http-outgoing-49 >> "[\r][\n]"
2020-08-03 10:01:00.011 DEBUG 95767 --- [ta-scheduling-2] org.apache.hc.client5.http.wire          : http-outgoing-49 >> "pesmic,dat1=MIC,dat2=2136-0000 dat3="null",dat4=60,dat5=0,dat6=0,dat7=0,activ="null" 1554026400000000000[\n]"
2020-08-03 10:01:00.034 DEBUG 95767 --- [ta-scheduling-2] org.apache.hc.client5.http.wire          : http-outgoing-49 >> "pesmic,dat1=MIC,dat2=2136-0001 dat3="null",dat4=40060,dat5=0,dat6=0,dat7=0,activ="null" 1554530400000000000"
2020-08-03 10:01:00.037 DEBUG 95767 --- [ta-scheduling-2] org.apache.hc.client5.http.wire          : http-outgoing-49 << "HTTP/1.1 404 [\r][\n]"
2020-08-03 10:01:00.037 DEBUG 95767 --- [ta-scheduling-2] org.apache.hc.client5.http.wire          : http-outgoing-49 << "X-Content-Type-Options: nosniff[\r][\n]"
2020-08-03 10:01:00.037 DEBUG 95767 --- [ta-scheduling-2] org.apache.hc.client5.http.wire          : http-outgoing-49 << "X-Frame-Options: SAMEORIGIN[\r][\n]"
2020-08-03 10:01:00.037 DEBUG 95767 --- [ta-scheduling-2] org.apache.hc.client5.http.wire          : http-outgoing-49 << "X-XSS-Protection: 1[\r][\n]"
2020-08-03 10:01:00.037 DEBUG 95767 --- [ta-scheduling-2] org.apache.hc.client5.http.wire          : http-outgoing-49 << "Set-Cookie: JSESSIONID=AEC03A81AD4A85F68CB94BAF23E784E8; Path=/; HttpOnly[\r][\n]"
2020-08-03 10:01:00.037 DEBUG 95767 --- [ta-scheduling-2] org.apache.hc.client5.http.wire          : http-outgoing-49 << "Content-Type: text/html;charset=UTF-8[\r][\n]"
2020-08-03 10:01:00.037 DEBUG 95767 --- [ta-scheduling-2] org.apache.hc.client5.http.wire          : http-outgoing-49 << "Content-Length: 690[\r][\n]"
2020-08-03 10:01:00.037 DEBUG 95767 --- [ta-scheduling-2] org.apache.hc.client5.http.wire          : http-outgoing-49 << "Date: Mon, 03 Aug 2020 08:01:00 GMT[\r][\n]"
2020-08-03 10:01:00.037 DEBUG 95767 --- [ta-scheduling-2] org.apache.hc.client5.http.wire          : http-outgoing-49 << "Keep-Alive: timeout=20[\r][\n]"
2020-08-03 10:01:00.037 DEBUG 95767 --- [ta-scheduling-2] org.apache.hc.client5.http.wire          : http-outgoing-49 << "Connection: keep-alive[\r][\n]"
2020-08-03 10:01:00.037 DEBUG 95767 --- [ta-scheduling-2] org.apache.hc.client5.http.wire          : http-outgoing-49 << "[\r][\n]"
2020-08-03 10:01:00.037 DEBUG 95767 --- [ta-scheduling-2] org.apache.hc.client5.http.wire          : http-outgoing-49 << "[\n]"

一开始我认为 spring 引导端的控制器是错误的,但我尝试了使用 cURL 的相同请求,它以成功结束:

    => Send header, 236 bytes (0xec)
0000: POST /api/test/write?db=test HTTP/1.1
0027: Host: localhost:8080
003d: Authorization: Basic YWRtaW46YWRtaW4=
0064: User-Agent: curl/7.64.1
007d: Accept: */*
008a: Content-Length: 2689786
00a3: Content-Type: application/x-www-form-urlencoded
00d4: Expect: 100-continue
00ea: 
<= Recv header, 23 bytes (0x17)
0000: HTTP/1.1 100 Continue
<= Recv header, 19 bytes (0x13)
0000: Content-Length: 0
=> Send data, 65536 bytes (0x10000)
== Info: We are completely uploaded and fine
<= Recv header, 25 bytes (0x19)
0000: HTTP/1.1 204 No Content
<= Recv header, 12 bytes (0xc)
0000: Expires: 0

我发现 apache http 客户端附加字符编码的内容类型有所不同。现在我想知道:

  1. 我应该强制使用 utf8 字符编码吗?如何?或
  2. 我的 apache httpclient post 示例中还有其他问题

我描述的问题从未在生产中出现,仅在我的本地开发环境 MacOSX Catalina 中出现。

我很少重启我的 MacBookPro,似乎它导致了问题。重启硬件后,问题再也不会发生了。

这不是完整的答案,但它是使问题无效的唯一方法:(