java - 如何使用 Apache HttpClient-5 Beta 正确执行 Http2

标签 java http2 apache-httpcomponents apache-httpasyncclient apache-httpclient-5.x

您好我正在尝试编写一个向多个服务器(已经支持 http2)发出 http2 请求的客户端。到目前为止,我已经编写了这个运行良好的独立程序,我可以看到所有请求都通过相同的 tcp 连接(在 wireshark 的帮助下验证)。我使用带有 conscrypt 的 Apache Httpclient 5 来​​支持 jdk8 中的 ALPN(jdk8 是我的要求,我无法升级到 jdk9 或更高版本)
主要 block 就像,

try {
    TrustManager[] trustAllCerts = new TrustManager[]{
        new X509TrustManager(){
            public X509Certificate[] getAcceptedIssuers(){ return null; }
            public void checkClientTrusted(X509Certificate[] certs, String authType) {}
            public void checkServerTrusted(X509Certificate[] certs, String authType) {}
        }
    };
    Provider provider = Conscrypt.newProvider();
        
    SSLContext sslContext = SSLContext.getInstance("TLSv1.3", provider);
    sslContext.init(null, trustAllCerts, new SecureRandom());
        
    client = Http2AsyncClientBuilder.create().setTlsStrategy(new ConscryptClientTlsStrategy(sslContext)).setH2Config(H2Config.DEFAULT).build();
    client.start();
        
    ThreadPoolExecutor tpe = (ThreadPoolExecutor)Executors.newFixedThreadPool(50);
    for (int i=0; i < 50; i++) {
        Runnable worker = new WorkerClass(client, i);
        tpe.execute(worker);
    }
} catch (Exception e) {
    e.printStackTrace();
}
可运行的就像,
static class WorkerClass implements Runnable {
    CloseableHttpAsyncClient client = null;
    int i = 0;
    WorkerClass(CloseableHttpAsyncClient client, int i) {
        this.client = client;
        this.i = i;
    }
    public void run() {
        RequestConfig requestConfig = RequestConfig.custom()
                .setConnectTimeout(Timeout.of(15, TimeUnit.SECONDS))//Determines the timeout until a new connection is fully established.
                .setConnectionRequestTimeout(Timeout.of(60, TimeUnit.SECONDS))//Returns the connection lease request timeout used when requesting a connection from the connection manager.
                .setResponseTimeout(Timeout.of(60, TimeUnit.SECONDS))//Determines the timeout until arrival of a response from the opposite endpoint.
                .build();
        
        String url = "https://localhost:8081/myagent/getOutput?"+System.currentTimeMillis();
        String cachedXMLRequest = "<?xml version=\"1.0\" standalone=\"no\"?><My XML REQUEST GOES HERE>";
        SimpleHttpRequest request= SimpleHttpRequests.POST.create(url);         
        request.setBodyText(cachedXMLRequest, ContentType.APPLICATION_JSON);
        request.setConfig(requestConfig);           
        final CountDownLatch latch = new CountDownLatch(1);         
        client.execute(request, new FutureCallback<SimpleHttpResponse>() {
            @Override
            public void cancelled() {
                System.out.println("Cancelled");
                latch.countDown();
            }

            @Override
            public void completed(SimpleHttpResponse arg0) {
                System.out.println("Completed "+arg0.getBodyText());
                latch.countDown();
            }

            @Override
            public void failed(Exception exception) {
                System.out.println("Failed ");
                exception.printStackTrace();
                latch.countDown();
            }
        });
        
        try {
            latch.await(60, TimeUnit.SECONDS);
        } catch (InterruptedException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }
}
所以我在我的客户端中模拟了这个独立的,它使用上述逻辑异步调用多个服务器进行轮询。但是在这里我可以看到每个请求有一个 tcp 连接,并且连接在请求结束时关闭(尽管响应 header 返回为 HTTP/2.0)。我使用主 block 代码在我的应用程序启动时初始化客户端(除了线程池部分,因为它用于独立模拟多线程环境)并创建 url、请求和响应对象并在线程模型中同时执行每个请求(异步多路复用)。我的要求是每个域使用一个 tcp 连接并长时间使用它来处理大量请求。我在这里想念什么?
或者有人用正确的方式帮助我使用 httpclient 5 处理 http2
Kindy对此有所了解。
已编辑 :
我追踪到两件事是异常连接关闭,
1. 每次请求后关闭连接
原因:这条线造成的
RequestConfig requestConfig = RequestConfig.custom()。 setConnectTimeout(Timeout.of(15, TimeUnit.SECONDS)) ...
此 setConnectTimeout 应该处于请求级别,但在 15 秒空闲后它会关闭 tcp 连接本身,我不知道这是错误还是预期行为,因为此配置用于根据 documentation 封装请求项.
删除此行允许 tcp 连接在请求之外保持 Activity 状态。
2. 由于非法 HEADERS 帧/协议(protocol)错误/内部错误
我可以在线路日志中看到这些异常,正是我的 tcp 连接关闭的位置(它随机关闭),我无法共享整个日志,因为有太多我只是发布了异常部分

0x0][0x1][0x0][0x0][0x4][0xffffffc9]"

