View Javadoc

1   //========================================================================
2   //$Id: HttpConnection.java,v 1.13 2005/11/25 21:01:45 gregwilkins Exp $
3   //Copyright 2004-2005 Mort Bay Consulting Pty. Ltd.
4   //------------------------------------------------------------------------
5   //Licensed under the Apache License, Version 2.0 (the "License");
6   //you may not use this file except in compliance with the License.
7   //You may obtain a copy of the License at
8   //http://www.apache.org/licenses/LICENSE-2.0
9   //Unless required by applicable law or agreed to in writing, software
10  //distributed under the License is distributed on an "AS IS" BASIS,
11  //WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12  //See the License for the specific language governing permissions and
13  //limitations under the License.
14  //========================================================================
15  
16  package org.mortbay.jetty;
17  
18  import java.io.IOException;
19  import java.io.InputStream;
20  import java.io.PrintWriter;
21  
22  import javax.servlet.ServletInputStream;
23  import javax.servlet.ServletOutputStream;
24  import javax.servlet.http.HttpServletResponse;
25  
26  import org.mortbay.io.Buffer;
27  import org.mortbay.io.Connection;
28  import org.mortbay.io.EndPoint;
29  import org.mortbay.io.BufferCache.CachedBuffer;
30  import org.mortbay.io.nio.SelectChannelEndPoint;
31  import org.mortbay.log.Log;
32  import org.mortbay.resource.Resource;
33  import org.mortbay.util.URIUtil;
34  import org.mortbay.util.ajax.Continuation;
35  
36  /**
37   * <p>A HttpConnection represents the connection of a HTTP client to the server
38   * and is created by an instance of a {@link Connector}. It's prime function is 
39   * to associate {@link Request} and {@link Response} instances with a {@link EndPoint}.
40   * </p>
41   * <p>
42   * A connection is also the prime mechanism used by jetty to recycle objects without
43   * pooling.  The {@link Request},  {@link Response}, {@link HttpParser}, {@link HttpGenerator}
44   * and {@link HttpFields} instances are all recycled for the duraction of
45   * a connection. Where appropriate, allocated buffers are also kept associated
46   * with the connection via the parser and/or generator.
47   * </p>
48   * 
49   * 
50   * @author gregw
51   * 
52   */
53  public class HttpConnection implements Connection
54  {
55      private static int UNKNOWN = -2;
56      private static ThreadLocal __currentConnection = new ThreadLocal();
57  
58      private long _timeStamp=System.currentTimeMillis();
59      private int _requests;
60      private boolean _handling;
61      private boolean _destroy;
62      
63      protected Connector _connector;
64      protected EndPoint _endp;
65      protected Server _server;
66  
67      protected HttpURI _uri=new HttpURI();
68  
69      protected Parser _parser;
70      protected HttpFields _requestFields;
71      protected Request _request;
72      protected ServletInputStream _in;
73  
74      protected Generator _generator;
75      protected HttpFields _responseFields;
76      protected Response _response;
77      protected Output _out;
78      protected OutputWriter _writer;
79      protected PrintWriter _printWriter;
80  
81      int _include;
82      
83      private Object _associatedObject; // associated object
84      
85      private transient int _expect = UNKNOWN;
86      private transient int _version = UNKNOWN;
87      private transient boolean _head = false;
88      private transient boolean _host = false;
89      private transient boolean  _delayedHandling=false;
90  
91      /* ------------------------------------------------------------ */
92      public static HttpConnection getCurrentConnection()
93      {
94          return (HttpConnection) __currentConnection.get();
95      }
96      
97      /* ------------------------------------------------------------ */
98      protected static void setCurrentConnection(HttpConnection connection)
99      {
100         __currentConnection.set(connection);
101     }
102 
103     /* ------------------------------------------------------------ */
104     /** Constructor
105      * 
106      */
107     public HttpConnection(Connector connector, EndPoint endpoint, Server server)
108     {
109         _connector = connector;
110         _endp = endpoint;
111         _parser = new HttpParser(_connector, endpoint, new RequestHandler(), _connector.getHeaderBufferSize(), _connector.getRequestBufferSize());
112         _requestFields = new HttpFields();
113         _responseFields = new HttpFields();
114         _request = new Request(this);
115         _response = new Response(this);
116         _generator = new HttpGenerator(_connector, _endp, _connector.getHeaderBufferSize(), _connector.getResponseBufferSize());
117         _generator.setSendServerVersion(server.getSendServerVersion());
118         _server = server;
119     }
120 
121     /* ------------------------------------------------------------ */
122     public void destroy()
123     {
124         synchronized(this)
125         {
126             _destroy=true;
127             if (!_handling)   
128             {
129                 if (_parser!=null)
130                     _parser.reset(true);
131 
132                 if (_generator!=null)
133                     _generator.reset(true);
134 
135                 if (_requestFields!=null)
136                     _requestFields.destroy();
137 
138                 if (_responseFields!=null)
139                     _responseFields.destroy();
140 
141                 _server=null;
142             }
143         }
144     }
145     
146     /* ------------------------------------------------------------ */
147     /**
148      * @return the parser used by this connection
149      */        
150     public Parser getParser()
151     {
152         return _parser;
153     }
154     
155     /* ------------------------------------------------------------ */
156     /**
157      * @return the number of requests handled by this connection
158      */
159     public int getRequests()
160     {
161         return _requests;
162     }
163 
164     /* ------------------------------------------------------------ */
165     /**
166      * @return The time this connection was established.
167      */
168     public long getTimeStamp()
169     {
170         return _timeStamp;
171     }
172     
173     /* ------------------------------------------------------------ */
174     /**
175      * @return Returns the associatedObject.
176      */
177     public Object getAssociatedObject()
178     {
179         return _associatedObject;
180     }
181 
182     /* ------------------------------------------------------------ */
183     /**
184      * @param associatedObject The associatedObject to set.
185      */
186     public void setAssociatedObject(Object associatedObject)
187     {
188         _associatedObject = associatedObject;
189     }
190 
191     /* ------------------------------------------------------------ */
192     /**
193      * @return Returns the connector.
194      */
195     public Connector getConnector()
196     {
197         return _connector;
198     }
199 
200     /* ------------------------------------------------------------ */
201     /**
202      * @return Returns the requestFields.
203      */
204     public HttpFields getRequestFields()
205     {
206         return _requestFields;
207     }
208 
209     /* ------------------------------------------------------------ */
210     /**
211      * @return Returns the responseFields.
212      */
213     public HttpFields getResponseFields()
214     {
215         return _responseFields;
216     }
217 
218     /* ------------------------------------------------------------ */
219     /**
220      * @return The result of calling {@link #getConnector}.{@link Connector#isConfidential(Request) isCondidential}(request), or false
221      *  if there is no connector.
222      */
223     public boolean isConfidential(Request request)
224     {
225         if (_connector!=null)
226             return _connector.isConfidential(request);
227         return false;
228     }
229     
230     /* ------------------------------------------------------------ */
231     /**
232      * Find out if the request is INTEGRAL security.
233      * @param request
234      * @return <code>true</code> if there is a {@link #getConnector() connector} and it considers <code>request</code>
235      *         to be {@link Connector#isIntegral(Request) integral}
236      */
237     public boolean isIntegral(Request request)
238     {
239         if (_connector!=null)
240             return _connector.isIntegral(request);
241         return false;
242     }
243 
244     /* ------------------------------------------------------------ */
245     /**
246      * @return The {@link EndPoint} for this connection.
247      */
248     public EndPoint getEndPoint()
249     {
250         return _endp;
251     }
252 
253     /* ------------------------------------------------------------ */
254     /**
255      * @return <code>false</code> (this method is not yet implemented)
256      */
257     public boolean getResolveNames()
258     {
259         return _connector.getResolveNames();
260     }
261 
262     /* ------------------------------------------------------------ */
263     /**
264      * @return Returns the request.
265      */
266     public Request getRequest()
267     {
268         return _request;
269     }
270 
271     /* ------------------------------------------------------------ */
272     /**
273      * @return Returns the response.
274      */
275     public Response getResponse()
276     {
277         return _response;
278     }
279 
280     /* ------------------------------------------------------------ */
281     /**
282      * @return The input stream for this connection. The stream will be created if it does not already exist.
283      */
284     public ServletInputStream getInputStream()
285     {
286         if (_in == null) 
287             _in = new HttpParser.Input(((HttpParser)_parser),_connector.getMaxIdleTime());
288         return _in;
289     }
290 
291     /* ------------------------------------------------------------ */
292     /**
293      * @return The output stream for this connection. The stream will be created if it does not already exist.
294      */
295     public ServletOutputStream getOutputStream()
296     {
297         if (_out == null) 
298             _out = new Output();
299         return _out;
300     }
301 
302     /* ------------------------------------------------------------ */
303     /**
304      * @return A {@link PrintWriter} wrapping the {@link #getOutputStream output stream}. The writer is created if it
305      *    does not already exist.
306      */
307     public PrintWriter getPrintWriter(String encoding)
308     {
309         getOutputStream();
310         if (_writer==null)
311         {
312             _writer=new OutputWriter();
313             _printWriter=new PrintWriter(_writer)
314             {
315                 /* ------------------------------------------------------------ */
316                 /* 
317                  * @see java.io.PrintWriter#close()
318                  */
319                 public void close() 
320                 {
321                     try
322                     {
323                         out.close();
324                     }
325                     catch(IOException e)
326                     {
327                         Log.debug(e);
328                         setError();
329                     }
330                 }
331                 
332             };
333         }
334         _writer.setCharacterEncoding(encoding);
335         return _printWriter;
336     }
337     
338     /* ------------------------------------------------------------ */
339     public boolean isResponseCommitted()
340     {
341         return _generator.isCommitted();
342     }
343 
344     /* ------------------------------------------------------------ */
345     public void handle() throws IOException
346     {
347         // Loop while more in buffer
348         boolean more_in_buffer =true; // assume true until proven otherwise
349         int no_progress=0;
350         
351         while (more_in_buffer)
352         {
353             try
354             {
355                 synchronized(this)
356                 {
357                     if (_handling)
358                         throw new IllegalStateException(); // TODO delete this check
359                     _handling=true;
360                 }
361                 
362                 setCurrentConnection(this);
363                 long io=0;
364                 
365                 Continuation continuation = _request.getContinuation();
366                 if (continuation != null && continuation.isPending())
367                 {
368                     Log.debug("resume continuation {}",continuation);
369                     if (_request.getMethod()==null)
370                         throw new IllegalStateException();
371                     handleRequest();
372                 }
373                 else
374                 {
375                     // If we are not ended then parse available
376                     if (!_parser.isComplete()) 
377                         io=_parser.parseAvailable();
378                     
379                     // Do we have more generating to do?
380                     // Loop here because some writes may take multiple steps and
381                     // we need to flush them all before potentially blocking in the
382                     // next loop.
383                     while (_generator.isCommitted() && !_generator.isComplete())
384                     {
385                         long written=_generator.flush();
386                         io+=written;
387                         if (written<=0)
388                             break;
389                         else if (_endp.isBufferingOutput())
390                             _endp.flush();
391                     }
392                     
393                     // Flush buffers
394                     if (_endp.isBufferingOutput())
395                     {
396                         _endp.flush();
397                         if (!_endp.isBufferingOutput())
398                             no_progress=0;
399                     }
400                     
401                     if (io>0)
402                         no_progress=0;
403                     else if (no_progress++>=2) 
404                         return;
405                 }
406             }
407             catch (HttpException e)
408             {
409                 if (Log.isDebugEnabled())
410                 {
411                     Log.debug("uri="+_uri);
412                     Log.debug("fields="+_requestFields);
413                     Log.debug(e);
414                 }
415                 _generator.sendError(e.getStatus(), e.getReason(), null, true);
416                 
417                 _parser.reset(true);
418                 _endp.close();
419                 throw e;
420             }
421             finally
422             {
423                 setCurrentConnection(null);
424                 
425                 more_in_buffer = _parser.isMoreInBuffer() || _endp.isBufferingInput();  
426                 
427                 synchronized(this)
428                 {
429                     _handling=false;
430                     
431                     if (_destroy)
432                     { 
433                         destroy();
434                         return;
435                     }
436                 }
437                 
438                 if (_parser.isComplete() && _generator.isComplete() && !_endp.isBufferingOutput())
439                 {  
440                     if (!_generator.isPersistent())
441                     {
442                         _parser.reset(true);
443                         more_in_buffer=false;
444                     }
445                     
446                     reset(!more_in_buffer);
447                     no_progress=0;
448                 }
449                 
450                 Continuation continuation = _request.getContinuation();
451                 if (continuation != null && continuation.isPending())
452                 {
453                     break;
454                 }
455                 else if (_generator.isCommitted() && !_generator.isComplete() && _endp instanceof SelectChannelEndPoint) // TODO remove SelectChannel dependency
456                     ((SelectChannelEndPoint)_endp).setWritable(false);
457             }
458         }
459     }
460 
461     /* ------------------------------------------------------------ */
462     public void reset(boolean returnBuffers)
463     {
464         _parser.reset(returnBuffers); // TODO maybe only release when low on resources
465         _requestFields.clear();
466         _request.recycle();
467         
468         _generator.reset(returnBuffers); // TODO maybe only release when low on resources
469         _responseFields.clear();
470         _response.recycle();
471         
472         _uri.clear(); 
473     }
474     
475     /* ------------------------------------------------------------ */
476     protected void handleRequest() throws IOException
477     {
478         if (_server != null)
479         {
480             boolean retrying = false;
481             boolean error = false;
482             String threadName=null;
483             try
484             {
485                 // TODO try to do this lazily or more efficiently
486                 String info=URIUtil.canonicalPath(_uri.getDecodedPath());
487                 if (info==null)
488                     throw new HttpException(400);
489                 _request.setPathInfo(info);
490                 
491                 if (_out!=null)
492                     _out.reopen();
493                 
494                 if (Log.isDebugEnabled())
495                 {
496                     threadName=Thread.currentThread().getName();
497                     Thread.currentThread().setName(threadName+" - "+_uri);
498                 }
499                 
500                 _connector.customize(_endp, _request);
501                 
502                 _server.handle(this);
503             }
504             catch (RetryRequest r)
505             {
506                 if (Log.isDebugEnabled())
507                     Log.ignore(r);
508                 retrying = true;
509             }
510             catch (EofException e)
511             {
512                 Log.ignore(e);
513                 error=true;
514             }
515             catch (HttpException e)
516             {
517                 Log.debug(e);
518                 _request.setHandled(true);
519                 _response.sendError(e.getStatus(), e.getReason());
520                 error=true;
521             }
522             catch (Exception e)
523             {
524                 Log.warn(e);
525                 _request.setHandled(true);
526                 _generator.sendError(500, null, null, true);
527                 error=true;
528             }
529             catch (Error e)
530             {
531                 Log.warn(e);
532                 _request.setHandled(true);
533                 _generator.sendError(500, null, null, true);
534                 error=true;
535             }
536             finally
537             {   
538                 if (threadName!=null)
539                     Thread.currentThread().setName(threadName);
540                 
541                 if (!retrying)
542                 {
543                     if (_request.getContinuation()!=null)
544                     {
545                         Log.debug("continuation still pending {}");
546                         _request.getContinuation().reset();
547                     }
548                     
549                     if(_endp.isOpen())
550                     {
551                         if (_generator.isPersistent())
552                             _connector.persist(_endp);
553                         
554                         if (error) 
555                             _endp.close();
556                         else
557                         {
558                             if (!_response.isCommitted() && !_request.isHandled())
559                                 _response.sendError(HttpServletResponse.SC_NOT_FOUND);
560                             _response.complete();
561                         }
562                     }
563                     else
564                     {
565                         _response.complete(); // TODO ????????????
566                     }
567                 }
568             }
569         }
570     }
571 
572     /* ------------------------------------------------------------ */
573     public void commitResponse(boolean last) throws IOException
574     {
575         if (!_generator.isCommitted())
576         {
577             _generator.setResponse(_response.getStatus(), _response.getReason());
578             _generator.completeHeader(_responseFields, last);
579         }
580         if (last) 
581             _generator.complete();
582     }
583 
584     /* ------------------------------------------------------------ */
585     public void completeResponse() throws IOException
586     {
587         if (!_generator.isCommitted())
588         {
589             _generator.setResponse(_response.getStatus(), _response.getReason());
590             _generator.completeHeader(_responseFields, HttpGenerator.LAST);
591         }
592 
593         _generator.complete();
594     }
595 
596     /* ------------------------------------------------------------ */
597     public void flushResponse() throws IOException
598     {
599         try
600         {
601             commitResponse(HttpGenerator.MORE);
602             _generator.flush();
603         }
604         catch(IOException e)
605         {
606             throw (e instanceof EofException) ? e:new EofException(e);
607         }
608     }
609 
610     /* ------------------------------------------------------------ */
611     public Generator getGenerator()
612     {
613         return _generator;
614     }
615     
616 
617     /* ------------------------------------------------------------ */
618     public boolean isIncluding()
619     {
620         return _include>0;
621     }
622 
623     /* ------------------------------------------------------------ */
624     public void include()
625     {
626         _include++;
627     }
628 
629     /* ------------------------------------------------------------ */
630     public void included()
631     {
632         _include--;
633         if (_out!=null)
634             _out.reopen();
635     }
636 
637     /* ------------------------------------------------------------ */
638     public boolean isIdle()
639     {
640         return _generator.isIdle() && (_parser.isIdle() || _delayedHandling);
641     }
642     
643     /* ------------------------------------------------------------ */
644     /* ------------------------------------------------------------ */
645     /* ------------------------------------------------------------ */
646     private class RequestHandler extends HttpParser.EventHandler
647     {
648         private String _charset;
649         
650         /*
651          * 
652          * @see org.mortbay.jetty.HttpParser.EventHandler#startRequest(org.mortbay.io.Buffer,
653          *      org.mortbay.io.Buffer, org.mortbay.io.Buffer)
654          */
655         public void startRequest(Buffer method, Buffer uri, Buffer version) throws IOException
656         {
657             _host = false;
658             _expect = UNKNOWN;
659             _delayedHandling=false;
660             _charset=null;
661 
662             if(_request.getTimeStamp()==0)
663                 _request.setTimeStamp(System.currentTimeMillis());
664             _request.setMethod(method.toString());
665 
666             try
667             {
668                 _uri.parse(uri.array(), uri.getIndex(), uri.length());
669                 _request.setUri(_uri);
670 
671                 if (version==null)
672                 {
673                     _request.setProtocol(HttpVersions.HTTP_0_9);
674                     _version=HttpVersions.HTTP_0_9_ORDINAL;
675                 }
676                 else
677                 {
678                     version= HttpVersions.CACHE.get(version);
679                     _version = HttpVersions.CACHE.getOrdinal(version);
680                     if (_version <= 0) _version = HttpVersions.HTTP_1_0_ORDINAL;
681                     _request.setProtocol(version.toString());
682                 }
683 
684                 _head = method == HttpMethods.HEAD_BUFFER; // depends on method being decached.
685             }
686             catch (Exception e)
687             {
688                 throw new HttpException(HttpStatus.ORDINAL_400_Bad_Request,null,e);
689             }
690         }
691 
692         /*
693          * @see org.mortbay.jetty.HttpParser.EventHandler#parsedHeaderValue(org.mortbay.io.Buffer)
694          */
695         public void parsedHeader(Buffer name, Buffer value)
696         {
697             int ho = HttpHeaders.CACHE.getOrdinal(name);
698             switch (ho)
699             {
700                 case HttpHeaders.HOST_ORDINAL:
701                     // TODO check if host matched a host in the URI.
702                     _host = true;
703                     break;
704                     
705                 case HttpHeaders.EXPECT_ORDINAL:
706                     value = HttpHeaderValues.CACHE.lookup(value);
707                     _expect = HttpHeaderValues.CACHE.getOrdinal(value);
708                     break;
709                     
710                 case HttpHeaders.ACCEPT_ENCODING_ORDINAL:
711                 case HttpHeaders.USER_AGENT_ORDINAL:
712                     value = HttpHeaderValues.CACHE.lookup(value);
713                     break;
714                     
715                 case HttpHeaders.CONTENT_TYPE_ORDINAL:
716                     value = MimeTypes.CACHE.lookup(value);
717                     _charset=MimeTypes.getCharsetFromContentType(value);
718                     break;
719 
720                 case HttpHeaders.CONNECTION_ORDINAL:
721                     //looks rather clumsy, but the idea is to optimize for a single valued header
722                     int ordinal = HttpHeaderValues.CACHE.getOrdinal(value);
723                     switch(ordinal)
724                     {
725                         case -1:
726                         { 
727                             String[] values = value.toString().split(",");
728                             for  (int i=0;values!=null && i<values.length;i++)
729                             {
730                                 CachedBuffer cb = HttpHeaderValues.CACHE.get(values[i].trim());
731 
732                                 if (cb!=null)
733                                 {
734                                     switch(cb.getOrdinal())
735                                     {
736                                         case HttpHeaderValues.CLOSE_ORDINAL:
737                                             _responseFields.add(HttpHeaders.CONNECTION_BUFFER,HttpHeaderValues.CLOSE_BUFFER);
738                                             _generator.setPersistent(false);
739                                             break;
740 
741                                         case HttpHeaderValues.KEEP_ALIVE_ORDINAL:
742                                             if (_version==HttpVersions.HTTP_1_0_ORDINAL)
743                                                 _responseFields.add(HttpHeaders.CONNECTION_BUFFER,HttpHeaderValues.KEEP_ALIVE_BUFFER);
744                                             break;
745                                     }
746                                 }
747                             }
748                             break;
749                         }
750                         case HttpHeaderValues.CLOSE_ORDINAL:
751                             _responseFields.put(HttpHeaders.CONNECTION_BUFFER,HttpHeaderValues.CLOSE_BUFFER);
752                             _generator.setPersistent(false);
753                             break;
754 
755                         case HttpHeaderValues.KEEP_ALIVE_ORDINAL:
756                             if (_version==HttpVersions.HTTP_1_0_ORDINAL)
757                                 _responseFields.put(HttpHeaders.CONNECTION_BUFFER,HttpHeaderValues.KEEP_ALIVE_BUFFER);
758                             break;
759                     } 
760             }
761 
762             _requestFields.add(name, value);
763         }
764 
765         /*
766          * @see org.mortbay.jetty.HttpParser.EventHandler#headerComplete()
767          */
768         public void headerComplete() throws IOException
769         {
770             _requests++;
771             _generator.setVersion(_version);
772             switch (_version)
773             {
774                 case HttpVersions.HTTP_0_9_ORDINAL:
775                     break;
776                 case HttpVersions.HTTP_1_0_ORDINAL:
777                     _generator.setHead(_head);
778                     break;
779                 case HttpVersions.HTTP_1_1_ORDINAL:
780                     _generator.setHead(_head);
781                     
782                     if (_server.getSendDateHeader())
783                         _responseFields.put(HttpHeaders.DATE_BUFFER, _request.getTimeStampBuffer(),_request.getTimeStamp());
784                     
785                     if (!_host)
786                     {
787                         _generator.setResponse(HttpStatus.ORDINAL_400_Bad_Request, null);
788                         _responseFields.put(HttpHeaders.CONNECTION_BUFFER, HttpHeaderValues.CLOSE_BUFFER);
789                         _generator.completeHeader(_responseFields, true);
790                         _generator.complete();
791                         return;
792                     }
793 
794                     if (_expect != UNKNOWN)
795                     {
796                         if (_expect == HttpHeaderValues.CONTINUE_ORDINAL)
797                         {
798                             // TODO delay sending 100 response until a read is attempted.
799                             if (((HttpParser)_parser).getHeaderBuffer()==null || ((HttpParser)_parser).getHeaderBuffer().length()<2)
800                             {
801                                 _generator.setResponse(HttpStatus.ORDINAL_100_Continue, null);
802                                 _generator.completeHeader(null, true);
803                                 _generator.complete();
804                                 _generator.reset(false);
805                             }
806                         }
807                         else if (_expect == HttpHeaderValues.PROCESSING_ORDINAL)
808                         {
809                         }
810                         else
811                         {
812                             _generator.sendError(HttpStatus.ORDINAL_417_Expectation_Failed, null, null, true);
813                             return;
814                         }
815                     }
816                     
817                     break;
818                 default:
819             }
820 
821             if(_charset!=null)
822                 _request.setCharacterEncodingUnchecked(_charset);
823             
824             // Either handle now or wait for first content
825             if (((HttpParser)_parser).getContentLength()<=0 && !((HttpParser)_parser).isChunking())
826                 handleRequest();
827             else
828                 _delayedHandling=true;
829         }
830 
831         /* ------------------------------------------------------------ */
832         /*
833          * @see org.mortbay.jetty.HttpParser.EventHandler#content(int, org.mortbay.io.Buffer)
834          */
835         public void content(Buffer ref) throws IOException
836         {
837             if (_delayedHandling)
838             {
839                 _delayedHandling=false;
840                 handleRequest();
841             }
842         }
843 
844         /*
845          * (non-Javadoc)
846          * 
847          * @see org.mortbay.jetty.HttpParser.EventHandler#messageComplete(int)
848          */
849         public void messageComplete(long contextLength) throws IOException
850         {
851             if (_delayedHandling)
852             {
853                 _delayedHandling=false;
854                 handleRequest();
855             }
856         }
857 
858         /*
859          * (non-Javadoc)
860          * 
861          * @see org.mortbay.jetty.HttpParser.EventHandler#startResponse(org.mortbay.io.Buffer, int,
862          *      org.mortbay.io.Buffer)
863          */
864         public void startResponse(Buffer version, int status, Buffer reason)
865         {
866             Log.debug("Bad request!: "+version+" "+status+" "+reason);
867         }
868 
869     }
870 
871     
872     /* ------------------------------------------------------------ */
873     /* ------------------------------------------------------------ */
874     /* ------------------------------------------------------------ */
875     public class Output extends AbstractGenerator.Output 
876     {
877         Output()
878         {
879             super((AbstractGenerator)HttpConnection.this._generator,_connector.getMaxIdleTime());
880         }
881         
882         /* ------------------------------------------------------------ */
883         /*
884          * @see java.io.OutputStream#close()
885          */
886         public void close() throws IOException
887         {
888             if (_closed)
889                 return;
890             
891             if (!isIncluding() && !_generator.isCommitted())
892                 commitResponse(HttpGenerator.LAST);
893             else
894                 flushResponse();
895             
896             super.close();
897         }
898 
899         
900         /* ------------------------------------------------------------ */
901         /*
902          * @see java.io.OutputStream#flush()
903          */
904         public void flush() throws IOException
905         {
906             if (!_generator.isCommitted())
907                 commitResponse(HttpGenerator.MORE);
908             super.flush();
909         }
910 
911         /* ------------------------------------------------------------ */
912         /* 
913          * @see javax.servlet.ServletOutputStream#print(java.lang.String)
914          */
915         public void print(String s) throws IOException
916         {
917             if (_closed)
918                 throw new IOException("Closed");
919             PrintWriter writer=getPrintWriter(null);
920             writer.print(s);
921         }
922 
923         /* ------------------------------------------------------------ */
924         public void sendResponse(Buffer response) throws IOException
925         {
926             ((HttpGenerator)_generator).sendResponse(response);
927         }
928         
929         /* ------------------------------------------------------------ */
930         public void sendContent(Object content) throws IOException
931         {
932             Resource resource=null;
933             
934             if (_closed)
935                 throw new IOException("Closed");
936             
937             if (_generator.getContentWritten() > 0) throw new IllegalStateException("!empty");
938 
939             if (content instanceof HttpContent)
940             {
941                 HttpContent c = (HttpContent) content;
942                 if (c.getContentType() != null && !_responseFields.containsKey(HttpHeaders.CONTENT_TYPE_BUFFER)) 
943                     _responseFields.add(HttpHeaders.CONTENT_TYPE_BUFFER, c.getContentType());
944                 if (c.getContentLength() > 0) 
945                     _responseFields.putLongField(HttpHeaders.CONTENT_LENGTH_BUFFER, c.getContentLength());
946                 Buffer lm = c.getLastModified();
947                 long lml=c.getResource().lastModified();
948                 if (lm != null) 
949                     _responseFields.put(HttpHeaders.LAST_MODIFIED_BUFFER, lm,lml);
950                 else if (c.getResource()!=null)
951                 {
952                     if (lml!=-1)
953                         _responseFields.putDateField(HttpHeaders.LAST_MODIFIED_BUFFER, lml);
954                 }
955                     
956                 content = c.getBuffer();
957                 if (content==null)
958                     content=c.getInputStream();
959             }
960             else if (content instanceof Resource)
961             {
962                 resource=(Resource)content;
963                 _responseFields.putDateField(HttpHeaders.LAST_MODIFIED_BUFFER, resource.lastModified());
964                 content=resource.getInputStream();
965             }
966             
967             
968             if (content instanceof Buffer)
969             {
970                 _generator.addContent((Buffer) content, HttpGenerator.LAST);
971                 commitResponse(HttpGenerator.LAST);
972             }
973             else if (content instanceof InputStream)
974             {
975                 InputStream in = (InputStream)content;
976                 
977                 try
978                 {
979                     int max = _generator.prepareUncheckedAddContent();
980                     Buffer buffer = _generator.getUncheckedBuffer();
981 
982                     int len=buffer.readFrom(in,max);
983 
984                     while (len>=0)
985                     {
986                         _generator.completeUncheckedAddContent();
987                         _out.flush();
988 
989                         max = _generator.prepareUncheckedAddContent();
990                         buffer = _generator.getUncheckedBuffer();
991                         len=buffer.readFrom(in,max);
992                     }
993                     _generator.completeUncheckedAddContent();
994                     _out.flush();   
995                 }
996                 finally
997                 {
998                     if (resource!=null)
999                         resource.release();
1000                     else
1001                         in.close();
1002                       
1003                 }
1004             }
1005             else
1006                 throw new IllegalArgumentException("unknown content type?");
1007             
1008             
1009         }     
1010     }
1011 
1012     /* ------------------------------------------------------------ */
1013     /* ------------------------------------------------------------ */
1014     /* ------------------------------------------------------------ */
1015     public class OutputWriter extends AbstractGenerator.OutputWriter
1016     {
1017         OutputWriter()
1018         {
1019             super(HttpConnection.this._out);
1020         }
1021     }
1022 
1023 }