How can I select a client certificate using the same HttpClient instance?
我有一个由多个客户使用的应用程序(其中一个带有他/她的客户证书)。该应用程序应该通过 HTTPS 与 Web 服务(使用 HttpClient)进行通信。要在此 Web 服务中进行身份验证,必须提供客户端证书。如前所述,每个客户端都有不同的客户端证书,我需要使用当前客户端的证书。我有以下代码正在执行所描述的操作。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 | X509TrustManager trustManager = new X509TrustManager() { public void checkClientTrusted(X509Certificate[] xcs, String string) throws CertificateException { } public void checkServerTrusted(X509Certificate[] xcs, String string) throws CertificateException { } public X509Certificate[] getAcceptedIssuers() { return null; } }; SSLContext sslcontext = SSLContext.getInstance("TLS"); KeyManager[] managers = /* Code that get the current client's KeyManager[] */; sslcontext.init(managers, trustManager, null); SSLSocketFactory socketFactory = new SSLSocketFactory(sslcontext); socketFactory.setHostnameVerifier(SSLSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER); HttpParams params = new BasicHttpParams(); params.setBooleanParameter(ClientPNames.HANDLE_REDIRECTS, true); HttpClient client = new DefaultHttpClient(params); Scheme sch = new Scheme("https", socketFactory, 443); client.getConnectionManager().getSchemeRegistry().register(sch); HttpPost post = /* Code that get the POST */ HttpResponse response = client.execute(post, new BasicHttpContext()); |
好的,它工作正常。但是有很多线程向这个服务发送请求。有时它给我一些问题。所以,我发现很多人告诉每个应用程序只有一个 HttpClient 更好,并且可以通过这种方式完成:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | SchemeRegistry schemeRegistry = new SchemeRegistry(); schemeRegistry.register(new Scheme("http", 80, PlainSocketFactory.getSocketFactory())); schemeRegistry.register(new Scheme("https", 443, SSLSocketFactory.getSocketFactory())); PoolingClientConnectionManager cm = new PoolingClientConnectionManager(schemeRegistry); // Increase max total connection to 200 cm.setMaxTotal(200); // Increase default max connection per route to 20 cm.setDefaultMaxPerRoute(20); // Increase max connections for localhost:80 to 50 HttpHost localhost = new HttpHost("localhost", 80); cm.setMaxPerRoute(new HttpRoute(localhost), 50); httpClient = new DefaultHttpClient(cm, params); |
这样我就有了一个带有连接池的 HttpClient。但是如何使用这种方法并为特定请求选择客户端证书?提前致谢。
编辑:
我会试着解释我为什么要这么做。我们有时会遇到问题。服务器停止与 Web 服务器的通信,一段时间后,它又开始工作。需要注意的是,在调用 web 服务之前已经完成了很多处理。所以,有一个池化的 Executor 来创建线程来完成所有的处理。工作完成后,该线程创建另一个与 Web 服务通信的线程。因此,第一个线程加入第二个线程超时(thread.join(timeout))。当第一个线程唤醒时,它会中断正在尝试与 web 服务通信的线程(如果尚未完成)。我不是实现这个的人,但它应该给请求一个超时来完成。
在我的开发机器中,我创建了一个 JMeter 测试,它模拟了许多使用该服务的客户端,并连接了 JConsole 以查看发生了什么。在完成 300 个任务(其中一些失败)后,Executor 的池创建的线程死亡。但是很多线程仍然活着,它们在 10 分钟后就死了。当我查看堆栈跟踪时,我看到它与 HttpClient 相关。然后,我开始搜索,发现有人说整个应用程序最好只使用一个 HttpClient ,使其汇集连接。
我认为 HttpClient 在搜索要连接的端口时被锁定并被第一个线程中断。然后,不知何故,它锁定了很长时间。
编辑:
这就是我在 JConsole 中看到的。这一次,我只启动了 100 个任务,并且有 15 个线程处于这种状态:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 | Name: Thread-228 State: RUNNABLE Total blocked: 0 Total waited: 0 Stack trace: java.net.SocketInputStream.socketRead0(Native Method) java.net.SocketInputStream.read(SocketInputStream.java:129) com.sun.net.ssl.internal.ssl.InputRecord.readFully(InputRecord.java:293) com.sun.net.ssl.internal.ssl.InputRecord.read(InputRecord.java:331) com.sun.net.ssl.internal.ssl.SSLSocketImpl.readRecord(SSLSocketImpl.java:798) - locked java.lang.Object@7c1975 com.sun.net.ssl.internal.ssl.SSLSocketImpl.performInitialHandshake(SSLSocketImpl.java:1138) - locked java.lang.Object@16b53a6 com.sun.net.ssl.internal.ssl.SSLSocketImpl.startHandshake(SSLSocketImpl.java:1165) com.sun.net.ssl.internal.ssl.SSLSocketImpl.getSession(SSLSocketImpl.java:1916) org.apache.http.conn.ssl.AbstractVerifier.verify(AbstractVerifier.java:91) org.apache.http.conn.ssl.SSLSocketFactory.connectSocket(SSLSocketFactory.java:572) org.apache.http.conn.ssl.SSLSocketFactory.connectSocket(SSLSocketFactory.java:692) org.apache.http.conn.scheme.SchemeSocketFactoryAdaptor.connectSocket(SchemeSocketFactoryAdaptor.java:65) org.apache.http.impl.conn.DefaultClientConnectionOperator.openConnection(DefaultClientConnectionOperator.java:180) org.apache.http.impl.conn.ManagedClientConnectionImpl.open(ManagedClientConnectionImpl.java:294) org.apache.http.impl.client.DefaultRequestDirector.tryConnect(DefaultRequestDirector.java:640) org.apache.http.impl.client.DefaultRequestDirector.execute(DefaultRequestDirector.java:479) org.apache.http.impl.client.AbstractHttpClient.execute(AbstractHttpClient.java:906) org.apache.http.impl.client.AbstractHttpClient.execute(AbstractHttpClient.java:805) sun.reflect.GeneratedMethodAccessor3671.invoke(Unknown Source) sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25) java.lang.reflect.Method.invoke(Method.java:597) org.codehaus.groovy.runtime.callsite.PojoMetaMethodSite$PojoCachedMethodSiteNoUnwrapNoCoerce.invoke(PojoMetaMethodSite.java:229) org.codehaus.groovy.runtime.callsite.PojoMetaMethodSite.call(PojoMetaMethodSite.java:52) org.codehaus.groovy.runtime.callsite.AbstractCallSite.call(AbstractCallSite.java:128) br.com.fibonacci.webservice.InvocacaoDeServico$_monteChamada_closure8.doCall(InvocacaoDeServico.groovy:441) sun.reflect.GeneratedMethodAccessor3667.invoke(Unknown Source) sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25) java.lang.reflect.Method.invoke(Method.java:597) org.codehaus.groovy.runtime.callsite.PogoMetaMethodSite$PogoCachedMethodSiteNoUnwrapNoCoerce.invoke(PogoMetaMethodSite.java:266) org.codehaus.groovy.runtime.callsite.PogoMetaMethodSite.callCurrent(PogoMetaMethodSite.java:51) org.codehaus.groovy.runtime.callsite.AbstractCallSite.callCurrent(AbstractCallSite.java:150) br.com.fibonacci.webservice.InvocacaoDeServico$_monteChamada_closure8.call(InvocacaoDeServico.groovy) sun.reflect.GeneratedMethodAccessor3662.invoke(Unknown Source) sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25) java.lang.reflect.Method.invoke(Method.java:597) org.codehaus.groovy.runtime.callsite.PogoMetaMethodSite$PogoCachedMethodSiteNoUnwrapNoCoerce.invoke(PogoMetaMethodSite.java:266) org.codehaus.groovy.runtime.callsite.PogoMetaMethodSite.call(PogoMetaMethodSite.java:63) org.codehaus.groovy.runtime.callsite.AbstractCallSite.call(AbstractCallSite.java:124) br.com.fibonacci.ChamadaComTimeout$_execute_closure1.doCall(ChamadaComTimeout.groovy:19) sun.reflect.GeneratedMethodAccessor3657.invoke(Unknown Source) sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25) java.lang.reflect.Method.invoke(Method.java:597) org.codehaus.groovy.runtime.callsite.PogoMetaMethodSite$PogoCachedMethodSiteNoUnwrapNoCoerce.invoke(PogoMetaMethodSite.java:266) org.codehaus.groovy.runtime.callsite.PogoMetaMethodSite.callCurrent(PogoMetaMethodSite.java:51) org.codehaus.groovy.runtime.callsite.AbstractCallSite.callCurrent(AbstractCallSite.java:150) br.com.fibonacci.ChamadaComTimeout$_execute_closure1.doCall(ChamadaComTimeout.groovy) sun.reflect.GeneratedMethodAccessor3655.invoke(Unknown Source) sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25) java.lang.reflect.Method.invoke(Method.java:597) org.codehaus.groovy.reflection.CachedMethod.invoke(CachedMethod.java:86) groovy.lang.MetaMethod.doMethodInvoke(MetaMethod.java:234) groovy.lang.MetaClassImpl.invokeMethod(MetaClassImpl.java:1061) groovy.lang.ExpandoMetaClass.invokeMethod(ExpandoMetaClass.java:910) groovy.lang.MetaClassImpl.invokeMethod(MetaClassImpl.java:892) groovy.lang.Closure.call(Closure.java:279) groovy.lang.Closure.call(Closure.java:274) groovy.lang.Closure.run(Closure.java:355) java.lang.Thread.run(Thread.java:662) |
我认为这是不可能的,因为 IMO 没有任何意义。
对纯文本重用连接是有意义的,但对于经过身份验证的加密连接则不然。
每个连接都属于一个用户,应该根据 SSL 参数终止,而不是在用户之间重复使用。
我发现这篇很棒的博文 http://javaeesupportpatterns.blogspot.com.br/2011/04/javanetsocketinputstreamsocketread0.html 解释了我的问题。它与 HttpClient 并没有真正的关系。问题在于超时实现。