All Downloads are FREE. Search and download functionalities are using the official Maven repository.

io.edurt.datacap.pinot.com.ning.http.client.providers.grizzly.AhcEventFilter Maven / Gradle / Ivy

There is a newer version: 2024.03.7
Show newest version
/*
 * Copyright (c) 2012-2015 Sonatype, Inc. All rights reserved.
 *
 * This program is licensed to you under the Apache License Version 2.0,
 * and you may not use this file except in compliance with the Apache License Version 2.0.
 * You may obtain a copy of the Apache License Version 2.0 at http://www.apache.org/licenses/LICENSE-2.0.
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the Apache License Version 2.0 is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the Apache License Version 2.0 for the specific language governing permissions and limitations there under.
 */
package io.edurt.datacap.pinot.com.ning.http.client.providers.grizzly;

import io.edurt.datacap.pinot.com.ning.http.client.providers.grizzly.events.SSLSwitchingEvent;
import io.edurt.datacap.pinot.com.ning.http.client.providers.grizzly.events.GracefulCloseEvent;
import io.edurt.datacap.pinot.com.ning.http.client.providers.grizzly.websocket.GrizzlyWebSocketAdapter;
import io.edurt.datacap.pinot.com.ning.http.client.AsyncHandler;
import io.edurt.datacap.pinot.com.ning.http.client.FluentCaseInsensitiveStringsMap;
import io.edurt.datacap.pinot.com.ning.http.client.MaxRedirectException;
import io.edurt.datacap.pinot.com.ning.http.client.Realm;
import io.edurt.datacap.pinot.com.ning.http.client.Realm.AuthScheme;
import io.edurt.datacap.pinot.com.ning.http.client.Request;
import io.edurt.datacap.pinot.com.ning.http.client.RequestBuilder;
import io.edurt.datacap.pinot.com.ning.http.client.cookie.CookieDecoder;
import io.edurt.datacap.pinot.com.ning.http.client.filter.FilterContext;
import io.edurt.datacap.pinot.com.ning.http.client.filter.ResponseFilter;
import io.edurt.datacap.pinot.com.ning.http.client.listener.TransferCompletionHandler;
import io.edurt.datacap.pinot.com.ning.http.client.ntlm.NTLMEngine;
import io.edurt.datacap.pinot.com.ning.http.client.ntlm.NTLMEngineException;
import io.edurt.datacap.pinot.com.ning.http.client.providers.grizzly.events.ContinueEvent;
import io.edurt.datacap.pinot.com.ning.http.client.uri.Uri;
import io.edurt.datacap.pinot.com.ning.http.client.ws.WebSocketUpgradeHandler;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import org.glassfish.grizzly.Buffer;
import org.glassfish.grizzly.Connection;
import org.glassfish.grizzly.filterchain.FilterChainContext;
import org.glassfish.grizzly.filterchain.FilterChainEvent;
import org.glassfish.grizzly.filterchain.NextAction;
import org.glassfish.grizzly.http.HttpClientFilter;
import org.glassfish.grizzly.http.HttpContent;
import org.glassfish.grizzly.http.HttpContext;
import org.glassfish.grizzly.http.HttpHeader;
import org.glassfish.grizzly.http.HttpResponsePacket;
import org.glassfish.grizzly.http.util.Header;
import org.glassfish.grizzly.http.util.HttpStatus;
import org.glassfish.grizzly.utils.Exceptions;
import org.glassfish.grizzly.utils.IdleTimeoutFilter;
import org.glassfish.grizzly.websockets.WebSocketHolder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import static io.edurt.datacap.pinot.com.ning.http.client.providers.netty.util.HttpUtils.getNTLM;
import static io.edurt.datacap.pinot.com.ning.http.util.AsyncHttpProviderUtils.*;
import static io.edurt.datacap.pinot.com.ning.http.util.MiscUtils.isNonEmpty;
/**
 * AHC {@link HttpClientFilter} implementation.
 * 
 * @author Grizzly Team
 */
final class AhcEventFilter extends HttpClientFilter {
    private final static Logger LOGGER =
            LoggerFactory.getLogger(AhcEventFilter.class);

    private static final Map HANDLER_MAP =
            new HashMap(8);
    
    private static IOException notKeepAliveReason;
    
    private final GrizzlyAsyncHttpProvider provider;
    
