You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@lucene.apache.org by da...@apache.org on 2018/11/12 17:28:16 UTC

lucene-solr:jira/http2: Fix precommit

Repository: lucene-solr
Updated Branches:
  refs/heads/jira/http2 7ca1dad37 -> f02f07b9d


Fix precommit


Project: http://git-wip-us.apache.org/repos/asf/lucene-solr/repo
Commit: http://git-wip-us.apache.org/repos/asf/lucene-solr/commit/f02f07b9
Tree: http://git-wip-us.apache.org/repos/asf/lucene-solr/tree/f02f07b9
Diff: http://git-wip-us.apache.org/repos/asf/lucene-solr/diff/f02f07b9

Branch: refs/heads/jira/http2
Commit: f02f07b9d385e02db0b5714e3ff82f63b98c7976
Parents: 7ca1dad
Author: Cao Manh Dat <da...@apache.org>
Authored: Mon Nov 12 17:28:08 2018 +0000
Committer: Cao Manh Dat <da...@apache.org>
Committed: Mon Nov 12 17:28:08 2018 +0000

----------------------------------------------------------------------
 .../solrj/impl/Krb5HttpClientBuilder.java       |   8 +-
 ...PreemptiveBasicAuthClientBuilderFactory.java |   4 +-
 .../client/AuthenticationProtocolHandler.java   | 314 -------------------
 .../SolrAuthenticationProtocolHandler.java      | 314 +++++++++++++++++++
 .../SolrWWWAuthenticationProtocolHandler.java   |  84 +++++
 5 files changed, 406 insertions(+), 318 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/f02f07b9/solr/solrj/src/java/org/apache/solr/client/solrj/impl/Krb5HttpClientBuilder.java
----------------------------------------------------------------------
diff --git a/solr/solrj/src/java/org/apache/solr/client/solrj/impl/Krb5HttpClientBuilder.java b/solr/solrj/src/java/org/apache/solr/client/solrj/impl/Krb5HttpClientBuilder.java
index 0695c98..a98f38f 100644
--- a/solr/solrj/src/java/org/apache/solr/client/solrj/impl/Krb5HttpClientBuilder.java
+++ b/solr/solrj/src/java/org/apache/solr/client/solrj/impl/Krb5HttpClientBuilder.java
@@ -43,9 +43,13 @@ import org.apache.http.cookie.CookieSpecProvider;
 import org.apache.http.entity.BufferedHttpEntity;
 import org.apache.http.impl.auth.SPNegoSchemeFactory;
 import org.apache.http.impl.client.BasicCredentialsProvider;
+import org.eclipse.jetty.client.SolrAuthenticationProtocolHandler;
 import org.eclipse.jetty.client.HttpAuthenticationStore;
 import org.eclipse.jetty.client.SPNEGOAuthentication;
