com.tinspx.util.net.Requests.java Source code

Java tutorial

Introduction

Here is the source code for com.tinspx.util.net.Requests.java

Source

/* Copyright (C) 2013-2014 Ian Teune <ian.teune@gmail.com>
 * 
 * Permission is hereby granted, free of charge, to any person obtaining
 * a copy of this software and associated documentation files (the
 * "Software"), to deal in the Software without restriction, including
 * without limitation the rights to use, copy, modify, merge, publish,
 * distribute, sublicense, and/or sell copies of the Software, and to
 * permit persons to whom the Software is furnished to do so, subject to
 * the following conditions:
 * 
 * The above copyright notice and this permission notice shall be
 * included in all copies or substantial portions of the Software.
 * 
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
 * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
 * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
 * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 */
package com.tinspx.util.net;

import com.google.common.base.Function;
import com.google.common.base.Functions;
import com.google.common.base.Objects;
import static com.google.common.base.Preconditions.*;
import com.google.common.base.Predicate;
import com.google.common.base.Predicates;
import com.google.common.base.Supplier;
import com.google.common.base.Throwables;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ListMultimap;
import com.google.common.net.HttpHeaders;
import com.google.common.net.InternetDomainName;
import com.google.common.primitives.Ints;
import com.google.common.util.concurrent.AsyncFunction;
import com.google.common.util.concurrent.FutureCallback;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;
import com.google.common.util.concurrent.ListenableScheduledFuture;
import com.google.common.util.concurrent.SettableFuture;
import com.tinspx.util.base.BasicError;
import com.tinspx.util.base.ContentCallback;
import com.tinspx.util.base.ContentListener;
import com.tinspx.util.base.Errors;
import com.tinspx.util.collect.CollectUtils;
import com.tinspx.util.collect.Predicated;
import com.tinspx.util.concurrent.FutureUtils;
import com.tinspx.util.concurrent.LimitedExecutorService;
import com.tinspx.util.io.ChannelSource;
import com.tinspx.util.io.StringUtils;
import com.tinspx.util.io.callbacks.FileCallback;
import java.io.IOException;
import java.net.URI;
import java.nio.ByteBuffer;
import java.util.List;
import java.util.Map;
import java.util.concurrent.Callable;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import javax.annotation.Nullable;
import lombok.NonNull;
import lombok.experimental.NonFinal;
import lombok.extern.slf4j.Slf4j;

/**
 * Various {@code Request} and {@code RequestContext} utilities.
 * 
 * @author Ian
 */
@Slf4j
public class Requests {

    public static RequestCallback emptyCallback() {
        return EmptyRequestCallback.INSTANCE;
    }

    static final RedirectHandler NO_FOLLOW = new RedirectHandler() {
        @Override
        public boolean shouldRedirect(Response response) {
            return false;
        }

        @Override
        public Request apply(Request input) {
            return null;
        }

        @Override
        public String toString() {
            return "Requests.noFollowRedirectHandler()";
        }
    };

    /**
     * Does not follow any redirects.
     * <p>
     * {@link RedirectHandler#shouldRedirect(Response)} always returns false and
     * {@link RedirectHandler#apply(Request)} always returns null to cancel the
     * redirect.
     */
    public static RedirectHandler noFollowRedirectHandler() {
        return NO_FOLLOW;
    }

    static final RedirectHandler DEFAULT = LimitedRedirect
            .wrap(ConfigRedirect.builder().redirect(301, 302, 303).with("POST").to("GET").removeBody()
                    .convertPostToGetParams().redirect(301, 302, 303, 307, 308).build());

    /**
     * Redirects on response codes 301, 302, 303, 307, and 308. The response
     * will be converted to GET on 301, 302, and 303 if the Request HTTP method
     * is POST (any parameters in a {@link FormEncodedBody} will be converted to
     * GET query parameters). Relative redirect locations are allowed. If the
     * Location header is missing, the Request is redirected to the same URI.
     * The default redirection limits of {@link LimitedRedirect} are enforced.
     */
    public static RedirectHandler defaultRedirectHandler() {
        return DEFAULT;
    }

    /**
     * same as {@link #wrap(ContentListener, boolean) wrap(delegate, true)}.
     */
    public static RequestCallback wrap(ContentListener<? super Response, ? super ByteBuffer> delegate) {
        return wrap(delegate, true);
    }