    // -------------------------------------------------------- Constructors

    AhcEventFilter(final GrizzlyAsyncHttpProvider provider,
            final int maxHerdersSizeProperty) {
        
        super(maxHerdersSizeProperty);
        this.provider = provider;
        HANDLER_MAP.put(HttpStatus.UNAUTHORIZED_401.getStatusCode(), AuthorizationHandler.INSTANCE);
        HANDLER_MAP.put(HttpStatus.MOVED_PERMANENTLY_301.getStatusCode(), RedirectHandler.INSTANCE);
        HANDLER_MAP.put(HttpStatus.FOUND_302.getStatusCode(), RedirectHandler.INSTANCE);
        HANDLER_MAP.put(HttpStatus.SEE_OTHER_303.getStatusCode(), RedirectHandler.INSTANCE);
        HANDLER_MAP.put(HttpStatus.TEMPORARY_REDIRECT_307.getStatusCode(), RedirectHandler.INSTANCE);
        HANDLER_MAP.put(HttpStatus.PERMANENT_REDIRECT_308.getStatusCode(), RedirectHandler.INSTANCE);
    }

    // --------------------------------------- Methods from HttpClientFilter

    @Override
    public NextAction handleEvent(final FilterChainContext ctx,
            final FilterChainEvent event) throws IOException {
        
        if (event.type() == GracefulCloseEvent.class) {
            // Connection was closed.
            // This event is fired only for responses, which don't have
            // associated transfer-encoding or content-length.
            // We have to complete such a request-response processing gracefully.
            final GracefulCloseEvent closeEvent =
                    (GracefulCloseEvent) event;
            final HttpResponsePacket response = closeEvent.getHttpTxContext().responsePacket;
            response.getProcessingState().getHttpContext().attach(ctx);
            onHttpPacketParsed(response, ctx);
            return ctx.getStopAction();
        }
        return ctx.getInvokeAction();
    }

    @Override
    public void exceptionOccurred(final FilterChainContext ctx,
            final Throwable error) {
        ctx.getCloseable().closeWithReason(Exceptions.makeIOException(error));
    }

    @Override
    protected void onHttpContentParsed(final HttpContent content,
            final FilterChainContext ctx) {
        
        final HttpTransactionContext context =
                HttpTransactionContext.currentTransaction(content.getHttpHeader());
        final AsyncHandler handler = context.getAsyncHandler();
        if (handler != null && context.currentState != AsyncHandler.STATE.ABORT) {
            try {
                context.currentState = handler.onBodyPartReceived(
                        new GrizzlyResponseBodyPart(content, ctx.getConnection()));
            } catch (Exception e) {
                handler.onThrowable(e);
            }
        }
    }

    @Override
    protected void onHttpHeadersEncoded(final HttpHeader httpHeader,
            final FilterChainContext ctx) {
        final HttpTransactionContext context =
                HttpTransactionContext.currentTransaction(httpHeader);
        final AsyncHandler handler = context.getAsyncHandler();
        if (handler instanceof TransferCompletionHandler) {
            ((TransferCompletionHandler) handler).onHeaderWriteCompleted();
        }
    }

    @Override
    protected void onHttpContentEncoded(final HttpContent content,
            final FilterChainContext ctx) {
        
        final HttpTransactionContext context =
                HttpTransactionContext.currentTransaction(content.getHttpHeader());
        
        final AsyncHandler handler = context.getAsyncHandler();
        if (handler instanceof TransferCompletionHandler) {
            final int written = content.getContent().remaining();
            context.totalBodyWritten += written;
            final long total = context.totalBodyWritten;
            ((TransferCompletionHandler) handler).onContentWriteProgress(
                    written, total, content.getHttpHeader().getContentLength());
        }
    }

