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.TimeoutException;
27  import java.util.concurrent.atomic.AtomicInteger;
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.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 AtomicInteger idleTimeoutState = new AtomicInteger();
49      private final HttpDestination destination;
50  
51      protected HttpConnection(HttpDestination destination)
52      {
53          this.destination = destination;
54      }
55  
56      public HttpClient getHttpClient()
57      {
58          return destination.getHttpClient();
59      }
60  
61      public HttpDestination getHttpDestination()
62      {
63          return destination;
64      }
65  
66      @Override
67      public void send(Request request, Response.CompleteListener listener)
68      {
69          ArrayList<Response.ResponseListener> listeners = new ArrayList<>(2);
70          if (request.getTimeout() > 0)
71          {
72              TimeoutCompleteListener timeoutListener = new TimeoutCompleteListener(request);
73              timeoutListener.schedule(getHttpClient().getScheduler());
74              listeners.add(timeoutListener);
75          }
76          if (listener != null)
77              listeners.add(listener);
78  
79          HttpExchange exchange = new HttpExchange(getHttpDestination(), (HttpRequest)request, listeners);
80  
81          SendFailure result = send(exchange);
82          if (result != null)
83              request.abort(result.failure);
84      }
85  
86      protected abstract SendFailure send(HttpExchange exchange);
87  
88      protected void normalizeRequest(Request request)
89      {
90          String method = request.getMethod();
91          HttpVersion version = request.getVersion();
92          HttpFields headers = request.getHeaders();
93          ContentProvider content = request.getContent();
94          ProxyConfiguration.Proxy proxy = destination.getProxy();
95  
96          // Make sure the path is there
97          String path = request.getPath();
98          if (path.trim().length() == 0)
99          {
100             path = "/";
101             request.path(path);
102         }
103         if (proxy != null && !HttpMethod.CONNECT.is(method))
104         {
105             path = request.getURI().toString();
106             request.path(path);
107         }
108 
109         // If we are HTTP 1.1, add the Host header
110         if (version.getVersion() > 10)
111         {
112             if (!headers.containsKey(HttpHeader.HOST.asString()))
113                 headers.put(getHttpDestination().getHostField());
114         }
115 
116         // Add content headers
117         if (content != null)
118         {
119             if (content instanceof ContentProvider.Typed)
120             {
121                 if (!headers.containsKey(HttpHeader.CONTENT_TYPE.asString()))
122                 {
123                     String contentType = ((ContentProvider.Typed)content).getContentType();
124                     if (contentType != null)
125                         headers.put(HttpHeader.CONTENT_TYPE, contentType);
126                 }
127             }
128             long contentLength = content.getLength();
129             if (contentLength >= 0)
130             {
131                 if (!headers.containsKey(HttpHeader.CONTENT_LENGTH.asString()))
132                     headers.put(HttpHeader.CONTENT_LENGTH, String.valueOf(contentLength));
133             }
134             else
135             {
136                 if (!headers.containsKey(HttpHeader.TRANSFER_ENCODING.asString()))
137                     headers.put(CHUNKED_FIELD);
138             }
139         }
140 
141         // Cookies
142         CookieStore cookieStore = getHttpClient().getCookieStore();
143         if (cookieStore != null)
144         {
145             URI uri = request.getURI();
146             StringBuilder cookies = null;
147             if (uri != null)
148                 cookies = convertCookies(cookieStore.get(uri), null);
149             cookies = convertCookies(request.getCookies(), cookies);
150             if (cookies != null)
151                 request.header(HttpHeader.COOKIE.asString(), cookies.toString());
152         }
153 
154         // Authorization
155         URI authenticationURI = proxy != null ? proxy.getURI() : request.getURI();
156         if (authenticationURI != null)
157         {
158             Authentication.Result authnResult = getHttpClient().getAuthenticationStore().findAuthenticationResult(authenticationURI);
159             if (authnResult != null)
160                 authnResult.apply(request);
161         }
162     }
163 
164     private StringBuilder convertCookies(List<HttpCookie> cookies, StringBuilder builder)
165     {
166         for (int i = 0; i < cookies.size(); ++i)
167         {
168             if (builder == null)
169                 builder = new StringBuilder();
170             if (builder.length() > 0)
171                 builder.append("; ");
172             HttpCookie cookie = cookies.get(i);
173             builder.append(cookie.getName()).append("=").append(cookie.getValue());
174         }
175         return builder;
176     }
177 
178     protected SendFailure send(HttpChannel channel, HttpExchange exchange)
179     {
180         // Forbid idle timeouts for the time window where
181         // the request is associated to the channel and sent.
182         // Use a counter to support multiplexed requests.
183         boolean send = false;
184         while (true)
185         {
186             int current = idleTimeoutState.get();
187             if (current < 0)
188                 break;
189             if (idleTimeoutState.compareAndSet(current, current + 1))
190             {
191                 send = true;
192                 break;
193             }
194         }
195 
196         if (send)
197         {
198             HttpRequest request = exchange.getRequest();
199             SendFailure result;
200             if (channel.associate(exchange))
201             {
202                 channel.send();
203                 result = null;
204             }
205             else
206             {
207                 channel.release();
208                 result = new SendFailure(new HttpRequestException("Could not associate request to connection", request), false);
209             }
210             idleTimeoutState.decrementAndGet();
211             return result;
212         }
213         else
214         {
215             return new SendFailure(new TimeoutException(), true);
216         }
217     }
218 
219     public boolean onIdleTimeout()
220     {
221         if (LOG.isDebugEnabled())
222             LOG.debug("Idle timeout state {} - {}", idleTimeoutState, this);
223         return idleTimeoutState.compareAndSet(0, -1);
224     }
225 
226     @Override
227     public String toString()
228     {
229         return String.format("%s@%h", getClass().getSimpleName(), this);
230     }
231 }