    /**
     * Wraps {@code delegate} as a {@code RequestCallback}.
     * {@code ignoreRedirects} controls whether the listener receives content
     * for any intermediary redirects. {@code ignoreRedirects} has no effect if
     * {@code delegate} is already a {@code RequestCallback}.
     */
    public static RequestCallback wrap(ContentListener<? super Response, ? super ByteBuffer> delegate,
            boolean ignoreRedirects) {
        if (delegate instanceof RequestCallback) {
            return (RequestCallback) delegate;
        }
        return new ContentListenerWrapper(delegate, ignoreRedirects);
    }

    //    static final Function<ContentListener<? super Response, ? super ByteBuffer>, RequestCallback> CONTENTLISTENER_FUNCTION_F = new Function<ContentListener<? super Response, ? super ByteBuffer>, RequestCallback>() {
    //        @Override
    //        public RequestCallback apply(ContentListener<? super Response, ? super ByteBuffer> input) {
    //            return input != null ? wrap(input, false) : null;
    //        }
    //    };
    //    
    //    static final Function<ContentListener<? super Response, ? super ByteBuffer>, RequestCallback> CONTENTLISTENER_FUNCTION_T = new Function<ContentListener<? super Response, ? super ByteBuffer>, RequestCallback>() {
    //        @Override
    //        public RequestCallback apply(ContentListener<? super Response, ? super ByteBuffer> input) {
    //            return input != null ? wrap(input, true) : null;
    //        }
    //    };

    //this fucks up the javadoc
    //    static Function<ContentListener<? super Response, ? super ByteBuffer>, RequestCallback> wrapContentListener() {
    //        return CONTENTLISTENER_FUNCTION_T;
    //    }
    //    
    //    static Function<ContentListener<? super Response, ? super ByteBuffer>, RequestCallback> wrapContentListener(boolean ignoreRedirects) {
    //        return ignoreRedirects ? CONTENTLISTENER_FUNCTION_T : CONTENTLISTENER_FUNCTION_F;
    //    }

    static class ContentListenerWrapper extends EmptyRequestCallback {

        final ContentListener<? super Response, ? super ByteBuffer> delegate;
        final boolean ignoreRedirects;

        public ContentListenerWrapper(ContentListener<? super Response, ? super ByteBuffer> delegate,
                boolean ignoreRedirects) {
            this.delegate = checkNotNull(delegate);
            this.ignoreRedirects = ignoreRedirects;
        }

        @Override
        public boolean onContentStart(Response context) {
            if (delegate instanceof ContentCallback) {
                return ((ContentCallback<? super Response, ? super ByteBuffer>) delegate).onContentStart(context);
            }
            return true;
        }

        @Override
        public boolean onContent(Response context, ByteBuffer content) {
            return delegate.onContent(context, content);
        }

        @Override
        public void onContentComplete(Response context) {
            if (delegate instanceof ContentCallback) {
                ((ContentCallback<? super Response, ? super ByteBuffer>) delegate).onContentComplete(context);
            }
        }

        @Override
        public void onError(BasicError error) {
            if (delegate instanceof BasicError.Listener) {
                ((BasicError.Listener) delegate).onError(error);
            }
        }

        @Override
        public boolean ignoreRedirects() {
            return ignoreRedirects;
        }
    }

    /**
     * The delegate will only receive error events, regardless of whether it
     * actually implements other content interfaces.
     */
    public static RequestCallback wrap(BasicError.Listener delegate) {
        return new ErrorListenerWrapper(delegate);
    }

    //    static final Function<BasicError.Listener, RequestCallback> ERRORLISTENER_FUNCTION = new Function<BasicError.Listener, RequestCallback>() {
    //        @Override
    //        public RequestCallback apply(BasicError.Listener input) {
    //            return input != null ? wrap(input) : null;
    //        }
    //    };
    //    
    //    static Function<BasicError.Listener, RequestCallback> wrapErrorListener() {
    //        return ERRORLISTENER_FUNCTION;
    //    }

    static class ErrorListenerWrapper extends EmptyRequestCallback {

        final BasicError.Listener delegate;

        public ErrorListenerWrapper(BasicError.Listener delegate) {
            this.delegate = checkNotNull(delegate);
        }

        @Override
        public void onError(BasicError error) {
            delegate.onError(error);
        }
    }

