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 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
139 EndPoint endPoint = getEndPoint();
140 idleTimeout = endPoint.getIdleTimeout();
141 endPoint.setIdleTimeout(request.getIdleTimeout());
142
143
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
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
182 if (version.getVersion() > 10)
183 {
184 if (!headers.containsKey(HttpHeader.HOST.asString()))
185 headers.put(getDestination().getHostField());
186 }
187
188
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
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
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
266
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
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
312
313
314
315
316
317 }
318 else
319 {
320 throw new IllegalStateException();
321 }
322 }
323
324 public boolean abort(Throwable cause)
325 {
326
327
328
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 }