private boolean handleUnauthorizedAndExit(
      int statusCode,
      Realm realm,
      final Request request,
      HttpResponse response,
      final NettyResponseFuture<?> future,
      ProxyServer proxyServer,
      final Channel channel)
      throws Exception {
    if (statusCode == UNAUTHORIZED.code() && realm != null) {

      List<String> authenticateHeaders =
          response.headers().getAll(HttpHeaders.Names.WWW_AUTHENTICATE);

      if (!authenticateHeaders.isEmpty() && !future.getAndSetAuth(true)) {
        future.setState(NettyResponseFuture.STATE.NEW);
        Realm newRealm = null;
        // NTLM
        boolean negociate = authenticateHeaders.contains("Negotiate");
        if (!authenticateHeaders.contains("Kerberos")
            && (isNTLM(authenticateHeaders) || negociate)) {
          newRealm =
              ntlmChallenge(
                  authenticateHeaders, request, proxyServer, request.getHeaders(), realm, future);
          // SPNEGO KERBEROS
        } else if (negociate) {
          newRealm =
              kerberosChallenge(
                  authenticateHeaders, request, proxyServer, request.getHeaders(), realm, future);
          if (newRealm == null) {
            return true;
          }
        } else {
          newRealm =
              new Realm.RealmBuilder()
                  .clone(realm)
                  .setScheme(realm.getAuthScheme())
                  .setUri(request.getURI().getPath())
                  .setMethodName(request.getMethod())
                  .setUsePreemptiveAuth(true)
                  .parseWWWAuthenticateHeader(authenticateHeaders.get(0))
                  .build();
        }

        Realm nr =
            new Realm.RealmBuilder()
                .clone(newRealm)
                .setUri(URI.create(request.getUrl()).getPath())
                .build();
        final Request nextRequest =
            new RequestBuilder(future.getRequest())
                .setHeaders(request.getHeaders())
                .setRealm(nr)
                .build();

        LOGGER.debug("Sending authentication to {}", request.getUrl());
        Callback callback =
            new Callback(future) {
              public void call() throws Exception {
                channels.drainChannel(channel, future);
                requestSender.sendNextRequest(nextRequest, future);
              }
            };

        if (future.isKeepAlive() && HttpHeaders.isTransferEncodingChunked(response)) {
          // We must make sure there is no bytes left
          // before executing the next request.
          Channels.setDefaultAttribute(channel, callback);
        } else {
          callback.call();
        }

        return true;
      }
    }

    return false;
  }
  protected boolean exitAfterHandlingRedirect( //
      Channel channel, //
      NettyResponseFuture<?> future, //
      HttpResponse response, //
      Request request, //
      int statusCode)
      throws Exception {

    if (followRedirect(config, request) && REDIRECT_STATUSES.contains(statusCode)) {
      if (future.incrementAndGetCurrentRedirectCount() >= config.getMaxRedirects()) {
        throw new MaxRedirectException("Maximum redirect reached: " + config.getMaxRedirects());

      } else {
        // We must allow 401 handling again.
        future.getAndSetAuth(false);

        HttpHeaders responseHeaders = response.headers();
        String location = responseHeaders.get(HttpHeaders.Names.LOCATION);
        Uri uri = Uri.create(future.getUri(), location);

        if (!uri.equals(future.getUri())) {
          final RequestBuilder requestBuilder = new RequestBuilder(future.getRequest());

          if (!config.isRemoveQueryParamOnRedirect())
            requestBuilder.addQueryParams(future.getRequest().getQueryParams());

          // if we are to strictly handle 302, we should keep the original method (which browsers
          // don't)
          // 303 must force GET
          if ((statusCode == FOUND.code() && !config.isStrict302Handling())
              || statusCode == SEE_OTHER.code()) requestBuilder.setMethod("GET");

          // in case of a redirect from HTTP to HTTPS, future attributes might change
          final boolean initialConnectionKeepAlive = future.isKeepAlive();
          final String initialPoolKey = channelManager.getPartitionId(future);

          future.setUri(uri);
          String newUrl = uri.toUrl();
          if (request.getUri().getScheme().startsWith(WEBSOCKET)) {
            newUrl = newUrl.replaceFirst(HTTP, WEBSOCKET);
          }

          logger.debug("Redirecting to {}", newUrl);

          for (String cookieStr : responseHeaders.getAll(HttpHeaders.Names.SET_COOKIE)) {
            Cookie c = CookieDecoder.decode(cookieStr, timeConverter);
            if (c != null) requestBuilder.addOrReplaceCookie(c);
          }

          Callback callback =
              channelManager.newDrainCallback(
                  future, channel, initialConnectionKeepAlive, initialPoolKey);

          if (HttpHeaders.isTransferEncodingChunked(response)) {
            // We must make sure there is no bytes left before
            // executing the next request.
            // FIXME investigate this
            Channels.setAttribute(channel, callback);
          } else {
            // FIXME don't understand: this offers the connection to the pool, or even closes it,
            // while the
            // request has not been sent, right?
            callback.call();
          }

          Request redirectRequest = requestBuilder.setUrl(newUrl).build();
          // FIXME why not reuse the channel is same host?
          requestSender.sendNextRequest(redirectRequest, future);
          return true;
        }
      }
    }
    return false;
  }