    public static RequestContext wrap(AsyncFunction<? super Request, Response> context,
            Function<? super Request, ? extends Request> function) {
        return RequestFunction.wrap(context, function);
    }

    public static RequestFunction combine(Function<? super Request, ? extends Request>... functions) {
        return RequestFunction.combine(functions);
    }

    public static RequestFunction combine(Iterable<Function<? super Request, ? extends Request>> functions) {
        return RequestFunction.combine(functions);
    }

    static final Function<Request, URI> REQUEST_TO_URI = new Function<Request, URI>() {
        @Override
        public URI apply(Request input) {
            return input != null ? input.uri() : null;
        }
    };

    static final Function<Response, URI> RESPONSE_TO_URI = new Function<Response, URI>() {
        @Override
        public URI apply(Response input) {
            return input != null ? input.uri() : null;
        }
    };

    public static Function<Request, URI> requestURI() {
        return REQUEST_TO_URI;
    }

    public static Function<Response, URI> responseURI() {
        return RESPONSE_TO_URI;
    }

    static final Function<Response, Integer> RESPONSE_CODE = new Function<Response, Integer>() {
        @Override
        public Integer apply(Response input) {
            return input != null ? input.code() : -1;
        }
    };

    /**
     * Returns a {@code Function} that returns the response code, or -1 if
     * null or unknown.
     */
    public static Function<Response, Integer> responseCode() {
        return RESPONSE_CODE;
    }

    /**
     * The {@code RequestBody}s are considered equal if they have the same
     * {@link RequestBody#headers() headers} (ignoring order) and their content
     * is {@link ChannelSource#contentEquals(ByteSource) equal}.
     */
    public static boolean bodyEquals(@Nullable RequestBody a, @Nullable RequestBody b) throws IOException {
        if (Objects.equal(a, b)) {
            return true;
        }
        if (a == null || b == null) {
            return false;
        }
        if (!CollectUtils.equalIgnoreOrder(a.headers(), b.headers())) {
            return false;
        }
        return a.contentEquals(b);
    }

    static final Function<Object, Object> UNWRAP = new Function<Object, Object>() {
        @Override
        public Object apply(Object input) {
            if (input instanceof ContentListenerWrapper) {
                return ((ContentListenerWrapper) input).delegate;
            } else if (input instanceof ErrorListenerWrapper) {
                return ((ErrorListenerWrapper) input).delegate;
            } else {
                return input;
            }
        }
    };

    /**
     * Schedules {@code request} to be submitted to {@code context}.
     */
    public static ListenableScheduledFuture<Response> schedule(final @NonNull Request request,
            final @NonNull AsyncFunction<? super Request, Response> context,
            @NonNull ScheduledExecutorService executor, long delay, @NonNull TimeUnit unit) {
        request.checkNotStarted();
        request.setSubmissionMillisIfAbsent();
        SettableFuture<Response> future = SettableFuture.create();
        ScheduledFuture<?> sf = executor.schedule(apply(context, request, future), delay, unit);
        return FutureUtils.delegateListenableScheduledFuture(future, sf);
    }

    private static Runnable apply(final @NonNull AsyncFunction<? super Request, Response> context,
            final @NonNull Request request, final @NonNull SettableFuture<Response> future) {
        return new Runnable() {
            @Override
            public void run() {
                try {
                    ListenableFuture<Response> result = context.apply(request);
                    FutureUtils.linkFutures(result, future);
                    FutureUtils.linkFailure(future, result, false);
                } catch (Throwable t) {
                    future.setException(t);
                    Throwables.propagateIfInstanceOf(t, Error.class);
                }
            }
        };
    }

    /**
     * Returns a {@code Callable} that returns a
     * {@link Request#duplicate() duplicate} of {@code request}. A defensive
     * copy of {@code request} is made; therefore, any subsequent changes made
     * to {@code request} will have no effect on the supplier.
     */
    public static Callable<Request> callable(@NonNull Request request) throws IOException {
        final Request r = request.duplicate();
        return new Callable<Request>() {
            @Override
            public Request call() throws Exception {
                return r.duplicate();
            }
        };
    }

