blob: 0e0042cddfea408895f3fc37c71ce17a105b2131 [file] [log] [blame]
Jake Slack03928ae2014-05-13 18:41:56 -07001//
2// ========================================================================
3// Copyright (c) 1995-2014 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
19package org.eclipse.jetty.client;
20
21import java.io.IOException;
22import java.io.InputStream;
23import java.util.Collections;
24import java.util.concurrent.atomic.AtomicBoolean;
25
26import org.eclipse.jetty.client.security.Authentication;
27import org.eclipse.jetty.http.HttpFields;
28import org.eclipse.jetty.http.HttpGenerator;
29import org.eclipse.jetty.http.HttpHeaderValues;
30import org.eclipse.jetty.http.HttpHeaders;
31import org.eclipse.jetty.http.HttpMethods;
32import org.eclipse.jetty.http.HttpParser;
33import org.eclipse.jetty.http.HttpSchemes;
34import org.eclipse.jetty.http.HttpStatus;
35import org.eclipse.jetty.http.HttpVersions;
36import org.eclipse.jetty.io.AbstractConnection;
37import org.eclipse.jetty.io.Buffer;
38import org.eclipse.jetty.io.Buffers;
39import org.eclipse.jetty.io.Connection;
40import org.eclipse.jetty.io.EndPoint;
41import org.eclipse.jetty.io.EofException;
42import org.eclipse.jetty.io.View;
43import org.eclipse.jetty.util.component.AggregateLifeCycle;
44import org.eclipse.jetty.util.component.Dumpable;
45import org.eclipse.jetty.util.log.Log;
46import org.eclipse.jetty.util.log.Logger;
47import org.eclipse.jetty.util.thread.Timeout;
48
49/**
50 *
51 * @version $Revision: 879 $ $Date: 2009-09-11 16:13:28 +0200 (Fri, 11 Sep 2009) $
52 */
53public abstract class AbstractHttpConnection extends AbstractConnection implements Dumpable
54{
55 private static final Logger LOG = Log.getLogger(AbstractHttpConnection.class);
56
57 protected HttpDestination _destination;
58 protected HttpGenerator _generator;
59 protected HttpParser _parser;
60 protected boolean _http11 = true;
61 protected int _status;
62 protected Buffer _connectionHeader;
63 protected boolean _reserved;
64
65 // The current exchange waiting for a response
66 protected volatile HttpExchange _exchange;
67 protected HttpExchange _pipeline;
68 private final Timeout.Task _idleTimeout = new ConnectionIdleTask();
69 private AtomicBoolean _idle = new AtomicBoolean(false);
70
71
72 AbstractHttpConnection(Buffers requestBuffers, Buffers responseBuffers, EndPoint endp)
73 {
74 super(endp);
75
76 _generator = new HttpGenerator(requestBuffers,endp);
77 _parser = new HttpParser(responseBuffers,endp,new Handler());
78 }
79
80 public void setReserved (boolean reserved)
81 {
82 _reserved = reserved;
83 }
84
85 public boolean isReserved()
86 {
87 return _reserved;
88 }
89
90 public HttpDestination getDestination()
91 {
92 return _destination;
93 }
94
95 public void setDestination(HttpDestination destination)
96 {
97 _destination = destination;
98 }
99
100 public boolean send(HttpExchange ex) throws IOException
101 {
102 LOG.debug("Send {} on {}",ex,this);
103 synchronized (this)
104 {
105 if (_exchange != null)
106 {
107 if (_pipeline != null)
108 throw new IllegalStateException(this + " PIPELINED!!! _exchange=" + _exchange);
109 _pipeline = ex;
110 return true;
111 }
112
113 _exchange = ex;
114 _exchange.associate(this);
115
116 // The call to associate() may have closed the connection, check if it's the case
117 if (!_endp.isOpen())
118 {
119 _exchange.disassociate();
120 _exchange = null;
121 return false;
122 }
123
124 _exchange.setStatus(HttpExchange.STATUS_WAITING_FOR_COMMIT);
125
126 adjustIdleTimeout();
127
128 return true;
129 }
130 }
131
132 private void adjustIdleTimeout() throws IOException
133 {
134 // Adjusts the idle timeout in case the default or exchange timeout
135 // are greater. This is needed for long polls, where one wants an
136 // aggressive releasing of idle connections (so idle timeout is small)
137 // but still allow long polls to complete normally
138
139 long timeout = _exchange.getTimeout();
140 if (timeout <= 0)
141 timeout = _destination.getHttpClient().getTimeout();
142
143 long endPointTimeout = _endp.getMaxIdleTime();
144
145 if (timeout > 0 && timeout > endPointTimeout)
146 {
147 // Make it larger than the exchange timeout so that there are
148 // no races between the idle timeout and the exchange timeout
149 // when trying to close the endpoint
150 _endp.setMaxIdleTime(2 * (int)timeout);
151 }
152 }
153
154 public abstract Connection handle() throws IOException;
155
156
157 public boolean isIdle()
158 {
159 synchronized (this)
160 {
161 return _exchange == null;
162 }
163 }
164
165 public boolean isSuspended()
166 {
167 return false;
168 }
169
170 public void onClose()
171 {
172 }
173
174 /**
175 * @throws IOException
176 */
177 protected void commitRequest() throws IOException
178 {
179 synchronized (this)
180 {
181 _status=0;
182 if (_exchange.getStatus() != HttpExchange.STATUS_WAITING_FOR_COMMIT)
183 throw new IllegalStateException();
184
185 _exchange.setStatus(HttpExchange.STATUS_SENDING_REQUEST);
186 _generator.setVersion(_exchange.getVersion());
187
188 String method=_exchange.getMethod();
189 String uri = _exchange.getRequestURI();
190 if (_destination.isProxied())
191 {
192 if (!HttpMethods.CONNECT.equals(method) && uri.startsWith("/"))
193 {
194 boolean secure = _destination.isSecure();
195 String host = _destination.getAddress().getHost();
196 int port = _destination.getAddress().getPort();
197 StringBuilder absoluteURI = new StringBuilder();
198 absoluteURI.append(secure ? HttpSchemes.HTTPS : HttpSchemes.HTTP);
199 absoluteURI.append("://");
200 absoluteURI.append(host);
201 // Avoid adding default ports
202 if (!(secure && port == 443 || !secure && port == 80))
203 absoluteURI.append(":").append(port);
204 absoluteURI.append(uri);
205 uri = absoluteURI.toString();
206 }
207 Authentication auth = _destination.getProxyAuthentication();
208 if (auth != null)
209 auth.setCredentials(_exchange);
210 }
211
212 _generator.setRequest(method, uri);
213 _parser.setHeadResponse(HttpMethods.HEAD.equalsIgnoreCase(method));
214
215 HttpFields requestHeaders = _exchange.getRequestFields();
216 if (_exchange.getVersion() >= HttpVersions.HTTP_1_1_ORDINAL)
217 {
218 if (!requestHeaders.containsKey(HttpHeaders.HOST_BUFFER))
219 requestHeaders.add(HttpHeaders.HOST_BUFFER,_destination.getHostHeader());
220 }
221
222 Buffer requestContent = _exchange.getRequestContent();
223 if (requestContent != null)
224 {
225 requestHeaders.putLongField(HttpHeaders.CONTENT_LENGTH, requestContent.length());
226 _generator.completeHeader(requestHeaders,false);
227 _generator.addContent(new View(requestContent),true);
228 _exchange.setStatus(HttpExchange.STATUS_WAITING_FOR_RESPONSE);
229 }
230 else
231 {
232 InputStream requestContentStream = _exchange.getRequestContentSource();
233 if (requestContentStream != null)
234 {
235 _generator.completeHeader(requestHeaders, false);
236 }
237 else
238 {
239 requestHeaders.remove(HttpHeaders.CONTENT_LENGTH);
240 _generator.completeHeader(requestHeaders, true);
241 _exchange.setStatus(HttpExchange.STATUS_WAITING_FOR_RESPONSE);
242 }
243 }
244 }
245 }
246
247 protected void reset() throws IOException
248 {
249 _connectionHeader = null;
250 _parser.reset();
251 _generator.reset();
252 _http11 = true;
253 }
254
255
256 private class Handler extends HttpParser.EventHandler
257 {
258 @Override
259 public void startRequest(Buffer method, Buffer url, Buffer version) throws IOException
260 {
261 // System.out.println( method.toString() + "///" + url.toString() +
262 // "///" + version.toString() );
263 // TODO validate this is acceptable, the <!DOCTYPE goop was coming
264 // out here
265 // throw new IllegalStateException();
266 }
267
268 @Override
269 public void startResponse(Buffer version, int status, Buffer reason) throws IOException
270 {
271 HttpExchange exchange = _exchange;
272 if (exchange==null)
273 {
274 LOG.warn("No exchange for response");
275 _endp.close();
276 return;
277 }
278
279 switch(status)
280 {
281 case HttpStatus.CONTINUE_100:
282 case HttpStatus.PROCESSING_102:
283 // TODO check if appropriate expect was sent in the request.
284 exchange.setEventListener(new NonFinalResponseListener(exchange));
285 break;
286
287 case HttpStatus.OK_200:
288 // handle special case for CONNECT 200 responses
289 if (HttpMethods.CONNECT.equalsIgnoreCase(exchange.getMethod()))
290 _parser.setHeadResponse(true);
291 break;
292 }
293
294 _http11 = HttpVersions.HTTP_1_1_BUFFER.equals(version);
295 _status=status;
296 exchange.getEventListener().onResponseStatus(version,status,reason);
297 exchange.setStatus(HttpExchange.STATUS_PARSING_HEADERS);
298
299 }
300
301 @Override
302 public void parsedHeader(Buffer name, Buffer value) throws IOException
303 {
304 HttpExchange exchange = _exchange;
305 if (exchange!=null)
306 {
307 if (HttpHeaders.CACHE.getOrdinal(name) == HttpHeaders.CONNECTION_ORDINAL)
308 {
309 _connectionHeader = HttpHeaderValues.CACHE.lookup(value);
310 }
311 exchange.getEventListener().onResponseHeader(name,value);
312 }
313 }
314
315 @Override
316 public void headerComplete() throws IOException
317 {
318 HttpExchange exchange = _exchange;
319 if (exchange!=null)
320 {
321 exchange.setStatus(HttpExchange.STATUS_PARSING_CONTENT);
322 if (HttpMethods.CONNECT.equalsIgnoreCase(exchange.getMethod()))
323 _parser.setPersistent(true);
324 }
325 }
326
327 @Override
328 public void content(Buffer ref) throws IOException
329 {
330 HttpExchange exchange = _exchange;
331 if (exchange!=null)
332 exchange.getEventListener().onResponseContent(ref);
333 }
334
335 @Override
336 public void messageComplete(long contextLength) throws IOException
337 {
338 HttpExchange exchange = _exchange;
339 if (exchange!=null)
340 exchange.setStatus(HttpExchange.STATUS_COMPLETED);
341 }
342
343 @Override
344 public void earlyEOF()
345 {
346 HttpExchange exchange = _exchange;
347 if (exchange!=null)
348 {
349 if (!exchange.isDone())
350 {
351 if (exchange.setStatus(HttpExchange.STATUS_EXCEPTED))
352 exchange.getEventListener().onException(new EofException("early EOF"));
353 }
354 }
355 }
356 }
357
358 @Override
359 public String toString()
360 {
361 return String.format("%s %s g=%s p=%s",
362 super.toString(),
363 _destination == null ? "?.?.?.?:??" : _destination.getAddress(),
364 _generator,
365 _parser);
366 }
367
368 public String toDetailString()
369 {
370 return toString() + " ex=" + _exchange + " idle for " + _idleTimeout.getAge();
371 }
372
373 public void close() throws IOException
374 {
375 //if there is a live, unfinished exchange, set its status to be
376 //excepted and wake up anyone waiting on waitForDone()
377
378 HttpExchange exchange = _exchange;
379 if (exchange != null && !exchange.isDone())
380 {
381 switch (exchange.getStatus())
382 {
383 case HttpExchange.STATUS_CANCELLED:
384 case HttpExchange.STATUS_CANCELLING:
385 case HttpExchange.STATUS_COMPLETED:
386 case HttpExchange.STATUS_EXCEPTED:
387 case HttpExchange.STATUS_EXPIRED:
388 break;
389 case HttpExchange.STATUS_PARSING_CONTENT:
390 if (_endp.isInputShutdown() && _parser.isState(HttpParser.STATE_EOF_CONTENT))
391 break;
392 default:
393 String exch= exchange.toString();
394 String reason = _endp.isOpen()?(_endp.isInputShutdown()?"half closed: ":"local close: "):"closed: ";
395 if (exchange.setStatus(HttpExchange.STATUS_EXCEPTED))
396 exchange.getEventListener().onException(new EofException(reason+exch));
397 }
398 }
399
400 if (_endp.isOpen())
401 {
402 _endp.close();
403 _destination.returnConnection(this, true);
404 }
405 }
406
407 public void setIdleTimeout()
408 {
409 synchronized (this)
410 {
411 if (_idle.compareAndSet(false, true))
412 _destination.getHttpClient().scheduleIdle(_idleTimeout);
413 else
414 throw new IllegalStateException();
415 }
416 }
417
418 public boolean cancelIdleTimeout()
419 {
420 synchronized (this)
421 {
422 if (_idle.compareAndSet(true, false))
423 {
424 _destination.getHttpClient().cancel(_idleTimeout);
425 return true;
426 }
427 }
428
429 return false;
430 }
431
432 protected void exchangeExpired(HttpExchange exchange)
433 {
434 synchronized (this)
435 {
436 // We are expiring an exchange, but the exchange is pending
437 // Cannot reuse the connection because the reply may arrive, so close it
438 if (_exchange == exchange)
439 {
440 try
441 {
442 _destination.returnConnection(this, true);
443 }
444 catch (IOException x)
445 {
446 LOG.ignore(x);
447 }
448 }
449 }
450 }
451
452 /* ------------------------------------------------------------ */
453 /**
454 * @see org.eclipse.jetty.util.component.Dumpable#dump()
455 */
456 public String dump()
457 {
458 return AggregateLifeCycle.dump(this);
459 }
460
461 /* ------------------------------------------------------------ */
462 /**
463 * @see org.eclipse.jetty.util.component.Dumpable#dump(java.lang.Appendable, java.lang.String)
464 */
465 public void dump(Appendable out, String indent) throws IOException
466 {
467 synchronized (this)
468 {
469 out.append(String.valueOf(this)).append("\n");
470 AggregateLifeCycle.dump(out,indent,Collections.singletonList(_endp));
471 }
472 }
473
474 /* ------------------------------------------------------------ */
475 private class ConnectionIdleTask extends Timeout.Task
476 {
477 /* ------------------------------------------------------------ */
478 @Override
479 public void expired()
480 {
481 // Connection idle, close it
482 if (_idle.compareAndSet(true, false))
483 {
484 _destination.returnIdleConnection(AbstractHttpConnection.this);
485 }
486 }
487 }
488
489
490 /* ------------------------------------------------------------ */
491 private class NonFinalResponseListener implements HttpEventListener
492 {
493 final HttpExchange _exchange;
494 final HttpEventListener _next;
495
496 /* ------------------------------------------------------------ */
497 public NonFinalResponseListener(HttpExchange exchange)
498 {
499 _exchange=exchange;
500 _next=exchange.getEventListener();
501 }
502
503 /* ------------------------------------------------------------ */
504 public void onRequestCommitted() throws IOException
505 {
506 }
507
508 /* ------------------------------------------------------------ */
509 public void onRequestComplete() throws IOException
510 {
511 }
512
513 /* ------------------------------------------------------------ */
514 public void onResponseStatus(Buffer version, int status, Buffer reason) throws IOException
515 {
516 }
517
518 /* ------------------------------------------------------------ */
519 public void onResponseHeader(Buffer name, Buffer value) throws IOException
520 {
521 _next.onResponseHeader(name,value);
522 }
523
524 /* ------------------------------------------------------------ */
525 public void onResponseHeaderComplete() throws IOException
526 {
527 _next.onResponseHeaderComplete();
528 }
529
530 /* ------------------------------------------------------------ */
531 public void onResponseContent(Buffer content) throws IOException
532 {
533 }
534
535 /* ------------------------------------------------------------ */
536 public void onResponseComplete() throws IOException
537 {
538 _exchange.setEventListener(_next);
539 _exchange.setStatus(HttpExchange.STATUS_WAITING_FOR_RESPONSE);
540 _parser.reset();
541 }
542
543 /* ------------------------------------------------------------ */
544 public void onConnectionFailed(Throwable ex)
545 {
546 _exchange.setEventListener(_next);
547 _next.onConnectionFailed(ex);
548 }
549
550 /* ------------------------------------------------------------ */
551 public void onException(Throwable ex)
552 {
553 _exchange.setEventListener(_next);
554 _next.onException(ex);
555 }
556
557 /* ------------------------------------------------------------ */
558 public void onExpire()
559 {
560 _exchange.setEventListener(_next);
561 _next.onExpire();
562 }
563
564 /* ------------------------------------------------------------ */
565 public void onRetry()
566 {
567 _exchange.setEventListener(_next);
568 _next.onRetry();
569 }
570 }
571}