2019-02-07 16:05:08,666 DEBUG - i/o-0000001B << stream 0 frame: WINDOW_UPDATE (0x8); flags: (0x0); length: 4

2019-02-07 16:05:08,666 DEBUG - i/o-0000001B << Increment 1225

2019-02-07 16:05:08,666 DEBUG - i/o-0000001B >> stream 0 flow control 1225 -> 65535

2019-02-07 16:05:08,666 DEBUG - i/o-0000001B << stream 1 frame: WINDOW_UPDATE (0x8); flags: (0x0); length: 4

2019-02-07 16:05:08,666 DEBUG - i/o-0000001B << Increment 1225

2019-02-07 16:05:08,666 DEBUG - i/o-0000001B >> stream 1 flow control 1225 -> 65535

2019-02-07 16:05:08,827 DEBUG - i/o-0000001B << "[0x0][0x0][0x1d][0x1][0x4][0x0][0x0][0x0][0x5]?[0xffffffe1]?[0xffffff88][0x1f][0x12][0xffffff96][0xffffffdf]=[0xffffffbf]J[0x1][0xffffffd5]0[0xffffff96]5[0x4][0x0][0xffffffbe][0xffffffa0]Aq[0xffffff91][0x10][0xffffff94][0xffffffc5][0xffffffa3][0x0][0x3]E[0x0][0x1][0x0][0x0][0x0][0x5][\n]"

2019-02-07 16:05:08,827 DEBUG - i/o-0000001B << "[\n]"

2019-02-07 16:05:08,827 DEBUG - i/o-0000001B << ""

2019-02-07 16:05:08,827 DEBUG - i/o-0000001B << stream 5 frame: HEADERS (0x1); flags: END_HEADERS (0x4); length: 29

2019-02-07 16:05:08,828 DEBUG - i/o-0000001B << ?.?. ...=.J..0.5 3f e1 3f 88 1f 12 96 df 3d bf 4a 01 d5 30 96 35

2019-02-07 16:05:08,828 DEBUG - i/o-0000001B << ....Aq..... 04 00 be a0 41 71 91 5c 10 94 c5 a3 7f

2019-02-07 16:05:08,828 DEBUG - i/o-0000001B >> stream 0 frame: GOAWAY (0x7); flags: (0x0); length: 29

2019-02-07 16:05:08,828 DEBUG - i/o-0000001B >> Last stream 0

2019-02-07 16:05:08,829 DEBUG - i/o-0000001B >> Code PROTOCOL_ERROR

2019-02-07 16:05:08,829 DEBUG - i/o-0000001B >> Illegal HEADERS frame