    /**
     * Returns a {@code Callable} that submits a
     * {@link Request#duplicate() duplicate} of {@code request} to
     * {@code context} every time {@code call()} is invoked. A defensive copy of
     * {@code request} is made; therefore, any subsequent changes made to
     * {@code request} will have no effect on the {@code Callable}.
     * 
     * @see #apply(AsyncFunction, Supplier)
     */
    public static Callable<ListenableFuture<Response>> apply(
            @NonNull AsyncFunction<? super Request, Response> context, @NonNull Request request)
            throws IOException {
        return apply(context, callable(request));
    }

    /**
     * Returns a {@code Callable} that submits a {@code Request} acquired from
     * {@code requestCallable} every time {@code call()} is invoked.
     */
    public static Callable<ListenableFuture<Response>> apply(
            final @NonNull AsyncFunction<? super Request, Response> context,
            final @NonNull Callable<Request> requestCallable) {
        return new Callable<ListenableFuture<Response>>() {
            @Override
            public ListenableFuture<Response> call() throws Exception {
                return context.apply(requestCallable.call());
            }
        };
    }

    private static void checkExpiration(long expiration, @NonNull TimeUnit unit) {
        checkArgument(expiration >= 0);
    }

    /**
     * Adds an expiration to {@code request} such that if the request is
     * executed more than {@code expiration} time {@code unit}s after it was
     * submitted to a {@code RequestContext}, it will be canceled.
     */
    public static Request withExpiration(@NonNull Request request, final long expiration, final TimeUnit unit) {
        checkExpiration(expiration, unit);
        final long millis = Math.max(1, unit.toMillis(expiration));
        request.callback(new EmptyRequestCallback() {
            boolean setup;

            @Override
            public void onSetup(Request request) {
                //should be ignoring redirects, but setup guards against onSetup
                //being called multiple times just in case
                if (setup) {
                    return;
                }
                setup = true;
                long submission = asLong(request.get(Request.SUBMISSION_MILLIS)), elapsed;
                if (submission > 0 && (elapsed = System.currentTimeMillis() - submission) > millis) {
                    request.cancel();
                    request.onError(Errors.message("request expired; expiration: %d %s, elapsed: %d %s", expiration,
                            unit, elapsed, TimeUnit.MILLISECONDS));
                }
            }
        });
        return request;
    }

    /**
     * Returns a Function that adds the specified expiration to its input using
     * {@link #withExpiration(Request, long, TimeUnit)}.
     */
    public static Function<Request, Request> withExpiration(final long expiration, final TimeUnit unit) {
        checkExpiration(expiration, unit);
        return new Function<Request, Request>() {
            @Override
            public Request apply(Request input) {
                return input != null ? withExpiration(input, expiration, unit) : null;
            }
        };
    }

    /**
     * Wraps {@code context} and adds an expiration using
     * {@link #withExpiration(Request, long, TimeUnit)} to all submitted
     * requests.
     */
    public static RequestContext withExpiration(@NonNull AsyncFunction<? super Request, Response> context,
            long expiration, TimeUnit unit) {
        return wrap(context, withExpiration(expiration, unit));
    }

    static long asLong(@Nullable Object obj) {
        if (obj instanceof Number) {
            long value = ((Number) obj).longValue();
            return value < 0 ? -1 : value;
        } else {
            return -1;
        }
    }

    static final Function<Request, Object> REQUEST_FULL_DOMAIN = Functions.compose(UriUtil.toFullDomain(),
            Requests.requestURI());

    static final Function<Request, Object> REQUEST_TOP_PRIVATE = Functions.compose(UriUtil.toTopPrivateDomain(),
            Requests.requestURI());

    /**
     * Converts the {@code Request} {@code URI} {@link URI#getHost() host} into
     * either an {@link InternetDomainName},
     * {@link java.net.InetAddress InetAddress}, or {@code String} using
     * {@link UriUtil#toFullDomain()}.
     *
     * @see #toTopPrivateDomain()
     * @return the {@code Request} {@code URI} as an {@code InternetDomainName},
     * {@code InetAddress}, or {@code String}.
     */
    public static Function<Request, Object> toFullDomain() {
        return REQUEST_FULL_DOMAIN;
    }

    /**
     * Converts the {@code Request} {@code URI} {@link URI#getHost() host} into
     * either an {@link InternetDomainName},
     * {@link java.net.InetAddress InetAddress}, or {@code String} using
     * {@link UriUtil#toTopPrivateDomain()}.
     *
     * @see #toFullDomain()
     * @return the {@code Request} {@code URI} as an
     * {@code InternetDomainName}, {@code InetAddress}, or {@code String}.
     */
    public static Function<Request, Object> toTopPrivateDomain() {
        return REQUEST_TOP_PRIVATE;
    }