    @Override
    protected void onInitialLineParsed(final HttpHeader httpHeader,
            final FilterChainContext ctx) {
        
        super.onInitialLineParsed(httpHeader, ctx);
        if (httpHeader.isSkipRemainder()) {
            return;
        }
        final HttpResponsePacket responsePacket = (HttpResponsePacket) httpHeader;
        final HttpTransactionContext context =
                HttpTransactionContext.currentTransaction(httpHeader);
        final int status = responsePacket.getStatus();
        if (context.establishingTunnel && HttpStatus.OK_200.statusMatches(status)) {
            return;
        }
        if (HttpStatus.CONINTUE_100.statusMatches(status)) {
            ctx.notifyUpstream(new ContinueEvent(context));            
            return;
        }

        final StatusHandler sh = context.statusHandler;
        context.statusHandler = null;
        
        if (sh != null &&
                !sh.handlesStatus(status)) {
            context.invocationStatus = StatusHandler.InvocationStatus.CONTINUE;
        }
        
        final boolean isRedirectAllowed = isRedirectAllowed(context);
        
        if (context.invocationStatus == StatusHandler.InvocationStatus.CONTINUE) {
            if (HANDLER_MAP.containsKey(status)) {
                context.statusHandler = HANDLER_MAP.get(status);
            }
            if (context.statusHandler instanceof RedirectHandler
                    && !isRedirectAllowed) {
                context.statusHandler = null;
            }
        }
        
        if (isRedirectAllowed) {
            if (isRedirect(status)) {
                if (context.statusHandler == null) {
                    context.statusHandler = RedirectHandler.INSTANCE;
                }
                context.redirectCount++;
                if (redirectCountExceeded(context)) {
                    httpHeader.setSkipRemainder(true);
                    context.abort(new MaxRedirectException());
                }
            } else {
                context.redirectCount = 0;
            }
        }
        final GrizzlyResponseStatus responseStatus =
                new GrizzlyResponseStatus(responsePacket,
                        context.getAhcRequest().getUri(),
                        provider.getClientConfig());
        
        context.responsePacket = responsePacket;
        context.responseStatus = responseStatus;
        if (context.statusHandler != null) {
            return;
        }
        if (context.currentState != AsyncHandler.STATE.ABORT) {
            try {
                final AsyncHandler handler = context.getAsyncHandler();
                if (handler != null) {
                    context.currentState = handler.onStatusReceived(responseStatus);
                    if (context.isWSRequest && context.currentState == AsyncHandler.STATE.ABORT) {
                        httpHeader.setSkipRemainder(true);
                        try {
                            context.done(handler.onCompleted());
                        } catch (Throwable e) {
                            context.abort(e);
                        }
                    }
                }
            } catch (Exception e) {
                httpHeader.setSkipRemainder(true);
                context.abort(e);
            }
        }
    }

    @Override
    protected void onHttpHeaderError(final HttpHeader httpHeader,
            final FilterChainContext ctx, final Throwable t)
            throws IOException {
        httpHeader.setSkipRemainder(true);
        HttpTransactionContext.currentTransaction(httpHeader).abort(t);
    }

    @Override
    protected void onHttpContentError(final HttpHeader httpHeader,
            final FilterChainContext ctx, final Throwable t)
            throws IOException {
        httpHeader.setSkipRemainder(true);
        HttpTransactionContext.currentTransaction(httpHeader).abort(t);
    }