2019-02-07 16:05:08,829 DEBUG - i/o-0000001B >> "[0x0][0x0][0x1d][0x7][0x0][0x0][0x0][0x0][0x0][0x0][0x0][0x0][0x0][0x0][0x0][0x0][0x1]Illegal HEADERS frame"

2019-02-07 16:05:10,401 DEBUG - ex-0000018D: preparing request execution


和,

2019-02-07 16:17:17,519 DEBUG - i/o-0000000C << stream 0 frame: WINDOW_UPDATE (0x8); flags: (0x0); length: 4

2019-02-07 16:17:17,519 DEBUG - i/o-0000000C << Increment 1208

2019-02-07 16:17:17,519 DEBUG - i/o-0000000C >> stream 0 flow control 1208 -> 65535

2019-02-07 16:17:17,519 DEBUG - i/o-0000000C << stream 1 frame: WINDOW_UPDATE (0x8); flags: (0x0); length: 4

2019-02-07 16:17:17,519 DEBUG - i/o-0000000C << Increment 1208

2019-02-07 16:17:17,519 DEBUG - i/o-0000000C >> stream 1 flow control 1208 -> 65535

2019-02-07 16:17:17,569 DEBUG - i/o-0000000C << "[0x0][0x0][0x4][0x3][0x0][0x0][0x0][0x0][0x3][0x0][0x0][0x0][0x2]"

2019-02-07 16:17:17,569 DEBUG - i/o-0000000C << stream 3 frame: RST_STREAM (0x3); flags: (0x0); length: 4

2019-02-07 16:17:17,569 DEBUG - i/o-0000000C << Code INTERNAL_ERROR

2019-02-07 16:17:17,569 DEBUG - i/o-0000000C >> stream 0 frame: GOAWAY (0x7); flags: (0x0); length: 31

2019-02-07 16:17:17,569 DEBUG - i/o-0000000C >> Last stream 0

2019-02-07 16:17:17,569 DEBUG - i/o-0000000C >> Code PROTOCOL_ERROR

2019-02-07 16:17:17,569 DEBUG - i/o-0000000C >> Unexpected stream id: 3


我不知道是什么导致了这些异常,我尝试使用静态和动态帖子正文数据,并且两者的行为相似
完整 session 日志 :
https://drive.google.com/open?id=12y8HnaMTrPo-NBeLaoCYpK6zklJBL56T
https://drive.google.com/open?id=16KHgqLWrwz3Z3ls3Yvpp58zOI3SUNATM
客户 :
Tomcat 9.0x,
ubuntu 16.x 上的 Jdk 1.8.x(带有 conscrypt 以支持 ALPN,我们通过 TLSv1.3 连接它)
服务器 :
Tomcat 9.0x(带有 openssl 实现,NIO 连接器,http2 支持),
Openssl 1.1.1 支持 TLSv1.3,
Ubuntu 14.x 上的 JDK 10.0.2
任何帮助将不胜感激
TIA

最佳答案

案例一:服务器发送HEADERS乱序。

更新

案例2:服务器发送RST_STREAM对于空闲流

这看起来像 Tomcat 中的一个错误。这是一个连接的 session 日志,内容仅为 DATA为简洁起见剪断了框架