    private static final Function<Object, Object> NULL_CONSTANT = Functions.constant(null);

    /**
     * Wraps {@code delegate}, using {@code executor} to rate limit
     * {@code Request} execution. Note that <i>all</i> requests, regardless of
     * domain or {@code URI}, will be subject to the same rate limiting. Use
     * {@link #limit(AsyncFunction, LimitedExecutorService, Function)} in
     * conjunction with {@link #toTopPrivateDomain()} and
     * {@link #toFullDomain()} to apply rate limiting based on the domain or
     * provide a more specialized function.
     * 
     * @param delegate the {@code RequestContext} to rate limit
     * @param executor the {@code LimitedExecutorService} that is used to rate
     * limit requests
     * @return a {@code RequestContext} using {@code executor} to rate limit
     * {@code delegate}
     */
    public static RequestContext limit(AsyncFunction<? super Request, Response> delegate,
            LimitedExecutorService executor) {
        return limit(delegate, executor, NULL_CONSTANT);
    }

    /**
     * Wraps {@code delegate}, using {@code executor} to rate limit
     * {@code Request} execution. {@code keyFunction} is used to convert
     * each submitted {@code Request} into a {@code key} that is used to
     * {@link LimitedExecutorService#execute(Object, Runnable) execute} the
     * request. Requests with the same {@code key} will be subject to the same
     * rate limiting. Using {@link #toTopPrivateDomain()} will cause all requests
     * to the same top private domain to be rate limited. {@code keyFunction}
     * may return {@code null} to use the {@code LimitedExecutorService} default
     * key.
     *
     * @see #toTopPrivateDomain()
     * @see #toFullDomain()
     * @param delegate the {@code RequestContext} to rate limit
     * @param executor the {@code LimitedExecutorService} that is used to rate
     * limit requests
     * @param keyFunction converts each {@code Request} into the key passed to
     * {@code executor} to enforce the rate limiting
     * @return a {@code RequestContext} using {@code executor} to rate limit
     * {@code delegate}
     */
    public static RequestContext limit(final @NonFinal AsyncFunction<? super Request, Response> delegate,
            final @NonFinal LimitedExecutorService executor,
            final @NonFinal Function<? super Request, ? extends Object> keyFunction) {
        return new RequestContext() {
            @Override
            public ListenableFuture<Response> apply(Request request) throws Exception {
                request.checkNotStarted();
                if (request.isCancelled()) {
                    request.onCancel(this, true);
                    return Futures.immediateCancelledFuture();
                }
                request.setSubmissionMillisIfAbsent();
                SettableFuture<Response> future = SettableFuture.create();
                Runnable task = Requests.apply(delegate, request, future);
                Object key = keyFunction.apply(request);
                if (key != null) {
                    executor.execute(key, task, future);
                } else {
                    executor.execute(task, future);
                }
                return future;
            }
        };
    }

    /**
     * see {@link #chain(Iterable)}.
     */
    public static RedirectHandler chain(RedirectHandler... handlers) {
        return chain(ImmutableList.copyOf(handlers));
    }

    /**
     * Combines the supplied redirect handlers. The first handler to indicate
     * a redirect is necessary will be used to apply the redirect. Only one
     * of the handlers will be used for any given redirect.
     */
    public static RedirectHandler chain(Iterable<? extends RedirectHandler> handlers) {
        final ImmutableList<RedirectHandler> redirectHandlers = ImmutableList
                .copyOf(CollectUtils.checkAllNotNull(handlers));
        checkArgument(!redirectHandlers.isEmpty(), "no handler provided");
        return new RedirectHandler() {
            @Override
            public boolean shouldRedirect(Response response) {
                if (response == null) {
                    return false;
                }
                for (RedirectHandler handler : redirectHandlers) {
                    if (handler.shouldRedirect(response)) {
                        return true;
                    }
                }
                return false;
            }

            @Override
            public Request apply(Request request) {
                if (request == null) {
                    return null;
                }
                final Response cause = request.cause().get();
                for (RedirectHandler handler : redirectHandlers) {
                    if (handler.shouldRedirect(cause)) {
                        return handler.apply(request);
                    }
                }
                return null;
            }
        };
    }