    @SuppressWarnings(value = {"unchecked"})
    @Override
    protected boolean onHttpHeaderParsed(final HttpHeader httpHeader,
            final Buffer buffer, final FilterChainContext ctx) {
        super.onHttpHeaderParsed(httpHeader, buffer, ctx);
        LOGGER.debug("RESPONSE: {}", httpHeader);
        
        if (httpHeader.isSkipRemainder()) {
            return false;
        }
        
        final HttpTransactionContext context =
                HttpTransactionContext.currentTransaction(httpHeader);
        if (context.establishingTunnel) {
            // finish request/response processing, because Grizzly itself
            // treats CONNECT traffic as part of request-response processing
            // and we don't want it be treated like that
            httpHeader.setExpectContent(false);
            return false;
        }
        
        final AsyncHandler handler = context.getAsyncHandler();
        final List filters =
                provider.getClientConfig().getResponseFilters();
        final GrizzlyResponseHeaders responseHeaders =
                new GrizzlyResponseHeaders((HttpResponsePacket) httpHeader);
        if (!filters.isEmpty()) {
            FilterContext fc = new FilterContext.FilterContextBuilder()
                    .asyncHandler(handler)
                    .request(context.getAhcRequest())
                    .responseHeaders(responseHeaders)
                    .responseStatus(context.responseStatus)
                    .build();
            try {
                for (final ResponseFilter f : filters) {
                    fc = f.filter(fc);
                }
            } catch (Exception e) {
                context.abort(e);
            }
            if (fc.replayRequest()) {
                httpHeader.setSkipRemainder(true);
                
                final Request newRequest = fc.getRequest();
                final AsyncHandler newHandler = fc.getAsyncHandler();
                try {
                    final GrizzlyResponseFuture responseFuture = context.future;
                    final ConnectionManager m = context.provider.getConnectionManager();
                    final Connection c = m.openSync(newRequest);

                    final HttpTransactionContext newContext =
                            context.cloneAndStartTransactionFor(c, newRequest);
                    responseFuture.setAsyncHandler(newHandler);
                    responseFuture.setHttpTransactionCtx(newContext);
                    
                    try {
                        provider.execute(newContext);
                    } catch (IOException ioe) {
                        newContext.abort(ioe);
                    }
                } catch (Exception e) {
                    context.abort(e);
                }
                return false;
            }
        }
        if (context.statusHandler != null &&
                context.invocationStatus == StatusHandler.InvocationStatus.CONTINUE) {
            final boolean result =
                    context.statusHandler.handleStatus(
                            (HttpResponsePacket) httpHeader, context, ctx);
            if (!result) {
                httpHeader.setSkipRemainder(true);
                return false;
            }
        }
        if (context.isWSRequest) {
            try {
                context.protocolHandler.setConnection(ctx.getConnection());
                final GrizzlyWebSocketAdapter webSocketAdapter =
                        createWebSocketAdapter(context);
                context.webSocket = webSocketAdapter;
                final org.glassfish.grizzly.websockets.WebSocket ws =
                        webSocketAdapter.getGrizzlyWebSocket();
                
                if (context.currentState == AsyncHandler.STATE.UPGRADE) {
                    httpHeader.setChunked(false);
                    ws.onConnect();
                    WebSocketHolder.set(ctx.getConnection(), context.protocolHandler, ws);
                    ((WebSocketUpgradeHandler) context.getAsyncHandler()).onSuccess(context.webSocket);
                    final int wsTimeout = provider.getClientConfig().getWebSocketTimeout();
                    IdleTimeoutFilter.setCustomTimeout(ctx.getConnection(),
                            (wsTimeout <= 0) ? IdleTimeoutFilter.FOREVER : wsTimeout,
                            TimeUnit.MILLISECONDS);
                    context.done(handler.onCompleted());
                } else {
                    httpHeader.setSkipRemainder(true);
                    ((WebSocketUpgradeHandler) context.getAsyncHandler()).onClose(
                            context.webSocket, 1002,
                            "WebSocket protocol error: unexpected HTTP response status during handshake.");
                    context.done();
                }
            } catch (Throwable e) {
                httpHeader.setSkipRemainder(true);
                context.abort(e);
            }
        } else {
            if (context.currentState != AsyncHandler.STATE.ABORT) {
                try {
                    context.currentState = handler.onHeadersReceived(responseHeaders);
                } catch (Exception e) {
                    httpHeader.setSkipRemainder(true);
                    context.abort(e);
                }
            }
        }
        
        return false;
    }

    @SuppressWarnings(value = {"unchecked"})
    @Override
    protected boolean onHttpPacketParsed(final HttpHeader httpHeader,
            final FilterChainContext ctx) {
        final Connection connection = ctx.getConnection();
        
        final boolean result = super.onHttpPacketParsed(httpHeader, ctx);
        
        if (httpHeader.isSkipRemainder()) {
            cleanup(httpHeader.getProcessingState().getHttpContext());
            return result;
        }
        
        final HttpTransactionContext context =
                HttpTransactionContext.currentTransaction(httpHeader);
        if (context.establishingTunnel && HttpStatus.OK_200.statusMatches(
                ((HttpResponsePacket) httpHeader).getStatus())) {
            context.establishingTunnel = false;
            context.tunnelEstablished(connection);
            try {
                provider.execute(context);
                return result;
            } catch (IOException e) {
                context.abort(e);
                return result;
            }
        } else {
            cleanup(httpHeader.getProcessingState().getHttpContext());
            final AsyncHandler handler = context.getAsyncHandler();
            if (handler != null) {
                try {
                    context.done(handler.onCompleted());
                } catch (Throwable e) {
                    context.abort(e);
                }
            } else {
                context.done();
            }
            return result;
        }
    }