2019-02-07 16:15:47,389 DEBUG - i/o-00000003 >> stream 0 frame: SETTINGS (0x4); flags: (0x0); length: 36
2019-02-07 16:15:47,389 DEBUG - i/o-00000003 >> HEADER_TABLE_SIZE: 8192
2019-02-07 16:15:47,389 DEBUG - i/o-00000003 >> ENABLE_PUSH: 0
2019-02-07 16:15:47,389 DEBUG - i/o-00000003 >> MAX_CONCURRENT_STREAMS: 100
2019-02-07 16:15:47,389 DEBUG - i/o-00000003 >> INITIAL_WINDOW_SIZE: 65535
2019-02-07 16:15:47,389 DEBUG - i/o-00000003 >> MAX_FRAME_SIZE: 65536
2019-02-07 16:15:47,389 DEBUG - i/o-00000003 >> MAX_HEADER_LIST_SIZE: 16777215
2019-02-07 16:15:47,389 DEBUG - i/o-00000003 >> "[0x0][0x0]$[0x4][0x0][0x0][0x0][0x0][0x0][0x0][0x1][0x0][0x0] [0x0][0x0][0x2][0x0][0x0][0x0][0x0][0x0][0x3][0x0][0x0][0x0]d[0x0][0x4][0x0][0x0][0xffffffff][0xffffffff][0x0][0x5][0x0][0x1][0x0][0x0][0x0][0x6][0x0][0xffffffff][0xffffffff][0xffffffff]"
2019-02-07 16:15:47,393 DEBUG - i/o-00000003 << "[0x0][0x0][0x6][0x4][0x0][0x0][0x0][0x0][0x0][0x0][0x3][0x0][0x0][0x0][0xffffffc8][0x0][0x0][0x0][0x4][0x1][0x0][0x0][0x0][0x0][0x0][0x0][0x8][0x6][0x0][0x0][0x0][0x0][0x0][0x0][0x0][0x0][0x0][0x0][0x0][0x0][0x1]"
2019-02-07 16:15:47,393 DEBUG - i/o-00000003 << stream 0 frame: SETTINGS (0x4); flags: (0x0); length: 6
2019-02-07 16:15:47,393 DEBUG - i/o-00000003 << MAX_CONCURRENT_STREAMS: 200
2019-02-07 16:15:47,393 DEBUG - i/o-00000003 >> stream 0 frame: SETTINGS (0x4); flags: ACK (0x1); length: 0
2019-02-07 16:15:47,393 DEBUG - i/o-00000003 >> "[0x0][0x0][0x0][0x4][0x1][0x0][0x0][0x0][0x0]"
2019-02-07 16:15:47,393 DEBUG - i/o-00000003 << stream 0 frame: SETTINGS (0x4); flags: ACK (0x1); length: 0
2019-02-07 16:15:47,393 DEBUG - i/o-00000003 << stream 0 frame: PING (0x6); flags: (0x0); length: 8
2019-02-07 16:15:47,393 DEBUG - i/o-00000003 << ........          00 00 00 00 00 00 00 01
2019-02-07 16:15:47,393 DEBUG - i/o-00000003 >> stream 0 frame: PING (0x6); flags: ACK (0x1); length: 8
2019-02-07 16:15:47,393 DEBUG - i/o-00000003 >> ........          00 00 00 00 00 00 00 01
2019-02-07 16:15:47,393 DEBUG - i/o-00000003 >> "[0x0][0x0][0x8][0x6][0x1][0x0][0x0][0x0][0x0][0x0][0x0][0x0][0x0][0x0][0x0][0x0][0x1]"
2019-02-07 16:15:47,393 DEBUG - i/o-00000003 >> :method: POST
2019-02-07 16:15:47,393 DEBUG - i/o-00000003 >> :scheme: https
2019-02-07 16:15:47,393 DEBUG - i/o-00000003 >> :authority: 172.21.170.214:8999
2019-02-07 16:15:47,393 DEBUG - i/o-00000003 >> :path: /monitoragent/getdcoutput?1000000004286_1520339351002_1549536347382
2019-02-07 16:15:47,393 DEBUG - i/o-00000003 >> user-agent: Apache-HttpAsyncClient/5.0-beta3 (Java/1.8.0_162)
2019-02-07 16:15:47,393 DEBUG - i/o-00000003 >> content-type: application/json; charset=UTF-8
2019-02-07 16:15:47,393 DEBUG - i/o-00000003 >> stream 1 frame: HEADERS (0x1); flags: END_HEADERS (0x4); length: 130
...
2019-02-07 16:15:47,394 DEBUG - i/o-00000003 >> stream 1 frame: DATA (0x0); flags: (0x0); length: 1208
...
2019-02-07 16:15:47,394 DEBUG - i/o-00000003 >> stream 0 flow control -1208 -> 64327
2019-02-07 16:15:47,394 DEBUG - i/o-00000003 >> stream 1 flow control -1208 -> 64327
...
2019-02-07 16:15:47,394 DEBUG - i/o-00000003 >> stream 1 frame: DATA (0x0); flags: END_STREAM (0x1); length: 0
2019-02-07 16:15:47,394 DEBUG - i/o-00000003 >> "[0x0][0x0][0x0][0x0][0x1][0x0][0x0][0x0][0x1]"
2019-02-07 16:15:47,395 DEBUG - i/o-00000003 >> :method: POST
2019-02-07 16:15:47,395 DEBUG - i/o-00000003 >> :scheme: https
2019-02-07 16:15:47,395 DEBUG - i/o-00000003 >> :authority: 172.21.170.214:8999
2019-02-07 16:15:47,395 DEBUG - i/o-00000003 >> :path: /monitoragent/getdcoutput?1000000004286_1520339351002_1549536347382
2019-02-07 16:15:47,395 DEBUG - i/o-00000003 >> user-agent: Apache-HttpAsyncClient/5.0-beta3 (Java/1.8.0_162)
2019-02-07 16:15:47,395 DEBUG - i/o-00000003 >> content-type: application/json; charset=UTF-8
2019-02-07 16:15:47,395 DEBUG - i/o-00000003 >> stream 3 frame: HEADERS (0x1); flags: END_HEADERS (0x4); length: 6
2019-02-07 16:15:47,395 DEBUG - i/o-00000003 >> ......            83 87 c1 c0 bf be
2019-02-07 16:15:47,395 DEBUG - i/o-00000003 >> "[0x0][0x0][0x6][0x1][0x4][0x0][0x0][0x0][0x3][0xffffff83][0xffffff87][0xffffffc1][0xffffffc0][0xffffffbf][0xffffffbe]"
...
2019-02-07 16:15:47,395 DEBUG - i/o-00000003 >> stream 0 flow control -1208 -> 63119
2019-02-07 16:15:47,395 DEBUG - i/o-00000003 >> stream 3 flow control -1208 -> 64327
...
2019-02-07 16:15:47,396 DEBUG - i/o-00000003 >> stream 3 frame: DATA (0x0); flags: END_STREAM (0x1); length: 0
2019-02-07 16:15:47,396 DEBUG - i/o-00000003 >> "[0x0][0x0][0x0][0x0][0x1][0x0][0x0][0x0][0x3]"
2019-02-07 16:15:47,399 DEBUG - i/o-00000003 << "[0x0][0x0][0x4][0x8][0x0][0x0][0x0][0x0][0x0][0x0][0x0][0x4][0xffffffb8][0x0][0x0][0x4][0x8][0x0][0x0][0x0][0x0][0x3][0x0][0x0][0x4][0xffffffb8]"
2019-02-07 16:15:47,399 DEBUG - i/o-00000003 << stream 0 frame: WINDOW_UPDATE (0x8); flags: (0x0); length: 4
2019-02-07 16:15:47,399 DEBUG - i/o-00000003 << Increment 1208
2019-02-07 16:15:47,399 DEBUG - i/o-00000003 >> stream 0 flow control 1208 -> 64327
2019-02-07 16:15:47,399 DEBUG - i/o-00000003 << stream 3 frame: WINDOW_UPDATE (0x8); flags: (0x0); length: 4
2019-02-07 16:15:47,399 DEBUG - i/o-00000003 << Increment 1208
2019-02-07 16:15:47,399 DEBUG - i/o-00000003 >> stream 3 flow control 1208 -> 65535
2019-02-07 16:15:47,399 DEBUG - i/o-00000003 << "[0x0][0x0][0x4][0x8][0x0][0x0][0x0][0x0][0x0][0x0][0x0][0x4][0xffffffb8][0x0][0x0][0x4][0x8][0x0][0x0][0x0][0x0][0x1][0x0][0x0][0x4][0xffffffb8]"
2019-02-07 16:15:47,399 DEBUG - i/o-00000003 << stream 0 frame: WINDOW_UPDATE (0x8); flags: (0x0); length: 4
2019-02-07 16:15:47,399 DEBUG - i/o-00000003 << Increment 1208
2019-02-07 16:15:47,399 DEBUG - i/o-00000003 >> stream 0 flow control 1208 -> 65535
2019-02-07 16:15:47,399 DEBUG - i/o-00000003 << stream 1 frame: WINDOW_UPDATE (0x8); flags: (0x0); length: 4
2019-02-07 16:15:47,399 DEBUG - i/o-00000003 << Increment 1208
2019-02-07 16:15:47,399 DEBUG - i/o-00000003 >> stream 1 flow control 1208 -> 65535
2019-02-07 16:15:47,503 DEBUG - i/o-00000003 << "[0x0][0x0][0x4][0x3][0x0][0x0][0x0][0x0]3[0x0][0x0][0x0][0x2]"
2019-02-07 16:15:47,503 DEBUG - i/o-00000003 << stream 51 frame: RST_STREAM (0x3); flags: (0x0); length: 4
2019-02-07 16:15:47,503 DEBUG - i/o-00000003 << Code INTERNAL_ERROR
2019-02-07 16:15:47,503 DEBUG - i/o-00000003 >> stream 0 frame: GOAWAY (0x7); flags: (0x0); length: 32
2019-02-07 16:15:47,503 DEBUG - i/o-00000003 >> Last stream 0
2019-02-07 16:15:47,503 DEBUG - i/o-00000003 >> Code PROTOCOL_ERROR
2019-02-07 16:15:47,503 DEBUG - i/o-00000003 >> Unexpected stream id: 51
2019-02-07 16:15:47,504 DEBUG - i/o-00000003 >> "[0x0][0x0] [0x7][0x0][0x0][0x0][0x0][0x0][0x0][0x0][0x0][0x0][0x0][0x0][0x0][0x1]Unexpected stream id: 51"  