    /**
     * redirect handler that always redirects, used for testing.
     */
    static RedirectHandler FOLLOW = new RedirectHandler() {
        @Override
        public boolean shouldRedirect(Response response) {
            return true;
        }

        @Override
        public Request apply(Request request) {
            if (request.cause().get().headers().contains("location")) {
                request.uri(URI.create(request.cause().get().headers().last("location")));
            }
            return request;
        }
    };

    /**
     * Wraps {@code delegate} in a {@code RequestContext} that will convert
     * successful responses that satisfy {@code failPredicate} to an error. Note
     * that converting the response to a failure only affects the status of the
     * response {@code ListenableFuture}, and has <i>no</i> effect on the
     * {@link RequestCallback} callback methods.
     */
    public static RequestContext toFailure(final @NonNull AsyncFunction<? super Request, Response> delegate,
            final @NonNull Predicate<? super Response> failPredicate) {
        return new RequestContext() {
            @Override
            public ListenableFuture<Response> apply(Request request) throws Exception {
                ListenableFuture<Response> delegateFuture = delegate.apply(request);
                final SettableFuture<Response> future = SettableFuture.create();
                Futures.addCallback(delegateFuture, new FutureCallback<Response>() {
                    @Override
                    public void onSuccess(Response result) {
                        try {
                            if (failPredicate.apply(result)) {
                                result.onError(Errors.message("predicate failure: %s", failPredicate));
                                future.setException(new RequestException(result));
                            } else {
                                future.set(result);
                            }
                        } catch (Throwable t) {
                            //have to ensure the returned future always completes
                            //even if the predicate or onError throws
                            future.setException(new RequestException(result, t));
                            //guava Futures should log this
                            throw Throwables.propagate(t);
                        }
                    }

                    @Override
                    public void onFailure(Throwable t) {
                        future.setException(t);
                    }
                });
                FutureUtils.linkFailure(future, delegateFuture, false);
                return future;
            }
        };
    }

    /**
     * Returns a predicates that returns true if the response is not null and
     * the response code is in {@code codes}.
     */
    public static Predicate<Response> isCode(int... codes) {
        checkArgument(codes.length > 0, "at least one code required");
        final int[] array = codes.clone();
        return new Predicate<Response>() {
            @Override
            public boolean apply(Response input) {
                if (input == null) {
                    return false;
                }
                return Ints.contains(array, input.code());
            }
        };
    }

    /**
     * same as
     * {@link #processIf(ContentListener, Predicate) processIf(listener, isCode(codes))}.
     */
    public static RequestCallback processIf(@NonNull ContentListener<? super Response, ? super ByteBuffer> listener,
            int... codes) {
        return processIf(listener, isCode(codes));
    }

    /**
     * Wraps {@code listener} in a {@code RequestCallback} that forwards
     * {@link ContentCallback} methods to {@code listener} only if the response
     * satisfies {@code predicate}. If {@code listener} is already a
     * {@code RequestCallback}, it is treated as only a {@code ContentCallback}
     * and will not receive other {@code RequestCallback} methods.
     * <p>
     * This is useful listeners (such as {@link FileCallback}) that should only
     * process the response content when the response was successful. For
     * example, to only save the first MB of content from a range request, use:
     * {@code Requests.processIf(new RandomAccessFileCallback("filename", "rw", Range.openClosed(0, 1024*1024)), 206)}.
     */
    public static RequestCallback processIf(
            final @NonNull ContentListener<? super Response, ? super ByteBuffer> listener,
            final @NonNull Predicate<? super Response> predicate) {
        return new EmptyRequestCallback() {
            boolean forward;

            @Override
            public boolean onContentStart(Response context) {
                forward = predicate.apply(context);
                if (forward && listener instanceof ContentCallback) {
                    forward = ((ContentCallback<? super Response, ?>) listener).onContentStart(context);
                }
                return forward;
            }

            @Override
            public boolean onContent(Response context, ByteBuffer content) {
                if (forward) {
                    forward = listener.onContent(context, content);
                }
                return forward;
            }

            @Override
            public void onContentComplete(Response context) {
                if (forward && listener instanceof ContentCallback) {
                    ((ContentCallback<? super Response, ?>) listener).onContentComplete(context);
                }
            }

            @Override
            public void onError(BasicError error) {
                if (listener instanceof BasicError.Listener) {
                    ((BasicError.Listener) listener).onError(error);
                }
            }
        };
    }