    // ----------------------------------------------------- Private Methods
    private static GrizzlyWebSocketAdapter createWebSocketAdapter(
            final HttpTransactionContext context) {
        
        return GrizzlyWebSocketAdapter.newInstance(
                context.provider.getClientConfig().getAsyncHttpProviderConfig(),
                context.protocolHandler);
    }

    private static boolean isRedirectAllowed(final HttpTransactionContext ctx) {
        final Request r = ctx.getAhcRequest();
        
        return r.getFollowRedirect() != null
                ? r.getFollowRedirect()
                : ctx.redirectsAllowed;
    }

    private static void cleanup(final HttpContext httpContext) {
        final HttpTransactionContext context =
                HttpTransactionContext.cleanupTransaction(httpContext);
        
        if (!context.isReuseConnection()) {
            final Connection c = (Connection) httpContext.getCloseable();
            final ConnectionManager cm = context.provider.getConnectionManager();
            if (!httpContext.getRequest().getProcessingState().isStayAlive()) {
                if (notKeepAliveReason == null) {
                    notKeepAliveReason =
                            new IOException("HTTP keep-alive was disabled for this connection");
                }
                c.closeWithReason(notKeepAliveReason);
            } else {
                cm.returnConnection(c);
            }
        }
    }

    private static boolean redirectCountExceeded(final HttpTransactionContext context) {
        return context.redirectCount > context.maxRedirectCount;
    }

    private static boolean isRedirect(final int status) {
        return HttpStatus.MOVED_PERMANENTLY_301.statusMatches(status)
                || HttpStatus.FOUND_302.statusMatches(status)
                || HttpStatus.SEE_OTHER_303.statusMatches(status)
                || HttpStatus.TEMPORARY_REDIRECT_307.statusMatches(status)
                || HttpStatus.PERMANENT_REDIRECT_308.statusMatches(status);
    }

    // ------------------------------------------------------- Inner Classes
    private static final class AuthorizationHandler implements StatusHandler {

        static final AuthorizationHandler INSTANCE = new AuthorizationHandler();
        // -------------------------------------- Methods from StatusHandler

        @Override
        public boolean handlesStatus(int statusCode) {
            return HttpStatus.UNAUTHORIZED_401.statusMatches(statusCode);
        }

        @SuppressWarnings(value = {"unchecked"})
        @Override
        public boolean handleStatus(final HttpResponsePacket responsePacket,
                final HttpTransactionContext httpTransactionContext,
                final FilterChainContext ctx) {
            final List authHeaders = listOf(responsePacket.getHeaders()
                    .values(Header.WWWAuthenticate));
            
            if (authHeaders.isEmpty()) {
                throw new IllegalStateException("401 response received, but no WWW-Authenticate header was present");
            }
            
            final GrizzlyAsyncHttpProvider provider =
                    httpTransactionContext.provider;
                        
            Realm realm = getRealm(httpTransactionContext);
            
            if (realm == null) {
                httpTransactionContext.invocationStatus = InvocationStatus.STOP;
                final AsyncHandler ah = httpTransactionContext.getAsyncHandler();
                
                if (ah != null) {
                    try {
                        ah.onStatusReceived(
                                httpTransactionContext.responseStatus);
                    } catch (Exception e) {
                        httpTransactionContext.abort(e);
                    }
                }
                return true;
            }
            
            final Request req = httpTransactionContext.getAhcRequest();

            try {
                final boolean isContinueAuth;
                
                String ntlmAuthenticate = getNTLM(authHeaders);

                final Realm newRealm;
                if (ntlmAuthenticate != null) {
                    // NTLM
                    // Connection-based auth
                    isContinueAuth = ntlmChallenge(ctx.getConnection(),
                            ntlmAuthenticate, req,
                            req.getHeaders(), realm);
                    
                    newRealm = new Realm.RealmBuilder().clone(realm)//
                            .setUri(req.getUri())//
                            .setMethodName(req.getMethod())//
                            .build();
                } else {
                    // Request-based auth
                    isContinueAuth = false;
                    
                    final String firstAuthHeader = authHeaders.get(0);

                    newRealm = new Realm.RealmBuilder()
                            .clone(realm)
                            .setUri(req.getUri())
                            .setMethodName(req.getMethod())
                            .setUsePreemptiveAuth(true)
                            .parseWWWAuthenticateHeader(firstAuthHeader)
                            .build();
                }

                responsePacket.setSkipRemainder(true); // ignore the remainder of the response
                
                final Connection c;
                
                // @TODO we may want to ditch the keep-alive connection if the response payload is too large
                if (responsePacket.getProcessingState().isKeepAlive()) {
                    // if it's HTTP keep-alive connection - reuse the
                    // same Grizzly Connection
                    c = ctx.getConnection();
                    httpTransactionContext.reuseConnection();
                } else {
                    // if it's not keep-alive - take new Connection from the pool
                    final ConnectionManager m = provider.getConnectionManager();
                    c = m.openSync(req);
                }
                
                final Request nextRequest = new RequestBuilder(req)
                        .setRealm(newRealm)
                        .build();
                
                final HttpTransactionContext newContext
                        = httpTransactionContext.cloneAndStartTransactionFor(
                                c, nextRequest);
                if (!isContinueAuth) {
                    newContext.invocationStatus = InvocationStatus.STOP;
                }
                
                try {
                    provider.execute(newContext);
                } catch (IOException ioe) {
                    newContext.abort(ioe);
                }
            } catch (Exception e) {
                httpTransactionContext.abort(e);
            }
            
            return false;
        }