在消息交换结束时,服务器发送 RST_STREAM对于流 51。没有流 51。流 1 和 3 都已正确关闭。然后,无论出于何种原因,服务器决定终止空闲流 51。根据 RFC 7540, section 6.4,客户端将此类操作视为严重的协议(protocol)违规。
RST_STREAM frames MUST NOT be sent for a stream in the "idle" state.
If a RST_STREAM frame identifying an idle stream is received, the
recipient MUST treat this as a connection error (Section 5.4.1) of
type PROTOCOL_ERROR.

请升级到最新版本的 Tomcat 9,如果问题仍然存在,请向 Tomcat 开发人员报告。

关于java - 如何使用 Apache HttpClient-5 Beta 正确执行 Http2,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/54548110/

相关文章:

node.js - 在 'end' 事件之后调用“错误”事件?

apache-camel - 为什么 MultipartEntityBuilder 会抛出 org.apache.http.ContentTooLongException?

java - 我们如何将 try finally 用于 Lock.lock 和 Lock.unlock

java - TreeMap java中的天花板和地板

css - 仅内联关键 CSS,或整个 15kb 的 CSS,当第一个页面 View 最重要时?

http2 - Web 开发人员如何在 http2 中设置资源的优先级?

java - 使用 apache http 组件监控下载进度

java - Apache HttpClient 不使用分页链接获取页面内容。我得到 200 状态,但 html 没有内容

java - IOIO,有没有办法调用 softreset();来自另一个类(class)?

java - 在 Java 中使用队列解决迷宫问题