Skip to main content
summaryrefslogtreecommitdiffstats
blob: 32194a2bc2e6107c47e0132d8a39ae94a9fe6181 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
//
//  ========================================================================
//  Copyright (c) 1995-2016 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());
    }
}

Back to the top