    /**
     * see {@link #removeHeaders(RedirectHandler, Iterable)}.
     */
    public static Function<Request, Request> removeHeaders(String... headers) {
        return removeHeaders(ImmutableList.copyOf(headers));
    }

    /**
     * Returns a Function that removes all headers mapped to the specified
     * header names.
     */
    public static Function<Request, Request> removeHeaders(@NonNull Iterable<String> headers) {
        final List<String> names = ImmutableList.copyOf(CollectUtils.checkAllNotNull(headers));
        checkArgument(!names.isEmpty());
        return new Function<Request, Request>() {
            @Override
            public Request apply(Request input) {
                if (input != null) {
                    for (String name : names) {
                        input.headers().removeAll(name);
                    }
                }
                return input;
            }
        };
    }

    public static RedirectHandler before(RedirectHandler delegate,
            final @NonNull Function<? super Request, ? extends Request> function) {
        return new ForwardingRedirectHandler(delegate) {
            @Override
            public Request apply(Request request) {
                return super.apply(function.apply(request));
            }
        };
    }

    public static RedirectHandler after(RedirectHandler delegate,
            final @NonNull Function<? super Request, ? extends Request> function) {
        return new ForwardingRedirectHandler(delegate) {
            @Override
            public Request apply(Request request) {
                return function.apply(super.apply(request));
            }
        };
    }

    public static Predicate<Response> asPredicate(@NonNull final RedirectHandler handler) {
        return new Predicate<Response>() {
            @Override
            public boolean apply(Response input) {
                return handler.shouldRedirect(input);
            }
        };
    }

    public static RedirectHandler asRedirectHandler(final @NonNull Predicate<? super Response> predicate,
            final @NonNull Function<? super Request, ? extends Request> function) {
        return new RedirectHandler() {
            @Override
            public boolean shouldRedirect(Response response) {
                return predicate.apply(response);
            }

            @Override
            public Request apply(Request request) {
                if (request == null) {
                    return null;
                }
                if (!predicate.apply(request.cause().get())) {
                    return null;
                }
                return function.apply(request);
            }
        };
    }

    static final Function<String, String> notNullTrimUpper = StringUtils.stringFunction().notNull().trim()
            .uppercaseAscii().get();

    static ListMultimap<String, String> notNull(ListMultimap<String, String> delegate) {
        return Predicated.listMultimap(delegate, Predicates.notNull(), Predicates.notNull());
    }

    /**
     * not null and not content-type or content-length
     */
    static final Predicate<String> HKEY_NO_TYPE_LEN = new Predicate<String>() {
        @Override
        public boolean apply(String input) {
            if (input == null) {
                return false;
            }
            input = Headers.normalize(input);
            return !HttpHeaders.CONTENT_TYPE.equals(input) && !HttpHeaders.CONTENT_LENGTH.equals(input);
        }
    };

    /**
     * not null and not content-length
     */
    static final Predicate<String> HKEY_NO_LEN = new Predicate<String>() {
        @Override
        public boolean apply(String input) {
            if (input == null) {
                return false;
            }
            input = Headers.normalize(input);
            return !HttpHeaders.CONTENT_LENGTH.equals(input);
        }
    };

    //this works, but much less clear than below
    //    static final Predicate<Map.Entry<String, String>> NOT_CONTENT_LENGTH =
    //        Predicates.compose(
    //            Predicates.compose(
    //                Predicates.and(
    //                    Predicates.notNull(),
    //                    Predicates.not(Predicates.equalTo(HttpHeaders.CONTENT_LENGTH))),
    //                Headers.normalize()),
    //            MapUtils.<String, String>key());

    static final Predicate<Map.Entry<String, String>> NOT_CONTENT_LENGTH = new Predicate<Map.Entry<String, String>>() {
        @Override
        public boolean apply(final Map.Entry<String, String> input) {
            if (input == null) {
                return false;
            }
            final String name = input.getKey();
            if (name == null) {
                return false;
            }
            return !HttpHeaders.CONTENT_LENGTH.equals(Headers.normalize(name));
        }
    };
}