// // ======================================================================== // Copyright (c) 1995-2017 Mort Bay Consulting Pty. Ltd. // ------------------------------------------------------------------------ // All rights reserved. This program and the accompanying materials // are made available under the terms of the Eclipse Public License v1.0 // and Apache License v2.0 which accompanies this distribution. // // The Eclipse Public License is available at // http://www.eclipse.org/legal/epl-v10.html // // The Apache License v2.0 is available at // http://www.opensource.org/licenses/apache2.0.php // // You may elect to redistribute this code under either of these licenses. // ======================================================================== // package org.eclipse.jetty.client; import java.util.ArrayList; import java.util.Deque; import java.util.List; import java.util.concurrent.ConcurrentLinkedDeque; import org.eclipse.jetty.client.api.Response; import org.eclipse.jetty.util.AttributesMap; public class HttpConversation extends AttributesMap { private final Deque<HttpExchange> exchanges = new ConcurrentLinkedDeque<>(); private volatile List<Response.ResponseListener> listeners; public Deque<HttpExchange> getExchanges() { return exchanges; } /** * Returns the list of response listeners that needs to be notified of response events. * This list changes as the conversation proceeds, as follows: * <ol> * <li> * request R1 send => conversation.updateResponseListeners(null) * <ul> * <li>exchanges in conversation: E1</li> * <li>listeners to be notified: E1.listeners</li> * </ul> * </li> * <li> * response R1 arrived, 401 => conversation.updateResponseListeners(AuthenticationProtocolHandler.listener) * <ul> * <li>exchanges in conversation: E1</li> * <li>listeners to be notified: AuthenticationProtocolHandler.listener</li> * </ul> * </li> * <li> * request R2 send => conversation.updateResponseListeners(null) * <ul> * <li>exchanges in conversation: E1 + E2</li> * <li>listeners to be notified: E2.listeners + E1.listeners</li> * </ul> * </li> * <li> * response R2 arrived, 302 => conversation.updateResponseListeners(RedirectProtocolHandler.listener) * <ul> * <li>exchanges in conversation: E1 + E2</li> * <li>listeners to be notified: E2.listeners + RedirectProtocolHandler.listener</li> * </ul> * </li> * <li> * request R3 send => conversation.updateResponseListeners(null) * <ul> * <li>exchanges in conversation: E1 + E2 + E3</li> * <li>listeners to be notified: E3.listeners + E1.listeners</li> * </ul> * </li> * <li> * response R3 arrived, 200 => conversation.updateResponseListeners(null) * <ul> * <li>exchanges in conversation: E1 + E2 + E3</li> * <li>listeners to be notified: E3.listeners + E1.listeners</li> * </ul> * </li> * </ol> * Basically the override conversation listener replaces the first exchange response listener, * and we also notify the last exchange response listeners (if it's not also the first). * * This scheme allows for protocol handlers to not worry about other protocol handlers, or to worry * too much about notifying the first exchange response listeners, but still allowing a protocol * handler to perform completion activities while another protocol handler performs new ones (as an * example, the {@link AuthenticationProtocolHandler} stores the successful authentication credentials * while the {@link RedirectProtocolHandler} performs a redirect). * * @return the list of response listeners that needs to be notified of response events */ public List<Response.ResponseListener> getResponseListeners() { return listeners; } /** * Requests to update the response listener, eventually using the given override response listener, * that must be notified instead of the first exchange response listeners. * This works in conjunction with {@link #getResponseListeners()}, returning the appropriate response * listeners that needs to be notified of response events. * * @param overrideListener the override response listener */ public void updateResponseListeners(Response.ResponseListener overrideListener) { // Create a new instance to avoid that iterating over the listeners // will notify a listener that may send a new request and trigger // another call to this method which will build different listeners // which may be iterated over when the iteration continues. List<Response.ResponseListener> listeners = new ArrayList<>(); HttpExchange firstExchange = exchanges.peekFirst(); HttpExchange lastExchange = exchanges.peekLast(); if (firstExchange == lastExchange) { if (overrideListener != null) listeners.add(overrideListener); else listeners.addAll(firstExchange.getResponseListeners()); } else { // Order is important, we want to notify the last exchange first listeners.addAll(lastExchange.getResponseListeners()); if (overrideListener != null) listeners.add(overrideListener); else listeners.addAll(firstExchange.getResponseListeners()); } this.listeners = listeners; } public boolean abort(Throwable cause) { HttpExchange exchange = exchanges.peekLast(); return exchange != null && exchange.abort(cause); } @Override public String toString() { return String.format("%s[%x]", HttpConversation.class.getSimpleName(), hashCode()); } }