        private boolean ntlmChallenge(final Connection c,
                final String wwwAuth, final Request request,
                final FluentCaseInsensitiveStringsMap headers,
                final Realm realm)
                throws NTLMEngineException {

            if (wwwAuth.equals("NTLM")) {
                // server replied bare NTLM => we didn't preemptively sent Type1Msg
                String challengeHeader = NTLMEngine.INSTANCE.generateType1Msg();

                addNTLMAuthorizationHeader(headers, challengeHeader, false);
                return true;
            } else {
                // probably receiving Type2Msg, so we issue Type3Msg
                addType3NTLMAuthorizationHeader(wwwAuth, headers, realm, false);
                // we mark NTLM as established for the Connection to
                // avoid preemptive NTLM
                Utils.setNtlmEstablished(c);
                
                return false;
            }
        }
    
        private void addNTLMAuthorizationHeader(
                FluentCaseInsensitiveStringsMap headers,
                String challengeHeader, boolean proxyInd) {
            headers.add(authorizationHeaderName(proxyInd), "NTLM " + challengeHeader);
        }

        private void addType3NTLMAuthorizationHeader(String auth,
                FluentCaseInsensitiveStringsMap headers, Realm realm,
                boolean proxyInd) throws NTLMEngineException {
            headers.remove(authorizationHeaderName(proxyInd));

            if (isNonEmpty(auth) && auth.startsWith("NTLM ")) {
                String serverChallenge = auth.substring("NTLM ".length()).trim();
                String challengeHeader = NTLMEngine.INSTANCE.generateType3Msg(
                        realm.getPrincipal(), realm.getPassword(),
                        realm.getNtlmDomain(), realm.getNtlmHost(), serverChallenge);
                addNTLMAuthorizationHeader(headers, challengeHeader, proxyInd);
            }
        }
        
        private String authorizationHeaderName(boolean proxyInd) {
            return proxyInd
                    ? Header.ProxyAuthorization.toString()
                    : Header.Authorization.toString();
        }
        
        private static List listOf(final Iterable values) {
            final List list = new ArrayList(2);
            for (String value : values) {
                list.add(value);
            }
            
            return list;
        }
    } // END AuthorizationHandler

    private static final class RedirectHandler implements StatusHandler {

        static final RedirectHandler INSTANCE = new RedirectHandler();

        // ------------------------------------------ Methods from StatusHandler
        @Override
        public boolean handlesStatus(int statusCode) {
            return isRedirect(statusCode);
        }