-import org.eclipse.jetty.client.WWWAuthenticationProtocolHandler;
+import org.eclipse.jetty.client.SolrWWWAuthenticationProtocolHandler;
+import org.eclipse.jetty.client.api.Request;
+import org.eclipse.jetty.client.api.Response;
+import org.eclipse.jetty.http.HttpHeader;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -126,7 +130,7 @@ public class Krb5HttpClientBuilder implements HttpClientBuilderFactory {
     HttpAuthenticationStore authenticationStore = new HttpAuthenticationStore();
     authenticationStore.addAuthentication(createSPNEGOAuthentication());
     http2Client.getHttpClient().setAuthenticationStore(authenticationStore);
-    http2Client.getProtocolHandlers().put(new WWWAuthenticationProtocolHandler(http2Client.getHttpClient()));
+    http2Client.getProtocolHandlers().put(new SolrWWWAuthenticationProtocolHandler(http2Client.getHttpClient()));
   }
 
   public SolrHttpClientBuilder getBuilder(SolrHttpClientBuilder builder) {

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/f02f07b9/solr/solrj/src/java/org/apache/solr/client/solrj/impl/PreemptiveBasicAuthClientBuilderFactory.java
----------------------------------------------------------------------
diff --git a/solr/solrj/src/java/org/apache/solr/client/solrj/impl/PreemptiveBasicAuthClientBuilderFactory.java b/solr/solrj/src/java/org/apache/solr/client/solrj/impl/PreemptiveBasicAuthClientBuilderFactory.java
index 3ec0bfb..9f266f5 100644
--- a/solr/solrj/src/java/org/apache/solr/client/solrj/impl/PreemptiveBasicAuthClientBuilderFactory.java
+++ b/solr/solrj/src/java/org/apache/solr/client/solrj/impl/PreemptiveBasicAuthClientBuilderFactory.java
@@ -37,7 +37,7 @@ import org.apache.solr.common.util.StrUtils;
 import org.eclipse.jetty.client.HttpAuthenticationStore;
 import org.eclipse.jetty.client.ProxyAuthenticationProtocolHandler;
 import org.apache.solr.client.solrj.util.SolrBasicAuthentication;
-import org.eclipse.jetty.client.WWWAuthenticationProtocolHandler;
+import org.eclipse.jetty.client.SolrWWWAuthenticationProtocolHandler;
 
 /**
  * HttpClientConfigurer implementation providing support for preemptive Http Basic authentication
@@ -117,7 +117,7 @@ public class PreemptiveBasicAuthClientBuilderFactory implements HttpClientBuilde
     HttpAuthenticationStore authenticationStore = new HttpAuthenticationStore();
     authenticationStore.addAuthentication(new SolrBasicAuthentication(basicAuthUser, basicAuthPass));
     client.getHttpClient().setAuthenticationStore(authenticationStore);
-    client.getProtocolHandlers().put(new WWWAuthenticationProtocolHandler(client.getHttpClient()));
+    client.getProtocolHandlers().put(new SolrWWWAuthenticationProtocolHandler(client.getHttpClient()));
     client.getProtocolHandlers().put(new ProxyAuthenticationProtocolHandler(client.getHttpClient()));
   }
 

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/f02f07b9/solr/solrj/src/java/org/eclipse/jetty/client/AuthenticationProtocolHandler.java
----------------------------------------------------------------------
diff --git a/solr/solrj/src/java/org/eclipse/jetty/client/AuthenticationProtocolHandler.java b/solr/solrj/src/java/org/eclipse/jetty/client/AuthenticationProtocolHandler.java
deleted file mode 100644
index f998944..0000000
--- a/solr/solrj/src/java/org/eclipse/jetty/client/AuthenticationProtocolHandler.java
+++ /dev/null
@@ -1,314 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.eclipse.jetty.client;
-
-import java.net.URI;
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
-
-import org.eclipse.jetty.client.api.Authentication;
-import org.eclipse.jetty.client.api.Authentication.HeaderInfo;
-import org.eclipse.jetty.client.api.Connection;
-import org.eclipse.jetty.client.api.ContentProvider;
-import org.eclipse.jetty.client.api.ContentResponse;
-import org.eclipse.jetty.client.api.Request;
-import org.eclipse.jetty.client.api.Response;
-import org.eclipse.jetty.client.api.Result;
-import org.eclipse.jetty.client.util.BufferingResponseListener;
-import org.eclipse.jetty.http.HttpField;
-import org.eclipse.jetty.http.HttpHeader;
-import org.eclipse.jetty.http.QuotedCSV;
-import org.eclipse.jetty.util.log.Log;
-import org.eclipse.jetty.util.log.Logger;
-
-public abstract class AuthenticationProtocolHandler implements ProtocolHandler
-{
-  public static final int DEFAULT_MAX_CONTENT_LENGTH = 16*1024;
-  public static final Logger LOG = Log.getLogger(AuthenticationProtocolHandler.class);
-  private final HttpClient client;
-  private final int maxContentLength;
-  private final ResponseNotifier notifier;
-
-  private static final Pattern CHALLENGE_PATTERN = Pattern.compile("(?<schemeOnly>[!#$%&'*+\\-.^_`|~0-9A-Za-z]+)|(?:(?<scheme>[!#$%&'*+\\-.^_`|~0-9A-Za-z]+)\\s+)?(?:(?<token68>[a-zA-Z0-9\\-._~+/]+=*)|(?<paramName>[!#$%&'*+\\-.^_`|~0-9A-Za-z]+)\\s*=\\s*(?:(?<paramValue>.*)))");
-
-  protected AuthenticationProtocolHandler(HttpClient client, int maxContentLength)
-  {
-    this.client = client;
-    this.maxContentLength = maxContentLength;
-    this.notifier = new ResponseNotifier();
-  }
-
-  protected HttpClient getHttpClient()
-  {
-    return client;
-  }
-
-  protected abstract HttpHeader getAuthenticateHeader();
-
-  protected abstract HttpHeader getAuthorizationHeader();
-
-  protected abstract URI getAuthenticationURI(Request request);
-
-  protected abstract String getAuthenticationAttribute();
-
-  @Override
-  public Response.Listener getResponseListener()
-  {
-    // Return new instances every time to keep track of the response content
-    return new AuthenticationListener();
-  }
-
-
-  protected List<HeaderInfo> getHeaderInfo(String header) throws IllegalArgumentException
-  {
-    List<HeaderInfo> headerInfos = new ArrayList<>();
-    Matcher m;
-
-    for(String value : new QuotedCSV(true, header))
-    {
-      m = CHALLENGE_PATTERN.matcher(value);
-      if (m.matches())
-      {
-        if(m.group("schemeOnly") != null)
-        {
-          headerInfos.add(new HeaderInfo(getAuthorizationHeader(), m.group(1), new HashMap<>()));
-          continue;
-        }
-
-        if (m.group("scheme") != null)
-        {
-          headerInfos.add(new HeaderInfo(getAuthorizationHeader(), m.group("scheme"), new HashMap<>()));
-        }
-
-        if (headerInfos.isEmpty())
-          throw new IllegalArgumentException("Parameters without auth-scheme");
-
-        Map<String, String> authParams = headerInfos.get(headerInfos.size() - 1).getParameters();
-        if (m.group("paramName") != null)
-        {
-          String paramVal = QuotedCSV.unquote(m.group("paramValue"));
-          authParams.put(m.group("paramName"), paramVal);
-        }
-        else if (m.group("token68") != null)
-        {
-          if (!authParams.isEmpty())
-            throw new IllegalArgumentException("token68 after auth-params");
-
-          authParams.put("base64", m.group("token68"));
-        }
-      }
-    }
-
-    return headerInfos;
-  }
-
-  private class AuthenticationListener extends BufferingResponseListener
-  {
-    private AuthenticationListener()
-    {
-      super(maxContentLength);
-    }
-
-    @Override
-    public void onComplete(Result result)
-    {
-      HttpRequest request = (HttpRequest)result.getRequest();
-      ContentResponse response = new HttpContentResponse(result.getResponse(), getContent(), getMediaType(), getEncoding());
-      if (result.getResponseFailure() != null)
-      {
-        if (LOG.isDebugEnabled())
-          LOG.debug("Authentication challenge failed {}", result.getFailure());
-        forwardFailureComplete(request, result.getRequestFailure(), response, result.getResponseFailure());
-        return;
-      }
-
-      String authenticationAttribute = getAuthenticationAttribute();
-      HttpConversation conversation = request.getConversation();
-      if (conversation.getAttribute(authenticationAttribute) != null)
-      {
-        // We have already tried to authenticate, but we failed again.
-        if (LOG.isDebugEnabled())
-          LOG.debug("Bad credentials for {}", request);
-        forwardSuccessComplete(request, response);
-        return;
-      }
-
-      HttpHeader header = getAuthenticateHeader();
-      List<Authentication.HeaderInfo> headerInfos = parseAuthenticateHeader(response, header);
-      if (headerInfos.isEmpty())
-      {
-        if (LOG.isDebugEnabled())
-          LOG.debug("Authentication challenge without {} header", header);
-        forwardFailureComplete(request, result.getRequestFailure(), response, new HttpResponseException("HTTP protocol violation: Authentication challenge without " + header + " header", response));
-        return;
-      }
-
-      Authentication authentication = null;
-      Authentication.HeaderInfo headerInfo = null;
-      URI authURI = resolveURI(request, getAuthenticationURI(request));
-      if (authURI != null)
-      {
-        for (Authentication.HeaderInfo element : headerInfos)
-        {
-          authentication = client.getAuthenticationStore().findAuthentication(element.getType(), authURI, element.getRealm());
-          if (authentication != null)
-          {
-            headerInfo = element;
-            break;
-          }
-        }
-      }
-      if (authentication == null)
-      {
-        if (LOG.isDebugEnabled())
-          LOG.debug("No authentication available for {}", request);
-        forwardSuccessComplete(request, response);
-        return;
-      }
-
-      ContentProvider requestContent = request.getContent();
-      if (requestContent != null && !requestContent.isReproducible())
-      {
-        if (LOG.isDebugEnabled())
-          LOG.debug("Request content not reproducible for {}", request);
-        forwardSuccessComplete(request, response);
-        return;
-      }
-
-      try
-      {
-        Authentication.Result authnResult = authentication.authenticate(request, response, headerInfo, conversation);
-        if (LOG.isDebugEnabled())
-          LOG.debug("Authentication result {}", authnResult);
-        if (authnResult == null)
-        {
-          forwardSuccessComplete(request, response);
-          return;
-        }
-
-        conversation.setAttribute(authenticationAttribute, true);
-
-        URI requestURI = request.getURI();
-        String path = null;
-        if (requestURI == null)
-        {
-          requestURI = resolveURI(request, null);
-          path = request.getPath();
-        }
-        Request newRequest = client.copyRequest(request, requestURI);
-        if (path != null)
-          newRequest.path(path);
-
-        authnResult.apply(newRequest);
-        // Copy existing, explicitly set, authorization headers.
-        copyIfAbsent(request, newRequest, HttpHeader.AUTHORIZATION);
-        copyIfAbsent(request, newRequest, HttpHeader.PROXY_AUTHORIZATION);
-
-        AfterAuthenticationListener listener = new AfterAuthenticationListener(authnResult);
-        Connection connection = (Connection)request.getAttributes().get(Connection.class.getName());
-        if (connection != null)
-          connection.send(newRequest, listener);
-        else
-          newRequest.send(listener);
-      }
-      catch (Throwable x)
-      {
-        if (LOG.isDebugEnabled())
-          LOG.debug("Authentication failed", x);
-        forwardFailureComplete(request, null, response, x);
-      }
-    }
-
-    private URI resolveURI(HttpRequest request, URI uri)
-    {
-      if (uri != null)
-        return uri;
-      String target = request.getScheme() + "://" + request.getHost();
-      int port = request.getPort();
-      if (port > 0)
-        target += ":" + port;
-      return URI.create(target);
-    }
-
-    private void copyIfAbsent(HttpRequest oldRequest, Request newRequest, HttpHeader header)
-    {
-      HttpField field = oldRequest.getHeaders().getField(header);
-      if (field != null && !newRequest.getHeaders().contains(header))
-        newRequest.getHeaders().put(field);
-    }
-
-    private void forwardSuccessComplete(HttpRequest request, Response response)
-    {
-      HttpConversation conversation = request.getConversation();
-      conversation.updateResponseListeners(null);
-      notifier.forwardSuccessComplete(conversation.getResponseListeners(), request, response);
-    }
-
-    private void forwardFailureComplete(HttpRequest request, Throwable requestFailure, Response response, Throwable responseFailure)
-    {
-      HttpConversation conversation = request.getConversation();
-      conversation.updateResponseListeners(null);
-      List<Response.ResponseListener> responseListeners = conversation.getResponseListeners();
-      if (responseFailure == null)
-        notifier.forwardSuccess(responseListeners, response);
-      else
-        notifier.forwardFailure(responseListeners, response, responseFailure);
-      notifier.notifyComplete(responseListeners, new Result(request, requestFailure, response, responseFailure));
-    }
-
-    private List<Authentication.HeaderInfo> parseAuthenticateHeader(Response response, HttpHeader header)
-    {
-      // TODO: these should be ordered by strength
-      List<Authentication.HeaderInfo> result = new ArrayList<>();
-      List<String> values = response.getHeaders().getValuesList(header);
-      for (String value : values)
-      {
-        try
-        {
-          result.addAll(getHeaderInfo(value));
-        }
-        catch(IllegalArgumentException e)
-        {
-          if (LOG.isDebugEnabled())
-            LOG.debug("Failed to parse authentication header", e);
-        }
-      }
-      return result;
-    }
-  }
-
-  private class AfterAuthenticationListener extends Response.Listener.Adapter
-  {
-    private final Authentication.Result authenticationResult;
-
-    private AfterAuthenticationListener(Authentication.Result authenticationResult)
-    {
-      this.authenticationResult = authenticationResult;
-    }
-
-    @Override
-    public void onSuccess(Response response)
-    {
-      client.getAuthenticationStore().addAuthenticationResult(authenticationResult);
-    }
-  }
-}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/f02f07b9/solr/solrj/src/java/org/eclipse/jetty/client/SolrAuthenticationProtocolHandler.java
----------------------------------------------------------------------
diff --git a/solr/solrj/src/java/org/eclipse/jetty/client/SolrAuthenticationProtocolHandler.java b/solr/solrj/src/java/org/eclipse/jetty/client/SolrAuthenticationProtocolHandler.java
new file mode 100644
index 0000000..6d40b38
--- /dev/null
+++ b/solr/solrj/src/java/org/eclipse/jetty/client/SolrAuthenticationProtocolHandler.java
@@ -0,0 +1,314 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.eclipse.jetty.client;
+
+import java.net.URI;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import org.eclipse.jetty.client.api.Authentication;
+import org.eclipse.jetty.client.api.Authentication.HeaderInfo;
+import org.eclipse.jetty.client.api.Connection;
+import org.eclipse.jetty.client.api.ContentProvider;
+import org.eclipse.jetty.client.api.ContentResponse;
+import org.eclipse.jetty.client.api.Request;
+import org.eclipse.jetty.client.api.Response;
+import org.eclipse.jetty.client.api.Result;
+import org.eclipse.jetty.client.util.BufferingResponseListener;
+import org.eclipse.jetty.http.HttpField;
+import org.eclipse.jetty.http.HttpHeader;
+import org.eclipse.jetty.http.QuotedCSV;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+
+public abstract class SolrAuthenticationProtocolHandler implements ProtocolHandler
+{
+  public static final int DEFAULT_MAX_CONTENT_LENGTH = 16*1024;
+  public static final Logger LOG = Log.getLogger(SolrAuthenticationProtocolHandler.class);
+  private final HttpClient client;
+  private final int maxContentLength;
+  private final ResponseNotifier notifier;
+
+  private static final Pattern CHALLENGE_PATTERN = Pattern.compile("(?<schemeOnly>[!#$%&'*+\\-.^_`|~0-9A-Za-z]+)|(?:(?<scheme>[!#$%&'*+\\-.^_`|~0-9A-Za-z]+)\\s+)?(?:(?<token68>[a-zA-Z0-9\\-._~+/]+=*)|(?<paramName>[!#$%&'*+\\-.^_`|~0-9A-Za-z]+)\\s*=\\s*(?:(?<paramValue>.*)))");
+
+  protected SolrAuthenticationProtocolHandler(HttpClient client, int maxContentLength)
+  {
+    this.client = client;
+    this.maxContentLength = maxContentLength;
+    this.notifier = new ResponseNotifier();
+  }
+
+  protected HttpClient getHttpClient()
+  {
+    return client;
+  }
+
+  protected abstract HttpHeader getAuthenticateHeader();
+
+  protected abstract HttpHeader getAuthorizationHeader();
+
+  protected abstract URI getAuthenticationURI(Request request);
+
+  protected abstract String getAuthenticationAttribute();
+
+  @Override
+  public Response.Listener getResponseListener()
+  {
+    // Return new instances every time to keep track of the response content
+    return new AuthenticationListener();
+  }
+
+
+  protected List<HeaderInfo> getHeaderInfo(String header) throws IllegalArgumentException
+  {
+    List<HeaderInfo> headerInfos = new ArrayList<>();
+    Matcher m;
+
+    for(String value : new QuotedCSV(true, header))
+    {
+      m = CHALLENGE_PATTERN.matcher(value);
+      if (m.matches())
+      {
+        if(m.group("schemeOnly") != null)
+        {
+          headerInfos.add(new HeaderInfo(getAuthorizationHeader(), m.group(1), new HashMap<>()));
+          continue;
+        }
+
+        if (m.group("scheme") != null)
+        {
+          headerInfos.add(new HeaderInfo(getAuthorizationHeader(), m.group("scheme"), new HashMap<>()));
+        }
+
+        if (headerInfos.isEmpty())
+          throw new IllegalArgumentException("Parameters without auth-scheme");
+
+        Map<String, String> authParams = headerInfos.get(headerInfos.size() - 1).getParameters();
+        if (m.group("paramName") != null)
+        {
+          String paramVal = QuotedCSV.unquote(m.group("paramValue"));
+          authParams.put(m.group("paramName"), paramVal);
+        }
+        else if (m.group("token68") != null)
+        {
+          if (!authParams.isEmpty())
+            throw new IllegalArgumentException("token68 after auth-params");
+
+          authParams.put("base64", m.group("token68"));
+        }
+      }
+    }
+
+    return headerInfos;
+  }
+
+  private class AuthenticationListener extends BufferingResponseListener
+  {
+    private AuthenticationListener()
+    {
+      super(maxContentLength);
+    }
+
+    @Override
+    public void onComplete(Result result)
+    {
+      HttpRequest request = (HttpRequest)result.getRequest();
+      ContentResponse response = new HttpContentResponse(result.getResponse(), getContent(), getMediaType(), getEncoding());
+      if (result.getResponseFailure() != null)
+      {
+        if (LOG.isDebugEnabled())
+          LOG.debug("Authentication challenge failed {}", result.getFailure());
+        forwardFailureComplete(request, result.getRequestFailure(), response, result.getResponseFailure());
+        return;
+      }
+
+      String authenticationAttribute = getAuthenticationAttribute();
+      HttpConversation conversation = request.getConversation();
+      if (conversation.getAttribute(authenticationAttribute) != null)
+      {
+        // We have already tried to authenticate, but we failed again.
+        if (LOG.isDebugEnabled())
+          LOG.debug("Bad credentials for {}", request);
+        forwardSuccessComplete(request, response);
+        return;
+      }
+
+      HttpHeader header = getAuthenticateHeader();
+      List<Authentication.HeaderInfo> headerInfos = parseAuthenticateHeader(response, header);
+      if (headerInfos.isEmpty())
+      {
+        if (LOG.isDebugEnabled())
+          LOG.debug("Authentication challenge without {} header", header);
+        forwardFailureComplete(request, result.getRequestFailure(), response, new HttpResponseException("HTTP protocol violation: Authentication challenge without " + header + " header", response));
+        return;
+      }
+
+      Authentication authentication = null;
+      Authentication.HeaderInfo headerInfo = null;
+      URI authURI = resolveURI(request, getAuthenticationURI(request));
+      if (authURI != null)
+      {
+        for (Authentication.HeaderInfo element : headerInfos)
+        {
+          authentication = client.getAuthenticationStore().findAuthentication(element.getType(), authURI, element.getRealm());
+          if (authentication != null)
+          {
+            headerInfo = element;
+            break;
+          }
+        }
+      }
+      if (authentication == null)
+      {
+        if (LOG.isDebugEnabled())
+          LOG.debug("No authentication available for {}", request);
+        forwardSuccessComplete(request, response);
+        return;
+      }
+
+      ContentProvider requestContent = request.getContent();
+      if (requestContent != null && !requestContent.isReproducible())
+      {
+        if (LOG.isDebugEnabled())
+          LOG.debug("Request content not reproducible for {}", request);
+        forwardSuccessComplete(request, response);
+        return;
+      }
+
+      try
+      {
+        Authentication.Result authnResult = authentication.authenticate(request, response, headerInfo, conversation);
+        if (LOG.isDebugEnabled())
+          LOG.debug("Authentication result {}", authnResult);
+        if (authnResult == null)
+        {
+          forwardSuccessComplete(request, response);
+          return;
+        }
+
+        conversation.setAttribute(authenticationAttribute, true);
+
+        URI requestURI = request.getURI();
+        String path = null;
+        if (requestURI == null)
+        {
+          requestURI = resolveURI(request, null);
+          path = request.getPath();
+        }
+        Request newRequest = client.copyRequest(request, requestURI);
+        if (path != null)
+          newRequest.path(path);
+
+        authnResult.apply(newRequest);
+        // Copy existing, explicitly set, authorization headers.
+        copyIfAbsent(request, newRequest, HttpHeader.AUTHORIZATION);
+        copyIfAbsent(request, newRequest, HttpHeader.PROXY_AUTHORIZATION);
+
+        AfterAuthenticationListener listener = new AfterAuthenticationListener(authnResult);
+        Connection connection = (Connection)request.getAttributes().get(Connection.class.getName());
+        if (connection != null)
+          connection.send(newRequest, listener);
+        else
+          newRequest.send(listener);
+      }
+      catch (Throwable x)
+      {
+        if (LOG.isDebugEnabled())
+          LOG.debug("Authentication failed", x);
+        forwardFailureComplete(request, null, response, x);
+      }
+    }
+
+    private URI resolveURI(HttpRequest request, URI uri)
+    {
+      if (uri != null)
+        return uri;
+      String target = request.getScheme() + "://" + request.getHost();
+      int port = request.getPort();
+      if (port > 0)
+        target += ":" + port;
+      return URI.create(target);
+    }
+
+    private void copyIfAbsent(HttpRequest oldRequest, Request newRequest, HttpHeader header)
+    {
+      HttpField field = oldRequest.getHeaders().getField(header);
+      if (field != null && !newRequest.getHeaders().contains(header))
+        newRequest.getHeaders().put(field);
+    }
+
+    private void forwardSuccessComplete(HttpRequest request, Response response)
+    {
+      HttpConversation conversation = request.getConversation();
+      conversation.updateResponseListeners(null);
+      notifier.forwardSuccessComplete(conversation.getResponseListeners(), request, response);
+    }
+
+    private void forwardFailureComplete(HttpRequest request, Throwable requestFailure, Response response, Throwable responseFailure)
+    {
+      HttpConversation conversation = request.getConversation();
+      conversation.updateResponseListeners(null);
+      List<Response.ResponseListener> responseListeners = conversation.getResponseListeners();
+      if (responseFailure == null)
+        notifier.forwardSuccess(responseListeners, response);
+      else
+        notifier.forwardFailure(responseListeners, response, responseFailure);
+      notifier.notifyComplete(responseListeners, new Result(request, requestFailure, response, responseFailure));
+    }
+
+    private List<Authentication.HeaderInfo> parseAuthenticateHeader(Response response, HttpHeader header)
+    {
+      // TODO: these should be ordered by strength
+      List<Authentication.HeaderInfo> result = new ArrayList<>();
+      List<String> values = response.getHeaders().getValuesList(header);
+      for (String value : values)
+      {
+        try
+        {
+          result.addAll(getHeaderInfo(value));
+        }
+        catch(IllegalArgumentException e)
+        {
+          if (LOG.isDebugEnabled())
+            LOG.debug("Failed to parse authentication header", e);
+        }
+      }
+      return result;
+    }
+  }
+
+  private class AfterAuthenticationListener extends Response.Listener.Adapter
+  {
+    private final Authentication.Result authenticationResult;
+
+    private AfterAuthenticationListener(Authentication.Result authenticationResult)
+    {
+      this.authenticationResult = authenticationResult;
+    }
+
+    @Override
+    public void onSuccess(Response response)
+    {
+      client.getAuthenticationStore().addAuthenticationResult(authenticationResult);
+    }
+  }
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/f02f07b9/solr/solrj/src/java/org/eclipse/jetty/client/SolrWWWAuthenticationProtocolHandler.java
----------------------------------------------------------------------
diff --git a/solr/solrj/src/java/org/eclipse/jetty/client/SolrWWWAuthenticationProtocolHandler.java b/solr/solrj/src/java/org/eclipse/jetty/client/SolrWWWAuthenticationProtocolHandler.java
new file mode 100644
index 0000000..b64372a
--- /dev/null
+++ b/solr/solrj/src/java/org/eclipse/jetty/client/SolrWWWAuthenticationProtocolHandler.java
@@ -0,0 +1,84 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.eclipse.jetty.client;
+
+import java.net.URI;
+
+import org.eclipse.jetty.client.api.Request;
+import org.eclipse.jetty.client.api.Response;
+import org.eclipse.jetty.http.HttpHeader;
+import org.eclipse.jetty.http.HttpStatus;
+
+/**
+ * <p>A protocol handler that handles the 401 response code
+ * in association with the {@code WWW-Authenticate} header.</p>
+ *
+ * @see ProxyAuthenticationProtocolHandler
+ */
+public class SolrWWWAuthenticationProtocolHandler extends SolrAuthenticationProtocolHandler
+{
+  public static final String NAME = "www-authenticate";
+  private static final String ATTRIBUTE = SolrWWWAuthenticationProtocolHandler.class.getName() + ".attribute";
+
+  public SolrWWWAuthenticationProtocolHandler(HttpClient client)
+  {
+    this(client, DEFAULT_MAX_CONTENT_LENGTH);
+  }
+
+  public SolrWWWAuthenticationProtocolHandler(HttpClient client, int maxContentLength)
+  {
+    super(client, maxContentLength);
+  }
+
+  @Override
+  public String getName()
+  {
+    return NAME;
+  }
+
+  @Override
+  public boolean accept(Request request, Response response)
+  {
+    return response.getStatus() == HttpStatus.UNAUTHORIZED_401;
+  }
+
+  @Override
+  protected HttpHeader getAuthenticateHeader()
+  {
+    return HttpHeader.WWW_AUTHENTICATE;
+  }
+
+  @Override
+  protected HttpHeader getAuthorizationHeader()
+  {
+    return HttpHeader.AUTHORIZATION;
+  }
+
+  @Override
+  protected URI getAuthenticationURI(Request request)
+  {
+    return request.getURI();
+  }
+
+  @Override
+  protected String getAuthenticationAttribute()
+  {
+    return ATTRIBUTE;
+  }
+}
+