Andrey K created HTTPCLIENT-1762:
------------------------------------

             Summary: Proxy reuses a fully established routed connection in 
attempt to CONNECT to target again causing 400 and NPE
                 Key: HTTPCLIENT-1762
                 URL: https://issues.apache.org/jira/browse/HTTPCLIENT-1762
             Project: HttpComponents HttpClient
          Issue Type: Bug
          Components: HttpClient
            Reporter: Andrey K


First I noticed I was getting sporadic errors when attempting to make an HTTPS 
call via HTTP Proxy that requires authentication:

java.lang.IllegalArgumentException: Auth scheme may not be null
        at org.apache.http.util.Args.notNull(Args.java:54)
        at 
org.apache.http.impl.client.AuthenticationStrategyImpl.authSucceeded(AuthenticationStrategyImpl.java:215)
        at 
org.apache.http.impl.client.ProxyAuthenticationStrategy.authSucceeded(ProxyAuthenticationStrategy.java:43)
        at 
org.apache.http.impl.auth.HttpAuthenticator.isAuthenticationRequested(HttpAuthenticator.java:88)
        at 
org.apache.http.impl.nio.client.MainClientExec.needAuthentication(MainClientExec.java:629)
        at 
org.apache.http.impl.nio.client.MainClientExec.handleResponse(MainClientExec.java:569)
        at 
org.apache.http.impl.nio.client.MainClientExec.responseReceived(MainClientExec.java:309)
        at 
org.apache.http.impl.nio.client.DefaultClientExchangeHandlerImpl.responseReceived(DefaultClientExchangeHandlerImpl.java:147)
        at 
org.apache.http.nio.protocol.HttpAsyncRequestExecutor.responseReceived(HttpAsyncRequestExecutor.java:303)
        at 
org.apache.http.impl.nio.DefaultNHttpClientConnection.consumeInput(DefaultNHttpClientConnection.java:255)
        at 
org.apache.http.impl.nio.client.InternalIODispatch.onInputReady(InternalIODispatch.java:81)
        at 
org.apache.http.impl.nio.client.InternalIODispatch.onInputReady(InternalIODispatch.java:39)
        at 
org.apache.http.impl.nio.reactor.AbstractIODispatch.inputReady(AbstractIODispatch.java:121)
        at 
org.apache.http.impl.nio.reactor.BaseIOReactor.readable(BaseIOReactor.java:162)
        at 
org.apache.http.impl.nio.reactor.AbstractIOReactor.processEvent(AbstractIOReactor.java:337)
        at 
org.apache.http.impl.nio.reactor.AbstractIOReactor.processEvents(AbstractIOReactor.java:315)
        at 
org.apache.http.impl.nio.reactor.AbstractIOReactor.execute(AbstractIOReactor.java:276)
        at 
org.apache.http.impl.nio.reactor.BaseIOReactor.execute(BaseIOReactor.java:104)
        at 
org.apache.http.impl.nio.reactor.AbstractMultiworkerIOReactor$Worker.run(AbstractMultiworkerIOReactor.java:588)

Upon closer look with trace debug, I saw that it was breaking on an unexpected 
400 status code coming back against a CONNECT+AUTH request. When I traced the 
connection that was used for this connect, I saw that it was a fully connected 
routed connection. What happened was the HttpClient got a 407 after attempting 
to do a CONNECT and that 407 contained instruction to close connection. 
HttpClient discarded the connection and obtained a new one from the pool but 
the one that was leased was already fully authenticated and tunneled. Thus the 
CONNECT request was sent to the actual tunneled target and returned with a 400.

As a workaround I have enhanced a custom ProxyAuthenticationStrategy to rename 
the USER_TOKEN context value to a temporary unique value to prevent reusing a 
fully established connection and then revert it back after authentication is 
sucessful:

  static AtomicLong counter = new AtomicLong(0);

  static final String ORIGINAL_USER_TOKEN = "ORIGINAL_USER_TOKEN";

  @Override
  public Map<String, Header> getChallenges(HttpHost authhost, HttpResponse 
response, HttpContext context) throws MalformedChallengeException {
    String originalUserToken = 
(String)context.getAttribute(ORIGINAL_USER_TOKEN);
    if (originalUserToken == null) {
      originalUserToken = 
(String)context.getAttribute(HttpClientContext.USER_TOKEN);
      context.setAttribute(ORIGINAL_USER_TOKEN, originalUserToken);
      String temporaryName = "NON-REUSABLE-" + counter.incrementAndGet();
      context.setAttribute(HttpClientContext.USER_TOKEN, temporaryName);
      log.trace("OnAuth: Renaming connection from {} to {}", originalUserToken, 
temporaryName);
    }
    return super.getChallenges(authhost, response, context);
  }

  @Override
  public void authSucceeded(HttpHost authhost, AuthScheme authScheme, 
HttpContext context) {
    String originalUserToken = 
(String)context.getAttribute(ORIGINAL_USER_TOKEN);
    if (originalUserToken != null) {
      String tempUserToken = 
(String)context.getAttribute(HttpClientContext.USER_TOKEN);
      context.setAttribute(HttpClientContext.USER_TOKEN, originalUserToken);
      context.removeAttribute(ORIGINAL_USER_TOKEN);
      log.trace("OnAuthSucc: Renaming connection from {} to {}", tempUserToken, 
originalUserToken);
    }
    super.authSucceeded(authhost, authScheme, context);
  }



--
This message was sent by Atlassian JIRA
(v6.3.4#6332)

---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to