        @SuppressWarnings(value = {"unchecked"})
        @Override
        public boolean handleStatus(final HttpResponsePacket responsePacket,
                final HttpTransactionContext httpTransactionContext,
                final FilterChainContext ctx) {
            final String redirectURL = responsePacket.getHeader(Header.Location);
            if (redirectURL == null) {
                throw new IllegalStateException("redirect received, but no location header was present");
            }
                        
            final Request req = httpTransactionContext.getAhcRequest();
            final GrizzlyAsyncHttpProvider provider = httpTransactionContext.provider;
            
            final Uri origUri = httpTransactionContext.lastRedirectUri == null
                    ? req.getUri()
                    : httpTransactionContext.lastRedirectUri;
            
            final Uri redirectUri = Uri.create(origUri, redirectURL);
            httpTransactionContext.lastRedirectUri = redirectUri;
            
            if (redirectUri.equals(origUri)) {
                httpTransactionContext.statusHandler = null;
                httpTransactionContext.invocationStatus = InvocationStatus.CONTINUE;
                try {
                    httpTransactionContext.getAsyncHandler().onStatusReceived(
                            httpTransactionContext.responseStatus);
                } catch (Exception e) {
                    httpTransactionContext.abort(e);
                }
                
                return true;
            }

            final Request nextRequest = newRequest(httpTransactionContext,
                    redirectUri, responsePacket,
                    getRealm(httpTransactionContext),
                    sendAsGet(responsePacket, httpTransactionContext));
            
            try {
                responsePacket.setSkipRemainder(true); // ignore the remainder of the response
                
                final Connection c;

                // @TODO we may want to ditch the keep-alive connection if the response payload is too large
                if (responsePacket.getProcessingState().isKeepAlive() &&
                        isSameHostAndProtocol(origUri, redirectUri)) {
                    // if it's HTTP keep-alive connection - reuse the
                    // same Grizzly Connection
                    c = ctx.getConnection();
                    httpTransactionContext.reuseConnection();
                } else {
                    // if it's not keep-alive - take new Connection from the pool
                    final ConnectionManager m = provider.getConnectionManager();
                    c = m.openSync(nextRequest);
                }
                
                final HttpTransactionContext newContext =
                        httpTransactionContext.cloneAndStartTransactionFor(
                                c, nextRequest);
                
                newContext.invocationStatus = InvocationStatus.CONTINUE;
                try {
                    provider.execute(newContext);
                } catch (IOException ioe) {
                    newContext.abort(ioe);
                }
                
                return false;
            } catch (Exception e) {
                httpTransactionContext.abort(e);
            }
            
            httpTransactionContext.invocationStatus = InvocationStatus.CONTINUE;
            return true;
        }

        // ------------------------------------------------- Private Methods
        private boolean sendAsGet(final HttpResponsePacket response,
                final HttpTransactionContext ctx) {
            final int statusCode = response.getStatus();
            return !(statusCode < 302 || statusCode > 303) &&
                    !(statusCode == 302 && ctx.provider.getClientConfig().isStrict302Handling());
        }

        private boolean switchingSchemes(final Uri oldUri, final Uri newUri) {
            return !oldUri.getScheme().equals(newUri.getScheme());
        }

        private void notifySchemeSwitch(final FilterChainContext ctx,
                final Connection c, final Uri uri) {
            ctx.notifyDownstream(new SSLSwitchingEvent("https".equals(uri.getScheme()), c));
        }
    } // END RedirectHandler
        

    // ----------------------------------------------------- Private Methods
    private static Request newRequest(final HttpTransactionContext ctx,
            final Uri newUri, final HttpResponsePacket response,
            final Realm realm, boolean asGet) {
        final Request prototype = ctx.getAhcRequest();
        final FluentCaseInsensitiveStringsMap prototypeHeaders =
                prototype.getHeaders();
        
        prototypeHeaders.remove(Header.Host.toString());
        prototypeHeaders.remove(Header.ContentLength.toString());
        
        if (asGet)
            prototypeHeaders.remove(Header.ContentType.toString());
        if (realm != null && realm.getScheme() == AuthScheme.NTLM) {
            prototypeHeaders.remove(Header.Authorization.toString());
            prototypeHeaders.remove(Header.ProxyAuthorization.toString());
        }
        
        final RequestBuilder builder = new RequestBuilder(prototype);
        if (asGet) {
            builder.setMethod("GET");
        }
        builder.setUrl(newUri.toString());
        for (String cookieStr : response.getHeaders().values(Header.SetCookie)) {
            builder.addOrReplaceCookie(CookieDecoder.decode(cookieStr));
        }
                
        return builder.build();
    }
    
    private static Realm getRealm(final HttpTransactionContext httpTransactionContext) {
        final Realm realm = httpTransactionContext.getAhcRequest().getRealm();
        
        return realm != null
                ? realm
                : httpTransactionContext.provider.getClientConfig().getRealm();
    }
    
} // END AsyncHttpClientEventFilter




© 2015 - 2024 Weber Informatics LLC | Privacy Policy