1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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 volatile 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 @Override
92 public void fillInterested()
93 {
94
95
96
97
98 if (!closed)
99 super.fillInterested();
100 }
101
102 @Override
103 protected boolean onReadTimeout()
104 {
105 LOG.debug("{} idle timeout", this);
106
107 HttpExchange exchange = getExchange();
108 if (exchange != null)
109 idleTimeout();
110 else
111 destination.remove(this);
112
113 return true;
114 }
115
116 protected void idleTimeout()
117 {
118 receiver.idleTimeout();
119 }
120
121 @Override
122 public void send(Request request, Response.CompleteListener listener)
123 {
124 ArrayList<Response.ResponseListener> listeners = new ArrayList<>(2);
125 if (request.getTimeout() > 0)
126 {
127 TimeoutCompleteListener timeoutListener = new TimeoutCompleteListener(request);
128 timeoutListener.schedule(client.getScheduler());
129 listeners.add(timeoutListener);
130 }
131 if (listener != null)
132 listeners.add(listener);
133
134 HttpConversation conversation = client.getConversation(request.getConversationID(), true);
135 HttpExchange exchange = new HttpExchange(conversation, getDestination(), request, listeners);
136 send(exchange);
137 }
138
139 public void send(HttpExchange exchange)
140 {
141 Request request = exchange.getRequest();
142 normalizeRequest(request);
143
144
145 EndPoint endPoint = getEndPoint();
146 idleTimeout = endPoint.getIdleTimeout();
147 endPoint.setIdleTimeout(request.getIdleTimeout());
148
149
150 associate(exchange);
151
152 sender.send(exchange);
153 }
154
155 private void normalizeRequest(Request request)
156 {
157 if (request.getMethod() == null)
158 request.method(HttpMethod.GET);
159
160 if (request.getVersion() == null)
161 request.version(HttpVersion.HTTP_1_1);
162
163 if (request.getIdleTimeout() <= 0)
164 request.idleTimeout(client.getIdleTimeout(), TimeUnit.MILLISECONDS);
165
166 HttpMethod method = request.getMethod();
167 HttpVersion version = request.getVersion();
168 HttpFields headers = request.getHeaders();
169 ContentProvider content = request.getContent();
170
171 if (request.getAgent() == null)
172 headers.put(client.getUserAgentField());
173
174
175 String path = request.getPath();
176 if (path.trim().length() == 0)
177 {
178 path = "/";
179 request.path(path);
180 }
181 if (destination.isProxied() && HttpMethod.CONNECT != method)
182 {
183 path = request.getURI().toString();
184 request.path(path);
185 }
186
187
188 if (version.getVersion() > 10)
189 {
190 if (!headers.containsKey(HttpHeader.HOST.asString()))
191 headers.put(getDestination().getHostField());
192 }
193
194
195 if (content != null)
196 {
197 long contentLength = content.getLength();
198 if (contentLength >= 0)
199 {
200 if (!headers.containsKey(HttpHeader.CONTENT_LENGTH.asString()))
201 headers.put(HttpHeader.CONTENT_LENGTH, String.valueOf(contentLength));
202 }
203 else
204 {
205 if (!headers.containsKey(HttpHeader.TRANSFER_ENCODING.asString()))
206 headers.put(CHUNKED_FIELD);
207 }
208 }
209
210
211 List<HttpCookie> cookies = client.getCookieStore().get(request.getURI());
212 StringBuilder cookieString = null;
213 for (int i = 0; i < cookies.size(); ++i)
214 {
215 if (cookieString == null)
216 cookieString = new StringBuilder();
217 if (i > 0)
218 cookieString.append("; ");
219 HttpCookie cookie = cookies.get(i);
220 cookieString.append(cookie.getName()).append("=").append(cookie.getValue());
221 }
222 if (cookieString != null)
223 request.header(HttpHeader.COOKIE.asString(), cookieString.toString());
224
225
226 URI authenticationURI = destination.isProxied() ? destination.getProxyURI() : request.getURI();
227 Authentication.Result authnResult = client.getAuthenticationStore().findAuthenticationResult(authenticationURI);
228 if (authnResult != null)
229 authnResult.apply(request);
230
231 if (!headers.containsKey(HttpHeader.ACCEPT_ENCODING.asString()))
232 {
233 HttpField acceptEncodingField = client.getAcceptEncodingField();
234 if (acceptEncodingField != null)
235 headers.put(acceptEncodingField);
236 }
237 }
238
239 public HttpExchange getExchange()
240 {
241 return exchange.get();
242 }
243
244 protected void associate(HttpExchange exchange)
245 {
246 if (!this.exchange.compareAndSet(null, exchange))
247 throw new UnsupportedOperationException("Pipelined requests not supported");
248 exchange.setConnection(this);
249 LOG.debug("{} associated to {}", exchange, this);
250 }
251
252 protected HttpExchange disassociate()
253 {
254 HttpExchange exchange = this.exchange.getAndSet(null);
255 if (exchange != null)
256 exchange.setConnection(null);
257 LOG.debug("{} disassociated from {}", exchange, this);
258 return exchange;
259 }
260
261 @Override
262 public void onFillable()
263 {
264 HttpExchange exchange = getExchange();
265 if (exchange != null)
266 {
267 receive();
268 }
269 else
270 {
271
272
273 close();
274 }
275 }
276
277 protected void receive()
278 {
279 receiver.receive();
280 }
281
282 public void complete(HttpExchange exchange, boolean success)
283 {
284 HttpExchange existing = disassociate();
285 if (existing == exchange)
286 {
287 exchange.awaitTermination();
288
289
290 getEndPoint().setIdleTimeout(idleTimeout);
291
292 LOG.debug("{} disassociated from {}", exchange, this);
293 if (success)
294 {
295 HttpFields responseHeaders = exchange.getResponse().getHeaders();
296 Enumeration<String> values = responseHeaders.getValues(HttpHeader.CONNECTION.asString(), ",");
297 if (values != null)
298 {
299 while (values.hasMoreElements())
300 {
301 if ("close".equalsIgnoreCase(values.nextElement()))
302 {
303 close();
304 return;
305 }
306 }
307 }
308 destination.release(this);
309 }
310 else
311 {
312 close();
313 }
314 }
315 else if (existing == null)
316 {
317
318
319
320
321
322
323 }
324 else
325 {
326 throw new IllegalStateException();
327 }
328 }
329
330 public boolean abort(Throwable cause)
331 {
332
333
334
335 sender.abort(cause);
336 return receiver.abort(cause);
337 }
338
339 public void proceed(boolean proceed)
340 {
341 sender.proceed(proceed);
342 }
343
344 @Override
345 public void close()
346 {
347 destination.remove(this);
348 getEndPoint().shutdownOutput();
349 LOG.debug("{} oshut", this);
350 getEndPoint().close();
351 LOG.debug("{} closed", this);
352 }
353
354 @Override
355 public String toString()
356 {
357 return String.format("%s@%x(l:%s <-> r:%s)",
358 HttpConnection.class.getSimpleName(),
359 hashCode(),
360 getEndPoint().getLocalAddress(),
361 getEndPoint().getRemoteAddress());
362 }
363 }