View Javadoc

1   //
2   //  ========================================================================
3   //  Copyright (c) 1995-2016 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.CookieStore;
22  import java.net.HttpCookie;
23  import java.net.URI;
24  import java.util.ArrayList;
25  import java.util.List;
26  import java.util.concurrent.TimeUnit;
27  import java.util.concurrent.TimeoutException;
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.HttpScheme;
39  import org.eclipse.jetty.http.HttpVersion;
40  import org.eclipse.jetty.util.log.Log;
41  import org.eclipse.jetty.util.log.Logger;
42  
43  public abstract class HttpConnection implements Connection
44  {
45      private static final Logger LOG = Log.getLogger(HttpConnection.class);
46      private static final HttpField CHUNKED_FIELD = new HttpField(HttpHeader.TRANSFER_ENCODING, HttpHeaderValue.CHUNKED);
47  
48      private final HttpDestination destination;
49      private int idleTimeoutGuard;
50      private long idleTimeoutStamp;
51  
52      protected HttpConnection(HttpDestination destination)
53      {
54          this.destination = destination;
55          this.idleTimeoutStamp = System.nanoTime();
56      }
57  
58      public HttpClient getHttpClient()
59      {
60          return destination.getHttpClient();
61      }
62  
63      public HttpDestination getHttpDestination()
64      {
65          return destination;
66      }
67  
68      @Override
69      public void send(Request request, Response.CompleteListener listener)
70      {
71          HttpRequest httpRequest = (HttpRequest)request;
72  
73          ArrayList<Response.ResponseListener> listeners = new ArrayList<>(httpRequest.getResponseListeners());
74          if (httpRequest.getTimeout() > 0)
75          {
76              TimeoutCompleteListener timeoutListener = new TimeoutCompleteListener(httpRequest);
77              timeoutListener.schedule(getHttpClient().getScheduler());
78              listeners.add(timeoutListener);
79          }
80          if (listener != null)
81              listeners.add(listener);
82  
83          HttpExchange exchange = new HttpExchange(getHttpDestination(), httpRequest, listeners);
84  
85          SendFailure result = send(exchange);
86          if (result != null)
87              httpRequest.abort(result.failure);
88      }
89  
90      protected abstract SendFailure send(HttpExchange exchange);
91  
92      protected void normalizeRequest(Request request)
93      {
94          HttpVersion version = request.getVersion();
95          HttpFields headers = request.getHeaders();
96          ContentProvider content = request.getContent();
97          ProxyConfiguration.Proxy proxy = destination.getProxy();
98  
99          // Make sure the path is there
100         String path = request.getPath();
101         if (path.trim().length() == 0)
102         {
103             path = "/";
104             request.path(path);
105         }
106 
107         URI uri = request.getURI();
108 
109         if (proxy instanceof HttpProxy && !HttpScheme.HTTPS.is(request.getScheme()) && uri != null)
110         {
111             path = uri.toString();
112             request.path(path);
113         }
114 
115         // If we are HTTP 1.1, add the Host header
116         if (version.getVersion() <= 11)
117         {
118             if (!headers.containsKey(HttpHeader.HOST.asString()))
119                 headers.put(getHttpDestination().getHostField());
120         }
121 
122         // Add content headers
123         if (content != null)
124         {
125             if (!headers.containsKey(HttpHeader.CONTENT_TYPE.asString()))
126             {
127                 String contentType = null;
128                 if (content instanceof ContentProvider.Typed)
129                     contentType = ((ContentProvider.Typed)content).getContentType();
130                 if (contentType != null)
131                     headers.put(HttpHeader.CONTENT_TYPE, contentType);
132                 else
133                     headers.put(HttpHeader.CONTENT_TYPE, "application/octet-stream");
134             }
135             long contentLength = content.getLength();
136             if (contentLength >= 0)
137             {
138                 if (!headers.containsKey(HttpHeader.CONTENT_LENGTH.asString()))
139                     headers.put(HttpHeader.CONTENT_LENGTH, String.valueOf(contentLength));
140             }
141         }
142 
143         // Cookies
144         CookieStore cookieStore = getHttpClient().getCookieStore();
145         if (cookieStore != null)
146         {
147             StringBuilder cookies = null;
148             if (uri != null)
149                 cookies = convertCookies(cookieStore.get(uri), null);
150             cookies = convertCookies(request.getCookies(), cookies);
151             if (cookies != null)
152                 request.header(HttpHeader.COOKIE.asString(), cookies.toString());
153         }
154 
155         // Authentication
156         applyAuthentication(request, proxy != null ? proxy.getURI() : null);
157         applyAuthentication(request, uri);
158     }
159 
160     private StringBuilder convertCookies(List<HttpCookie> cookies, StringBuilder builder)
161     {
162         for (int i = 0; i < cookies.size(); ++i)
163         {
164             if (builder == null)
165                 builder = new StringBuilder();
166             if (builder.length() > 0)
167                 builder.append("; ");
168             HttpCookie cookie = cookies.get(i);
169             builder.append(cookie.getName()).append("=").append(cookie.getValue());
170         }
171         return builder;
172     }
173 
174     private void applyAuthentication(Request request, URI uri)
175     {
176         if (uri != null)
177         {
178             Authentication.Result result = getHttpClient().getAuthenticationStore().findAuthenticationResult(uri);
179             if (result != null)
180                 result.apply(request);
181         }
182     }
183 
184     protected SendFailure send(HttpChannel channel, HttpExchange exchange)
185     {
186         // Forbid idle timeouts for the time window where
187         // the request is associated to the channel and sent.
188         // Use a counter to support multiplexed requests.
189         boolean send;
190         synchronized (this)
191         {
192             send = idleTimeoutGuard >= 0;
193             if (send)
194                 ++idleTimeoutGuard;
195         }
196 
197         if (send)
198         {
199             HttpRequest request = exchange.getRequest();
200             SendFailure result;
201             if (channel.associate(exchange))
202             {
203                 channel.send();
204                 result = null;
205             }
206             else
207             {
208                 channel.release();
209                 result = new SendFailure(new HttpRequestException("Could not associate request to connection", request), false);
210             }
211 
212             synchronized (this)
213             {
214                 --idleTimeoutGuard;
215                 idleTimeoutStamp = System.nanoTime();
216             }
217 
218             return result;
219         }
220         else
221         {
222             return new SendFailure(new TimeoutException(), true);
223         }
224     }
225 
226     public boolean onIdleTimeout(long idleTimeout)
227     {
228         synchronized (this)
229         {
230             if (idleTimeoutGuard == 0)
231             {
232                 long elapsed = TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - idleTimeoutStamp);
233                 boolean idle = elapsed > idleTimeout / 2;
234                 if (idle)
235                     idleTimeoutGuard = -1;
236                 if (LOG.isDebugEnabled())
237                     LOG.debug("Idle timeout {}/{}ms - {}", elapsed, idleTimeout, this);
238                 return idle;
239             }
240             else
241             {
242                 if (LOG.isDebugEnabled())
243                     LOG.debug("Idle timeout skipped - {}", this);
244                 return false;
245             }
246         }
247     }
248 
249     @Override
250     public String toString()
251     {
252         return String.format("%s@%h", getClass().getSimpleName(), this);
253     }
254 }