Source code: org/mortbay/http/HttpConnection.java
1 // ========================================================================
2 // Copyright (c) 1999-2003 Mort Bay Consulting (Australia) Pty. Ltd.
3 // $Id: HttpConnection.java,v 1.67 2003/11/22 16:06:01 gregwilkins Exp $
4 // ========================================================================
5
6 package org.mortbay.http;
7
8 import java.io.IOException;
9 import java.io.InputStream;
10 import java.io.OutputStream;
11 import java.net.InetAddress;
12 import java.net.Socket;
13 import java.util.Enumeration;
14 import java.util.List;
15
16 import org.apache.commons.logging.Log;
17 import org.apache.commons.logging.LogFactory;
18 import org.mortbay.util.InetAddrPort;
19 import org.mortbay.util.LineInput;
20 import org.mortbay.util.LogSupport;
21 import org.mortbay.util.OutputObserver;
22 import org.mortbay.util.StringUtil;
23
24
25 /* ------------------------------------------------------------ */
26 /** A HTTP Connection.
27 * This class provides the generic HTTP handling for
28 * a connection to a HTTP server. An instance of HttpConnection
29 * is normally created by a HttpListener and then given control
30 * in order to run the protocol handling before and after passing
31 * a request to the HttpServer of the HttpListener.
32 *
33 * This class is not synchronized as it should only ever be known
34 * to a single thread.
35 *
36 * @see HttpListener
37 * @see HttpServer
38 * @version $Id: HttpConnection.java,v 1.67 2003/11/22 16:06:01 gregwilkins Exp $
39 * @author Greg Wilkins (gregw)
40 */
41 public class HttpConnection
42 implements OutputObserver
43 {
44 private static Log log = LogFactory.getLog(HttpConnection.class);
45
46 /* ------------------------------------------------------------ */
47 private static ThreadLocal __threadConnection=new ThreadLocal();
48
49
50 /** Support for FRC2068 Continues.
51 * If true, then 100 Continues will be sent when expected or for POST requests. If false, 100 Continues will
52 * only be sent if expected. Can be configured with the org.mortbay.http.HttpConnection.2068Continue system
53 * property.
54 */
55 private static boolean __2068_Continues=Boolean.getBoolean("org.mortbay.http.HttpConnection.2068Continue");
56
57 /* ------------------------------------------------------------ */
58 protected HttpRequest _request;
59 protected HttpResponse _response;
60 protected boolean _persistent;
61 protected boolean _keepAlive;
62
63 private HttpListener _listener;
64 private HttpInputStream _inputStream;
65 private HttpOutputStream _outputStream;
66 private boolean _close;
67 private int _dotVersion;
68 private boolean _firstWrite;
69 private Thread _handlingThread;
70
71 private InetAddress _remoteInetAddress;
72 private String _remoteAddr;
73 private String _remoteHost;
74 private HttpServer _httpServer;
75 private Object _connection;
76 private boolean _throttled ;
77
78 private boolean _statsOn;
79 private long _tmpTime;
80 private long _openTime;
81 private long _reqTime;
82 private int _requests;
83 private Object _object;
84 private HttpTunnel _tunnel ;
85 private boolean _resolveRemoteHost;
86
87 /* ------------------------------------------------------------ */
88 /** Constructor.
89 * @param listener The listener that created this connection.
90 * @param remoteAddr The address of the remote end or null.
91 * @param in InputStream to read request(s) from.
92 * @param out OutputputStream to write response(s) to.
93 * @param connection The underlying connection object, most likely
94 * a socket. This is not used by HttpConnection other than to make
95 * it available via getConnection().
96 */
97 public HttpConnection(HttpListener listener,
98 InetAddress remoteAddr,
99 InputStream in,
100 OutputStream out,
101 Object connection)
102 {
103 if(log.isDebugEnabled())log.debug("new HttpConnection: "+connection);
104 _listener=listener;
105 _remoteInetAddress=remoteAddr;
106 int bufferSize=listener==null?4096:listener.getBufferSize();
107 int reserveSize=listener==null?512:listener.getBufferReserve();
108 _inputStream=new HttpInputStream(in,bufferSize);
109 _outputStream=new HttpOutputStream(out,bufferSize,reserveSize);
110 _outputStream.addObserver(this);
111 _firstWrite=false;
112 if (_listener!=null)
113 _httpServer=_listener.getHttpServer();
114 _connection=connection;
115
116 _statsOn=_httpServer!=null && _httpServer.getStatsOn();
117 if (_statsOn)
118 {
119 _openTime=System.currentTimeMillis();
120 _httpServer.statsOpenConnection();
121 }
122 _reqTime=0;
123 _requests=0;
124
125 _request = new HttpRequest(this);
126 _response = new HttpResponse(this);
127
128 _resolveRemoteHost =
129 _listener!=null &&
130 _listener.getHttpServer().getResolveRemoteHost();
131 }
132
133 /* ------------------------------------------------------------ */
134 /** Get the ThreadLocal HttpConnection.
135 * The ThreadLocal HttpConnection is set by the handle() method.
136 * @return HttpConnection for this thread.
137 */
138 static HttpConnection getHttpConnection()
139 {
140 return (HttpConnection)__threadConnection.get();
141 }
142
143 /* ------------------------------------------------------------ */
144 /** Get the Remote address.
145 * @return the remote address
146 */
147 public InetAddress getRemoteInetAddress()
148 {
149 return _remoteInetAddress;
150 }
151
152 /* ------------------------------------------------------------ */
153 /** Get the Remote address.
154 * @return the remote host name
155 */
156 public String getRemoteAddr()
157 {
158 if (_remoteAddr==null)
159 {
160 if (_remoteInetAddress==null)
161 return "127.0.0.1";
162 _remoteAddr=_remoteInetAddress.getHostAddress();
163 }
164 return _remoteAddr;
165 }
166
167 /* ------------------------------------------------------------ */
168 /** Get the Remote address.
169 * @return the remote host name
170 */
171 public String getRemoteHost()
172 {
173 if (_remoteHost==null)
174 {
175 if (_resolveRemoteHost)
176 {
177 if (_remoteInetAddress==null)
178 return "localhost";
179 _remoteHost=_remoteInetAddress.getHostName();
180 }
181 else
182 {
183 if (_remoteInetAddress==null)
184 return "127.0.0.1";
185 _remoteHost=getRemoteAddr();
186 }
187 }
188 return _remoteHost;
189 }
190
191 /* ------------------------------------------------------------ */
192 /** Get the connections InputStream.
193 * @return the connections InputStream
194 */
195 public HttpInputStream getInputStream()
196 {
197 return _inputStream;
198 }
199
200 /* ------------------------------------------------------------ */
201 /** Get the connections OutputStream.
202 * @return the connections OutputStream
203 */
204 public HttpOutputStream getOutputStream()
205 {
206 return _outputStream;
207 }
208
209 /* ------------------------------------------------------------ */
210 /** Get the underlying connection object.
211 * This opaque object, most likely a socket. This is not used by
212 * HttpConnection other than to make it available via getConnection().
213 * @return Connection abject
214 */
215 public Object getConnection()
216 {
217 return _connection;
218 }
219
220 /* ------------------------------------------------------------ */
221 /** Get the request.
222 * @return the request
223 */
224 public HttpRequest getRequest()
225 {
226 return _request;
227 }
228
229 /* ------------------------------------------------------------ */
230 /** Get the response.
231 * @return the response
232 */
233 public HttpResponse getResponse()
234 {
235 return _response;
236 }
237
238 /* ------------------------------------------------------------ */
239 /** Force the connection to not be persistent.
240 */
241 public void forceClose()
242 {
243 _persistent=false;
244 _close=true;
245 }
246
247 /* ------------------------------------------------------------ */
248 /** Close the connection.
249 * This method calls close on the input and output streams and
250 * interrupts any thread in the handle method.
251 * may be specialized to close sockets etc.
252 * @exception IOException
253 */
254 public void close()
255 throws IOException
256 {
257 try{
258 _outputStream.close();
259 _inputStream.close();
260 }
261 finally
262 {
263 if (_handlingThread!=null && Thread.currentThread()!=_handlingThread)
264 _handlingThread.interrupt();
265 }
266 }
267
268 /* ------------------------------------------------------------ */
269 /** Get the connections listener.
270 * @return HttpListener that created this Connection.
271 */
272 public HttpListener getListener()
273 {
274 return _listener;
275 }
276
277 /* ------------------------------------------------------------ */
278 /** Get the listeners HttpServer .
279 * Conveniance method equivalent to getListener().getHttpServer().
280 * @return HttpServer.
281 */
282 public HttpServer getHttpServer()
283 {
284 return _httpServer;
285 }
286
287 /* ------------------------------------------------------------ */
288 /** Get the listeners Default scheme.
289 * Conveniance method equivalent to getListener().getDefaultProtocol().
290 * @return HttpServer.
291 */
292 public String getDefaultScheme()
293 {
294 return _listener.getDefaultScheme();
295 }
296
297 /* ------------------------------------------------------------ */
298 /** Get the listeners HttpServer.
299 * But if the name is 0.0.0.0, then the real interface address is used.
300 * @return HttpServer.
301 */
302 public String getServerName()
303 {
304 String host=_listener.getHost();
305 if (InetAddrPort.__0_0_0_0.equals(host) &&
306 _connection instanceof Socket)
307 host = ((Socket)_connection).getLocalAddress().getHostName();
308
309 return host;
310 }
311
312 /* ------------------------------------------------------------ */
313 /** Get the listeners HttpServer.
314 * @return HttpServer.
315 */
316 public String getServerAddr()
317 {
318 if (_connection instanceof Socket)
319 return ((Socket)_connection).getLocalAddress().getHostAddress();
320 return _listener.getHost();
321 }
322
323 /* ------------------------------------------------------------ */
324 /** Get the listeners Port .
325 * Conveniance method equivalent to getListener().getPort().
326 * @return local port.
327 */
328 public int getServerPort()
329 {
330 return _listener.getPort();
331 }
332
333 /* ------------------------------------------------------------ */
334 /** Get the remote Port .
335 * @return remote port.
336 */
337 public int getRemotePort()
338 {
339 if (_connection instanceof Socket)
340 return ((Socket)_connection).getPort();
341 return 0;
342 }
343
344 /* ------------------------------------------------------------ */
345 /**
346 * @return True if this connections state has been altered due
347 * to low resources.
348 */
349 public boolean isThrottled()
350 {
351 return _throttled;
352 }
353
354 /* ------------------------------------------------------------ */
355 /**
356 * @param throttled True if this connections state has been altered due
357 * to low resources.
358 */
359 public void setThrottled(boolean throttled)
360 {
361 _throttled = throttled;
362 }
363
364 /* ------------------------------------------------------------ */
365 /** Get associated object.
366 * Used by a particular HttpListener implementation to associate
367 * private datastructures with the connection.
368 * @return An object associated with the connecton by setObject.
369 */
370 public Object getObject()
371 {
372 return _object;
373 }
374
375 /* ------------------------------------------------------------ */
376 /** Set associated object.
377 * Used by a particular HttpListener implementation to associate
378 * private datastructures with the connection.
379 * @param o An object associated with the connecton.
380 */
381 public void setObject(Object o)
382 {
383 _object=o;
384 }
385
386 /* ------------------------------------------------------------ */
387 /**
388 * @return The HttpTunnel set for the connection or null.
389 */
390 public HttpTunnel getHttpTunnel()
391 {
392 return _tunnel;
393 }
394
395 /* ------------------------------------------------------------ */
396 /** Set a HttpTunnel for the connection.
397 * A HTTP tunnel is used if the connection is to be taken over for
398 * non-HTTP communications. An example of this is the CONNECT method
399 * in proxy handling. If a HttpTunnel is set on a connection, then it's
400 * handle method is called bu the next call to handleNext().
401 * @param tunnel The HttpTunnel set for the connection or null.
402 */
403 public void setHttpTunnel(HttpTunnel tunnel)
404 {
405 _tunnel = tunnel;
406 }
407
408 /* ------------------------------------------------------------ */
409 /* Verify HTTP/1.0 request
410 * @exception HttpException problem with the request.
411 * @exception IOException problem with the connection.
412 */
413 private void verifyHTTP_1_0()
414 {
415 // Set content length
416 int content_length=
417 _request.getIntField(HttpFields.__ContentLength);
418 if (content_length>=0)
419 _inputStream.setContentLength(content_length);
420 else if (content_length<0)
421 {
422 // TODO - can't do this check because IE does this after
423 // a redirect.
424 // Can't have content without a content length
425 // String content_type=_request.getField(HttpFields.__ContentType);
426 // if (content_type!=null && content_type.length()>0)
427 // throw new HttpException(_HttpResponse.__411_Length_Required);
428 _inputStream.setContentLength(0);
429 }
430
431 // Check netscape proxy connection - this is not strictly correct.
432 if (!_keepAlive && HttpFields.__KeepAlive.equalsIgnoreCase(_request.getField(HttpFields.__ProxyConnection)))
433 _keepAlive=true;
434
435 // persistent connections in HTTP/1.0 only if requested.
436 _persistent=_keepAlive;
437 }
438
439 /* ------------------------------------------------------------ */
440 /* Verify HTTP/1.1 request
441 * @exception HttpException problem with the request.
442 * @exception IOException problem with the connection.
443 */
444 private void verifyHTTP_1_1()
445 throws HttpException, IOException
446 {
447 // Check Host Field exists
448 String host=_request.getField(HttpFields.__Host);
449 if (host==null)
450 throw new HttpException(HttpResponse.__400_Bad_Request);
451
452 // check and enable requests transfer encodings.
453 String transfer_coding=
454 _request.getField(HttpFields.__TransferEncoding);
455
456 if (transfer_coding!=null && transfer_coding.length()>0)
457 {
458 // Handling of codings other than chunking is now
459 // the responsibility of handlers, filters or servlets.
460 // Thanks to the compression filter, we now don't know if
461 // what we can handle here.
462
463 if (transfer_coding.equalsIgnoreCase(HttpFields.__Chunked) ||
464 StringUtil.endsWithIgnoreCase(transfer_coding,HttpFields.__Chunked))
465 _inputStream.setChunking();
466 else if (StringUtil.asciiToLowerCase(transfer_coding)
467 .indexOf(HttpFields.__Chunked)>=0)
468 throw new HttpException(HttpResponse.__400_Bad_Request);
469 }
470
471 // Check input content length can be determined
472 int content_length=_request.getIntField(HttpFields.__ContentLength);
473 String content_type=_request.getField(HttpFields.__ContentType);
474 if (!_inputStream.isChunking())
475 {
476 // If we have a content length, use it
477 if (content_length>=0)
478 _inputStream.setContentLength(content_length);
479 // else if we have no content
480 else if (content_type==null || content_type.length()==0)
481 _inputStream.setContentLength(0);
482 // else we need a content length
483 else
484 {
485 // TODO - can't do this check as IE stuff up on
486 // a redirect.
487 // throw new HttpException(HttpResponse.__411_Length_Required);
488 _inputStream.setContentLength(0);
489 }
490 }
491
492 // Handle Continue Expectations
493 String expect=_request.getField(HttpFields.__Expect);
494 if (expect!=null && expect.length()>0)
495 {
496 if (StringUtil.asciiToLowerCase(expect)
497 .equals(HttpFields.__ExpectContinue))
498 {
499 // Send continue if no body available yet.
500 if (_inputStream.available()<=0)
501 {
502 OutputStream real_out=_outputStream.getOutputStream();
503 real_out.write(HttpResponse.__Continue);
504 real_out.flush();
505 }
506 }
507 else
508 throw new HttpException(HttpResponse.__417_Expectation_Failed);
509 }
510 else if (__2068_Continues &&
511 _inputStream.available()<=0 &&
512 (HttpRequest.__PUT.equals(_request.getMethod()) ||
513 HttpRequest.__POST.equals(_request.getMethod())))
514 {
515 // Send continue for RFC 2068 exception
516 OutputStream real_out=_outputStream.getOutputStream();
517 real_out.write(HttpResponse.__Continue);
518 real_out.flush();
519 }
520
521 // Persistent unless requested otherwise
522 _persistent=!_close;
523 }
524
525
526 /* ------------------------------------------------------------ */
527 /** Output Notifications.
528 * Trigger header and/or filters from output stream observations.
529 * Also finalizes method of indicating response content length.
530 * Called as a result of the connection subscribing for notifications
531 * to the HttpOutputStream.
532 * @see HttpOutputStream
533 * @param out The output stream observed.
534 * @param action The action.
535 */
536 public void outputNotify(OutputStream out, int action, Object ignoredData)
537 throws IOException
538 {
539 if (_response==null)
540 return;
541
542 switch(action)
543 {
544 case OutputObserver.__FIRST_WRITE:
545 if (!_firstWrite)
546 {
547 firstWrite();
548 _firstWrite=true;
549 }
550 break;
551
552 case OutputObserver.__RESET_BUFFER:
553 _firstWrite=false;
554 break;
555
556 case OutputObserver.__COMMITING:
557 commit();
558 break;
559
560 case OutputObserver.__CLOSING:
561 if (_response!=null &&
562 !_response.isCommitted() &&
563 _request.getState()==HttpMessage.__MSG_RECEIVED)
564 commit();
565 break;
566
567 case OutputObserver.__CLOSED:
568 break;
569 }
570 }
571
572 /* ------------------------------------------------------------ */
573 /** Setup the reponse output stream.
574 * Use the current state of the request and response, to set tranfer
575 * parameters such as chunking and content length.
576 */
577 protected void firstWrite()
578 throws IOException
579 {
580 if (_response.isCommitted())
581 return;
582
583 // Determine how to limit content length and
584 // enable output transfer encodings
585
586 String transfer_coding=_response.getField(HttpFields.__TransferEncoding);
587 if (transfer_coding==null ||
588 transfer_coding.length()==0 ||
589 HttpFields.__Identity.equalsIgnoreCase(transfer_coding))
590 {
591 switch(_dotVersion)
592 {
593 case 1:
594 {
595 // if (not closed and no length)
596 if ((!HttpFields.__Close.equals(_response.getField(HttpFields.__Connection)))&&
597 (_response.getField(HttpFields.__ContentLength)==null))
598 {
599 // Chunk it!
600 _response.setField(HttpFields.__TransferEncoding,HttpFields.__Chunked);
601 _outputStream.setChunking();
602 }
603 break;
604 }
605 case 0:
606 {
607 // If we dont have a content length (except 304 replies),
608 // or we have been requested to close
609 // then we can't be persistent
610 if (!_keepAlive || !_persistent ||
611 HttpResponse.__304_Not_Modified!=_response.getStatus() &&
612 _response.getField(HttpFields.__ContentLength)==null ||
613 HttpFields.__Close.equals(_response.getField(HttpFields.__Connection)))
614 {
615 _persistent=false;
616 if (_keepAlive)
617 _response.setField(HttpFields.__Connection,
618 HttpFields.__Close);
619 _keepAlive=false;
620 }
621 else if (_keepAlive)
622 _response.setField(HttpFields.__Connection,
623 HttpFields.__KeepAlive);
624 break;
625 }
626 default:
627 _keepAlive=false;
628 _persistent=false;
629 }
630 }
631 else if (_dotVersion<1)
632 {
633 // Error for transfer encoding to be set in HTTP/1.0
634 _response.removeField(HttpFields.__TransferEncoding);
635 throw new HttpException(HttpResponse.__501_Not_Implemented,
636 "Transfer-Encoding not supported in HTTP/1.0");
637 }
638 else
639 {
640 // Use transfer encodings to determine length
641 _response.removeField(HttpFields.__ContentLength);
642 _outputStream.setChunking();
643
644 if (!HttpFields.__Chunked.equalsIgnoreCase(transfer_coding))
645 {
646 // Check against any TE field
647 List te = _request.getAcceptableTransferCodings();
648 Enumeration enum =
649 _response.getFieldValues(HttpFields.__TransferEncoding,
650 HttpFields.__separators);
651 while (enum.hasMoreElements())
652 {
653 String coding=(String)enum.nextElement();
654 if (HttpFields.__Identity.equalsIgnoreCase(coding) ||
655 HttpFields.__Chunked.equalsIgnoreCase(coding))
656 continue;
657 if (te==null || !te.contains(coding))
658 throw new HttpException(HttpResponse.__501_Not_Implemented,coding);
659 }
660 }
661 }
662
663 // Nobble the OutputStream for HEAD requests
664 if (HttpRequest.__HEAD.equals(_request.getMethod()))
665 _outputStream.nullOutput();
666 }
667
668
669 /* ------------------------------------------------------------ */
670 protected void commit()
671 throws IOException
672 {
673 if (_response.isCommitted())
674 return;
675
676 // Mark request as handled.
677 _request.setHandled(true);
678
679 // Handler forced close, listener stopped or no idle threads left.
680 _close=HttpFields.__Close.equals(_response.getField(HttpFields.__Connection));
681 if (!_close && (!_listener.isStarted()||_listener.isOutOfResources()))
682 {
683 _close=true;
684 _response.setField(HttpFields.__Connection,
685 HttpFields.__Close);
686 }
687 if (_close)
688 _persistent=false;
689
690
691 // if we have no content or encoding, and no content length
692 int status = _response.getStatus();
693 if (!_outputStream.isWritten() &&
694 !_response.containsField(HttpFields.__ContentLength) &&
695 !_response.containsField(HttpFields.__TransferEncoding))
696 {
697 // Special case for responses with no content.
698 if (status==HttpResponse.__304_Not_Modified ||
699 status==HttpResponse.__204_No_Content)
700 {
701 if (_persistent && _keepAlive && _dotVersion==0)
702 _response.setField(HttpFields.__Connection,
703 HttpFields.__KeepAlive);
704 }
705 else
706 {
707 if(_persistent)
708 {
709 switch (_dotVersion)
710 {
711 case 0:
712 {
713 _close=true;
714 _persistent=false;
715 _response.setField(HttpFields.__Connection,
716 HttpFields.__Close);
717 }
718 break;
719 case 1:
720 {
721 // force chunking on.
722 _response.setField(HttpFields.__TransferEncoding,
723 HttpFields.__Chunked);
724 _outputStream.setChunking();
725 }
726 break;
727
728 default:
729 _close=true;
730 _response.setField(HttpFields.__Connection,
731 HttpFields.__Close);
732 break;
733 }
734 }
735 else
736 {
737 _close=true;
738 _response.setField(HttpFields.__Connection,
739 HttpFields.__Close);
740 }
741 }
742 }
743
744 _outputStream.writeHeader(_response);
745 }
746
747
748 /* ------------------------------------------------------------ */
749 /* Exception reporting policy method.
750 * @param e the Throwable to report.
751 */
752 private void exception(Throwable e)
753 {
754 try{
755 _persistent=false;
756 int error_code=HttpResponse.__500_Internal_Server_Error;
757
758 if (e instanceof HttpException)
759 {
760 error_code=((HttpException)e).getCode();
761
762 if (_request==null)
763 log.warn(e.toString());
764 else
765 log.warn(_request.getRequestLine()+" "+e.toString());
766 log.debug(LogSupport.EXCEPTION,e);
767 }
768 else if (e instanceof EOFException)
769 {
770 LogSupport.ignore(log,e);
771 return;
772 }
773 else
774 {
775 _request.setAttribute("javax.servlet.error.exception_type",e.getClass());
776 _request.setAttribute("javax.servlet.error.exception",e);
777
778 if (_request==null)
779 log.warn(LogSupport.EXCEPTION,e);
780 else
781 log.warn(_request.getRequestLine(),e);
782 }
783
784 if (_response != null && !_response.isCommitted())
785 {
786 _response.reset();
787 _response.removeField(HttpFields.__TransferEncoding);
788 _response.setField(HttpFields.__Connection,HttpFields.__Close);
789 _response.sendError(error_code);
790 }
791 }
792 catch(Exception ex)
793 {
794 LogSupport.ignore(log,ex);
795 }
796 }
797
798
799 /* ------------------------------------------------------------ */
800 /** Service a Request.
801 * This implementation passes the request and response to the
802 * service method of the HttpServer for this connections listener.
803 * If no HttpServer has been associated, the 503 is returned.
804 * This method may be specialized to implement other ways of
805 * servicing a request.
806 * @param request The request
807 * @param response The response
808 * @return The HttpContext that completed handling of the request or null.
809 * @exception HttpException
810 * @exception IOException
811 */
812 protected HttpContext service(HttpRequest request, HttpResponse response)
813 throws HttpException, IOException
814 {
815 if (_httpServer==null)
816 throw new HttpException(HttpResponse.__503_Service_Unavailable);
817 return _httpServer.service(request,response);
818 }
819
820 /* ------------------------------------------------------------ */
821 /** Handle the connection.
822 * Once the connection has been created, this method is called
823 * to handle one or more requests that may be received on the
824 * connection. The method only returns once all requests have been
825 * handled, an error has been returned to the requestor or the
826 * connection has been closed.
827 * The handleNext() is called in a loop until it returns false.
828 */
829 public final void handle()
830 {
831 try
832 {
833 associateThread();
834 while(_listener.isStarted() && handleNext())
835 recycle();
836 }
837 finally
838 {
839 disassociateThread();
840 destroy();
841 }
842 }
843
844 /* ------------------------------------------------------------ */
845 protected void associateThread()
846 {
847 __threadConnection.set(this);
848 _handlingThread=Thread.currentThread();
849 }
850
851 /* ------------------------------------------------------------ */
852 protected void disassociateThread()
853 {
854 _handlingThread=null;
855 __threadConnection.set(null);
856 }
857
858
859 /* ------------------------------------------------------------ */
860 protected void readRequest()
861 throws IOException
862 {
863 log.debug("readRequest() ...");
864 _request.readHeader((LineInput)(_inputStream)
865 .getInputStream());
866 }
867
868 /* ------------------------------------------------------------ */
869 /** Handle next request off the connection.
870 * The service(request,response) method is called by handle to
871 * service each request received on the connection.
872 * If the thread is a PoolThread, the thread is set as inactive
873 * when waiting for a request.
874 * <P>
875 * If a HttpTunnel has been set on this connection, it's handle method is
876 * called and when that completes, false is return from this method.
877 * <P>
878 * The Connection is set as a ThreadLocal of the calling thread and is
879 * available via the getHttpConnection() method.
880 * @return true if the connection is still open and may provide
881 * more requests.
882 */
883 public boolean handleNext()
884 {
885 // Handle a HTTP tunnel
886 if (_tunnel!=null)
887 {
888 if(log.isDebugEnabled())log.debug("Tunnel: "+_tunnel);
889 _outputStream.resetObservers();
890 _tunnel.handle(_inputStream.getInputStream(),
891 _outputStream.getOutputStream());
892 return false;
893 }
894
895 // Normal handling.
896 HttpContext context=null;
897 try
898 {
899 // Assume the connection is not persistent,
900 // unless told otherwise.
901 _persistent=false;
902 _close=false;
903 _keepAlive=false;
904 _firstWrite=false;
905 _dotVersion=0;
906
907 // Read requests
908 readRequest();
909 _listener.customizeRequest(this,_request);
910 if (_request.getState()!=HttpMessage.__MSG_RECEIVED)
911 throw new HttpException(HttpResponse.__400_Bad_Request);
912
913 // We have a valid request!
914 statsRequestStart();
915 if (log.isDebugEnabled())
916 {
917 _response.setField("Jetty-Request",
918 _request.getRequestLine());
919 if(log.isDebugEnabled())log.debug("REQUEST:\n"+_request);
920 }
921
922 // Pick response version, we assume that _request.getVersion() == 1
923 _dotVersion=_request.getDotVersion();
924
925 if (_dotVersion>1)
926 {
927 _dotVersion=1;
928 }
929
930 // Common fields on the response
931 _response.setVersion(HttpMessage.__HTTP_1_1);
932 _response.setField(HttpFields.__Date,_request.getTimeStampStr());
933 _response.setField(HttpFields.__Server,Version.__VersionDetail);
934 // _response.setField(HttpFields.__ServletEngine,Version.__ServletEngine);
935
936 // Handle Connection header field
937 Enumeration connectionValues =
938 _request.getFieldValues(HttpFields.__Connection,
939 HttpFields.__separators);
940 if (connectionValues!=null)
941 {
942 while (connectionValues.hasMoreElements())
943 {
944 String token=connectionValues.nextElement().toString();
945 // handle close token
946 if (token.equalsIgnoreCase(HttpFields.__Close))
947 {
948 _close=true;
949 _response.setField(HttpFields.__Connection,
950 HttpFields.__Close);
951 }
952 else if (token.equalsIgnoreCase(HttpFields.__KeepAlive) &&
953 _dotVersion==0)
954 _keepAlive=true;
955
956 // Remove headers for HTTP/1.0 requests
957 if (_dotVersion==0)
958 _request.forceRemoveField(token);
959 }
960 }
961
962 // Handle version specifics
963 if (_dotVersion==1)
964 verifyHTTP_1_1();
965 else if (_dotVersion==0)
966 verifyHTTP_1_0();
967 else if (_dotVersion!=-1)
968 throw new HttpException(HttpResponse.__505_HTTP_Version_Not_Supported);
969
970 if(LogSupport.isTraceEnabled(log))log.trace("IN is "+(_inputStream.isChunking()?"chunked":"not chunked")+" Content-Length="+_inputStream.getContentLength());
971
972
973 // handle HttpListener handlers
974 if (!_request.isHandled() && _listener.getHttpHandler()!=null)
975 _listener.getHttpHandler().handle("",null, _request, _response);
976
977 // service the request
978 if (!_request.isHandled())
979 context=service(_request,_response);
980 }
981 catch(HttpException e) {exception(e);}
982 catch (IOException e)
983 {
984 if (_request.getState()!=HttpMessage.__MSG_RECEIVED)
985 {
986 if (log.isDebugEnabled())
987 {
988 if (LogSupport.isTraceEnabled(log)) log.trace(LogSupport.EXCEPTION,e);
989 else if(log.isDebugEnabled())log.debug(e.toString());
990 }
991 _response.destroy();
992 _response=null;
993 }
994 else
995 exception(e);
996 }
997 catch (Exception e) {exception(e);}
998 catch (Error e) {exception(e);}
999 finally
1000 {
1001 int bytes_written=0;
1002 int content_length = _response==null
1003 ?-1:_response.getIntField(HttpFields.__ContentLength);
1004
1005 // Complete the request
1006 if (_persistent)
1007 {
1008 try{
1009 // Read remaining input
1010 while(_inputStream.skip(4096)>0 || _inputStream.read()>=0);
1011 }
1012 catch(IOException e)
1013 {
1014 if (_inputStream.getContentLength()>0)
1015 _inputStream.setContentLength(0);
1016 _persistent=false;
1017 LogSupport.ignore(log,e);
1018 exception(new HttpException(HttpResponse.__400_Bad_Request,
1019 "Missing Content"));
1020 }
1021
1022 // Check for no more content
1023 if (_inputStream.getContentLength()>0)
1024 {
1025 _inputStream.setContentLength(0);
1026 _persistent=false;
1027 exception (new HttpException(HttpResponse.__400_Bad_Request,
1028 "Missing Content"));
1029 }
1030
1031 // Commit the response
1032 try{
1033 _outputStream.close();
1034 bytes_written=_outputStream.getBytesWritten();
1035 _outputStream.resetStream();
1036 _outputStream.addObserver(this);
1037 _inputStream.resetStream();
1038 }
1039 catch(IOException e) {exception(e);}
1040 }
1041 else if (_response!=null) // There was a request
1042 {
1043 // half hearted attempt to eat any remaining input
1044 try{
1045 if (_inputStream.getContentLength()>0)
1046 while(_inputStream.skip(4096)>0 || _inputStream.read()>=0);
1047 _inputStream.resetStream();
1048 }
1049 catch(IOException e){LogSupport.ignore(log,e);}
1050
1051 // commit non persistent
1052 try{
1053 _outputStream.flush();
1054 _response.commit();
1055 bytes_written=_outputStream.getBytesWritten();
1056 _outputStream.close();
1057 _outputStream.resetStream();
1058 }
1059 catch(IOException e) {exception(e);}
1060 }
1061
1062 // Check response length
1063 if (_response!=null)
1064 {
1065 if(log.isDebugEnabled())log.debug("RESPONSE:\n"+_response);
1066 if (_persistent &&
1067 content_length>=0 && bytes_written>0 && content_length!=bytes_written)
1068 {
1069 log.warn("Invalid length: Content-Length="+content_length+
1070 " written="+bytes_written+
1071 " for "+_request.getRequestURL());
1072 _persistent=false;
1073 try{_outputStream.close();}
1074 catch(IOException e) {log.warn(LogSupport.EXCEPTION,e);}
1075 }
1076 }
1077
1078 // stats & logging
1079 statsRequestEnd();
1080 if (context!=null)
1081 context.log(_request,_response,bytes_written);
1082 }
1083
1084 return (_tunnel!=null) || _persistent;
1085 }
1086
1087 /* ------------------------------------------------------------ */
1088 protected void statsRequestStart()
1089 {
1090 if (_statsOn)
1091 {
1092 if (_reqTime>0)
1093 statsRequestEnd();
1094 _requests++;
1095 _tmpTime=_request.getTimeStamp();
1096 _reqTime=_tmpTime;
1097 _httpServer.statsGotRequest();
1098 }
1099 }
1100
1101 /* ------------------------------------------------------------ */
1102 protected void statsRequestEnd()
1103 {
1104 if (_statsOn && _reqTime>0)
1105 {
1106 _httpServer.statsEndRequest(System.currentTimeMillis()-_reqTime,
1107 (_response!=null));
1108 _reqTime=0;
1109 }
1110 }
1111
1112 /* ------------------------------------------------------------ */
1113 /** Recycle the connection.
1114 * called by handle when handleNext returns true.
1115 */
1116 protected void recycle()
1117 {
1118 _listener.persistConnection(this);
1119 if (_request!=null)
1120 _request.recycle(this);
1121 if (_response!=null)
1122 _response.recycle(this);
1123 }
1124
1125 /* ------------------------------------------------------------ */
1126 /** Destroy the connection.
1127 * called by handle when handleNext returns false.
1128 */
1129 protected void destroy()
1130 {
1131 try{close();}
1132 catch (IOException e){LogSupport.ignore(log,e);}
1133 catch (Exception e){log.warn(LogSupport.EXCEPTION,e);}
1134
1135 // Destroy request and response
1136 if (_request!=null)
1137 _request.destroy();
1138 if (_response!=null)
1139 _response.destroy();
1140 if (_inputStream!=null)
1141 _inputStream.destroy();
1142 if (_outputStream!=null)
1143 _outputStream.destroy();
1144 _inputStream=null;
1145 _outputStream=null;
1146 _request=null;
1147 _response=null;
1148 _handlingThread=null;
1149
1150 if (_statsOn)
1151 {
1152 _tmpTime=System.currentTimeMillis();
1153 if (_reqTime>0)
1154 _httpServer.statsEndRequest(_tmpTime-_reqTime,false);
1155 _httpServer.statsCloseConnection(_tmpTime-_openTime,_requests);
1156 }
1157 }
1158}