View Javadoc

1   //
2   //  ========================================================================
3   //  Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
4   //  ------------------------------------------------------------------------
5   //  All rights reserved. This program and the accompanying materials
6   //  are made available under the terms of the Eclipse Public License v1.0
7   //  and Apache License v2.0 which accompanies this distribution.
8   //
9   //      The Eclipse Public License is available at
10  //      http://www.eclipse.org/legal/epl-v10.html
11  //
12  //      The Apache License v2.0 is available at
13  //      http://www.opensource.org/licenses/apache2.0.php
14  //
15  //  You may elect to redistribute this code under either of these licenses.
16  //  ========================================================================
17  //
18  
19  package org.eclipse.jetty.client;
20  
21  import java.net.HttpCookie;
22  import java.net.URI;
23  import java.util.ArrayList;
24  import java.util.Enumeration;
25  import java.util.List;
26  import java.util.concurrent.TimeUnit;
27  import java.util.concurrent.atomic.AtomicReference;
28  
29  import org.eclipse.jetty.client.api.Authentication;
30  import org.eclipse.jetty.client.api.Connection;
31  import org.eclipse.jetty.client.api.ContentProvider;
32  import org.eclipse.jetty.client.api.Request;
33  import org.eclipse.jetty.client.api.Response;
34  import org.eclipse.jetty.http.HttpField;
35  import org.eclipse.jetty.http.HttpFields;
36  import org.eclipse.jetty.http.HttpHeader;
37  import org.eclipse.jetty.http.HttpHeaderValue;
38  import org.eclipse.jetty.http.HttpMethod;
39  import org.eclipse.jetty.http.HttpVersion;
40  import org.eclipse.jetty.io.AbstractConnection;
41  import org.eclipse.jetty.io.EndPoint;
42  import org.eclipse.jetty.util.log.Log;
43  import org.eclipse.jetty.util.log.Logger;
44  
45  public class HttpConnection extends AbstractConnection implements Connection
46  {
47      private static final Logger LOG = Log.getLogger(HttpConnection.class);
48      private static final HttpField CHUNKED_FIELD = new HttpField(HttpHeader.TRANSFER_ENCODING, HttpHeaderValue.CHUNKED);
49  
50      private final AtomicReference<HttpExchange> exchange = new AtomicReference<>();
51      private final HttpClient client;
52      private final HttpDestination destination;
53      private final HttpSender sender;
54      private final HttpReceiver receiver;
55      private long idleTimeout;
56      private boolean closed;
57  
58      public HttpConnection(HttpClient client, EndPoint endPoint, HttpDestination destination)
59      {
60          super(endPoint, client.getExecutor(), client.isDispatchIO());
61          this.client = client;
62          this.destination = destination;
63          this.sender = new HttpSender(this);
64          this.receiver = new HttpReceiver(this);
65      }
66  
67      public HttpClient getHttpClient()
68      {
69          return client;
70      }
71  
72      public HttpDestination getDestination()
73      {
74          return destination;
75      }
76  
77      @Override
78      public void onOpen()
79      {
80          super.onOpen();
81          fillInterested();
82      }
83  
84      @Override
85      public void onClose()
86      {
87          closed = true;
88          super.onClose();
89      }
90  
91      protected boolean isClosed()
92      {
93          return closed;
94      }
95  
96      @Override
97      protected boolean onReadTimeout()
98      {
99          LOG.debug("{} idle timeout", this);
100 
101         HttpExchange exchange = getExchange();
102         if (exchange != null)
103             idleTimeout();
104         else
105             destination.remove(this);
106 
107         return true;
108     }
109 
110     protected void idleTimeout()
111     {
112         receiver.idleTimeout();
113     }
114 
115     @Override
116     public void send(Request request, Response.CompleteListener listener)
117     {
118         ArrayList<Response.ResponseListener> listeners = new ArrayList<>(2);
119         if (request.getTimeout() > 0)
120         {
121             TimeoutCompleteListener timeoutListener = new TimeoutCompleteListener(request);
122             timeoutListener.schedule(client.getScheduler());
123             listeners.add(timeoutListener);
124         }
125         if (listener != null)
126             listeners.add(listener);
127 
128         HttpConversation conversation = client.getConversation(request.getConversationID(), true);
129         HttpExchange exchange = new HttpExchange(conversation, getDestination(), request, listeners);
130         send(exchange);
131     }
132 
133     public void send(HttpExchange exchange)
134     {
135         Request request = exchange.getRequest();
136         normalizeRequest(request);
137 
138         // Save the old idle timeout to restore it
139         EndPoint endPoint = getEndPoint();
140         idleTimeout = endPoint.getIdleTimeout();
141         endPoint.setIdleTimeout(request.getIdleTimeout());
142 
143         // Associate the exchange to the connection
144         associate(exchange);
145 
146         sender.send(exchange);
147     }
148 
149     private void normalizeRequest(Request request)
150     {
151         if (request.method() == null)
152             request.method(HttpMethod.GET);
153 
154         if (request.getVersion() == null)
155             request.version(HttpVersion.HTTP_1_1);
156 
157         if (request.getIdleTimeout() <= 0)
158             request.idleTimeout(client.getIdleTimeout(), TimeUnit.MILLISECONDS);
159 
160         String method = request.method();
161         HttpVersion version = request.getVersion();
162         HttpFields headers = request.getHeaders();
163         ContentProvider content = request.getContent();
164 
165         if (request.getAgent() == null)
166             headers.put(client.getUserAgentField());
167 
168         // Make sure the path is there
169         String path = request.getPath();
170         if (path.trim().length() == 0)
171         {
172             path = "/";
173             request.path(path);
174         }
175         if (destination.isProxied() && !HttpMethod.CONNECT.is(method))
176         {
177             path = request.getURI().toString();
178             request.path(path);
179         }
180 
181         // If we are HTTP 1.1, add the Host header
182         if (version.getVersion() > 10)
183         {
184             if (!headers.containsKey(HttpHeader.HOST.asString()))
185                 headers.put(getDestination().getHostField());
186         }
187 
188         // Add content headers
189         if (content != null)
190         {
191             long contentLength = content.getLength();
192             if (contentLength >= 0)
193             {
194                 if (!headers.containsKey(HttpHeader.CONTENT_LENGTH.asString()))
195                     headers.put(HttpHeader.CONTENT_LENGTH, String.valueOf(contentLength));
196             }
197             else
198             {
199                 if (!headers.containsKey(HttpHeader.TRANSFER_ENCODING.asString()))
200                     headers.put(CHUNKED_FIELD);
201             }
202         }
203 
204         // Cookies
205         List<HttpCookie> cookies = client.getCookieStore().get(request.getURI());
206         StringBuilder cookieString = null;
207         for (int i = 0; i < cookies.size(); ++i)
208         {
209             if (cookieString == null)
210                 cookieString = new StringBuilder();
211             if (i > 0)
212                 cookieString.append("; ");
213             HttpCookie cookie = cookies.get(i);
214             cookieString.append(cookie.getName()).append("=").append(cookie.getValue());
215         }
216         if (cookieString != null)
217             request.header(HttpHeader.COOKIE.asString(), cookieString.toString());
218 
219         // Authorization
220         URI authenticationURI = destination.isProxied() ? destination.getProxyURI() : request.getURI();
221         Authentication.Result authnResult = client.getAuthenticationStore().findAuthenticationResult(authenticationURI);
222         if (authnResult != null)
223             authnResult.apply(request);
224 
225         if (!headers.containsKey(HttpHeader.ACCEPT_ENCODING.asString()))
226         {
227             HttpField acceptEncodingField = client.getAcceptEncodingField();
228             if (acceptEncodingField != null)
229                 headers.put(acceptEncodingField);
230         }
231     }
232 
233     public HttpExchange getExchange()
234     {
235         return exchange.get();
236     }
237 
238     protected void associate(HttpExchange exchange)
239     {
240         if (!this.exchange.compareAndSet(null, exchange))
241             throw new UnsupportedOperationException("Pipelined requests not supported");
242         exchange.setConnection(this);
243         LOG.debug("{} associated to {}", exchange, this);
244     }
245 
246     protected HttpExchange disassociate()
247     {
248         HttpExchange exchange = this.exchange.getAndSet(null);
249         if (exchange != null)
250             exchange.setConnection(null);
251         LOG.debug("{} disassociated from {}", exchange, this);
252         return exchange;
253     }
254 
255     @Override
256     public void onFillable()
257     {
258         HttpExchange exchange = getExchange();
259         if (exchange != null)
260         {
261             receive();
262         }
263         else
264         {
265             // If there is no exchange, then could be either a remote close,
266             // or garbage bytes; in both cases we close the connection
267             close();
268         }
269     }
270 
271     protected void receive()
272     {
273         receiver.receive();
274     }
275 
276     public void complete(HttpExchange exchange, boolean success)
277     {
278         HttpExchange existing = disassociate();
279         if (existing == exchange)
280         {
281             exchange.awaitTermination();
282 
283             // Restore idle timeout
284             getEndPoint().setIdleTimeout(idleTimeout);
285 
286             LOG.debug("{} disassociated from {}", exchange, this);
287             if (success)
288             {
289                 HttpFields responseHeaders = exchange.getResponse().getHeaders();
290                 Enumeration<String> values = responseHeaders.getValues(HttpHeader.CONNECTION.asString(), ",");
291                 if (values != null)
292                 {
293                     while (values.hasMoreElements())
294                     {
295                         if ("close".equalsIgnoreCase(values.nextElement()))
296                         {
297                             close();
298                             return;
299                         }
300                     }
301                 }
302                 destination.release(this);
303             }
304             else
305             {
306                 close();
307             }
308         }
309         else if (existing == null)
310         {
311             // It is possible that the exchange has already been disassociated,
312             // for example if the connection idle timeouts: this will fail
313             // the response, but the request may still be under processing.
314             // Eventually the request will also fail as the connection is closed
315             // and will arrive here without an exchange being present.
316             // We just ignore this fact, as the exchange has already been processed
317         }
318         else
319         {
320             throw new IllegalStateException();
321         }
322     }
323 
324     public boolean abort(Throwable cause)
325     {
326         // We want the return value to be that of the response
327         // because if the response has already successfully
328         // arrived then we failed to abort the exchange
329         sender.abort(cause);
330         return receiver.abort(cause);
331     }
332 
333     public void proceed(boolean proceed)
334     {
335         sender.proceed(proceed);
336     }
337 
338     @Override
339     public void close()
340     {
341         destination.remove(this);
342         getEndPoint().shutdownOutput();
343         LOG.debug("{} oshut", this);
344         getEndPoint().close();
345         LOG.debug("{} closed", this);
346     }
347 
348     @Override
349     public String toString()
350     {
351         return String.format("%s@%x(l:%s <-> r:%s)",
352                 HttpConnection.class.getSimpleName(),
353                 hashCode(),
354                 getEndPoint().getLocalAddress(),
355                 getEndPoint().getRemoteAddress());
356     }
357 }