NTLM和Java Apache httpclient 4.5.6的多线程问题
我有一个独立的Java客户机试图通过NTLM代理进行RMI。 它是多线程的。 我正在使用ApacheHttpClient 4.5.6。 我有一个5分钟超时周期的代理 基本情况下,只要两个线程没有在代理超时的同时发出请求,当代理发出质询时,每5分钟重新验证一次。然后它失败了。一旦失败,所有后续尝试都将失败 我附上了一个wireshark截图来澄清(截图来自4.5.2,但我升级到了4.5.6,看到了相同的行为) 一个好的循环看起来像NTLM和Java Apache httpclient 4.5.6的多线程问题,java,multithreading,apache-httpclient-4.x,ntlm,Java,Multithreading,Apache Httpclient 4.x,Ntlm,我有一个独立的Java客户机试图通过NTLM代理进行RMI。 它是多线程的。 我正在使用ApacheHttpClient 4.5.6。 我有一个5分钟超时周期的代理 基本情况下,只要两个线程没有在代理超时的同时发出请求,当代理发出质询时,每5分钟重新验证一次。然后它失败了。一旦失败,所有后续尝试都将失败 我附上了一个wireshark截图来澄清(截图来自4.5.2,但我升级到了4.5.6,看到了相同的行为) 一个好的循环看起来像 客户端尝试连接(无NTML标志) 使用407(无NTML标志)
- 客户端尝试连接(无NTML标志)
- 使用407(无NTML标志)的代理答复
- 客户端尝试使用ntlm消息类型NTLMSSP\u再次连接
- 代理答复407 NTLMSSP_质询
- 客户端确实与NTLMSSP_AUTH和我的凭据连接
- 代理回复200,我们可以再继续5分钟
- 客户端尝试连接(无NTML标志)
- 使用407(无NTML标志)的代理答复
- 客户端尝试使用ntlm消息类型NTLMSSP\u再次连接
- 客户端尝试连接(无NTML标志)
- 使用407(无NTML标志)的代理答复
- 代理答复407 NTLMSSP_质询
- 在几秒钟内,更多的连接和407没有NTML标志
java.lang.IllegalStateException: Auth scheme is null
at org.apache.http.util.Asserts.notNull(Asserts.java:52)
at org.apache.http.impl.auth.HttpAuthenticator.ensureAuthScheme(HttpAuthenticator.java:229)
at org.apache.http.impl.auth.HttpAuthenticator.generateAuthResponse(HttpAuthenticator.java:184)
at org.apache.http.impl.execchain.MainClientExec.createTunnelToTarget(MainClientExec.java:484)
at org.apache.http.impl.execchain.MainClientExec.establishRoute(MainClientExec.java:411)
at org.apache.http.impl.execchain.MainClientExec.execute(MainClientExec.java:237)
at org.apache.http.impl.execchain.ProtocolExec.execute(ProtocolExec.java:185)
at org.apache.http.impl.execchain.RetryExec.execute(RetryExec.java:89)
at org.apache.http.impl.execchain.RedirectExec.execute(RedirectExec.java:110)
at org.apache.http.impl.client.InternalHttpClient.doExecute(InternalHttpClient.java:185)
at org.apache.http.impl.client.CloseableHttpClient.execute(CloseableHttpClient.java:83)
有什么办法可以防止这种情况发生,或者解决它,或者从中恢复过来?
(除了通话同步之外,这会大大降低已经很慢的应用程序的速度)
应用程序中的一些代码片段:
// this is done only once
HttpClientBuilder builder = HttpClients.custom();
SocketConfig.Builder socketConfig = SocketConfig.custom();
RequestConfig.Builder requestConfig = RequestConfig.custom();
HttpHost proxy = new HttpHost(proxyHost, proxyPort);
builder.setProxy(proxy);
requestConfig.setProxy(proxy);
builder.setProxyAuthenticationStrategy(new ProxyAuthenticationStrategy());
CredentialsProvider credentialsProvider = new BasicCredentialsProvider();
String localHost = getLocalHostname();
credentialsProvider.setCredentials(
new AuthScope(proxyHost, proxyPort, AuthScope.ANY_REALM, "ntlm"),
new NTCredentials(user, password, localHost, domain));
builder.setDefaultCredentialsProvider(credentialsProvider);
builder.setDefaultSocketConfig(socketConfig.build());
builder.setDefaultRequestConfig(requestConfig.build());
CloseableHttpClient client = builder.build();
...
// cached, we use the same one every time in accordance with section 4.7 of
// https://hc.apache.org/httpcomponents-client-4.5.x/tutorial/html/authentication.html
HttpClientContext context = HttpClientContext.create();
context.setCredentialsProvider(credentialsProvider);
...
// new HttpPost every time
HttpPost postMethod = new HttpPost(uri);
postMethod.setEntity(new ByteArrayEntity(bytesOut.toByteArray()));
response = client.execute(postMethod, context);
HttpContext
实例是完全线程安全的。但是,存储在上下文中的某些属性(如身份验证握手状态)显然不是。确保HttpContext
实例不会同时更新,问题应该会消失 谢谢你,Oleg,这就是我所做的,到目前为止它似乎还有效(太长了,无法对你的答案发表评论,但我想分享我的代码)
//我在不通过代理时使用基本版本
公共类HttpClientContextFactory{
公共HttpClientContext创建(){
返回HttpClientContext.create();
}
}
//我在通过NTLM代理时使用此选项
私有HttpClientContextFactory getNtlmContextFactory(
最终证书提供者(证书提供者){
返回新的HttpClientContextFactory(){
ThreadLocal tlContext=ThreadLocal
.带首字母(()->{
HttpClientContext=HttpClientContext.create();
context.setCredentialsProvider(credentialsProvider);
返回上下文;
});
@凌驾
公共HttpClientContext创建(){
返回tlContext.get();
}
};
}
//然后在连接到服务器时执行此操作
response=client.execute(postMethod,contextFactory.create());
多个线程同时使用了HttpClientContext
吗?是。的第4.7节说始终使用同一个线程,尽管它没有提到线程。不要。我如何确保这一点?我的代码没有更新HttpClientContext
,这发生在apachehttpclient代码的深处。我对上下文所做的唯一一件事就是将其传递到CloseableHttpClient.execute(HttpUriRequest,HttpContext)
谢谢,这就是我所做的,到目前为止它似乎还在工作。
// I use the base version when not going through a proxy
public class HttpClientContextFactory {
public HttpClientContext create() {
return HttpClientContext.create();
}
}
// I use this when I go through a NTLM proxy
private HttpClientContextFactory getNtlmContextFactory(
final CredentialsProvider credentialsProvider) {
return new HttpClientContextFactory() {
ThreadLocal<HttpClientContext> tlContext = ThreadLocal
.<HttpClientContext> withInitial(() -> {
HttpClientContext context = HttpClientContext.create();
context.setCredentialsProvider(credentialsProvider);
return context;
});
@Override
public HttpClientContext create() {
return tlContext.get();
}
};
}
// then do this when I connect to the server
response = client.execute(postMethod, contextFactory.create());