View Javadoc

1   // ========================================================================
2   // Copyright (c) 2004-2009 Mort Bay Consulting Pty. Ltd.
3   // ------------------------------------------------------------------------
4   // All rights reserved. This program and the accompanying materials
5   // are made available under the terms of the Eclipse Public License v1.0
6   // and Apache License v2.0 which accompanies this distribution.
7   // The Eclipse Public License is available at 
8   // http://www.eclipse.org/legal/epl-v10.html
9   // The Apache License v2.0 is available at
10  // http://www.opensource.org/licenses/apache2.0.php
11  // You may elect to redistribute this code under either of these licenses. 
12  // ========================================================================
13  
14  package org.eclipse.jetty.http;
15  
16  import java.io.IOException;
17  
18  import org.eclipse.jetty.io.Buffer;
19  import org.eclipse.jetty.io.Buffers;
20  import org.eclipse.jetty.io.ByteArrayBuffer;
21  import org.eclipse.jetty.io.EndPoint;
22  import org.eclipse.jetty.io.EofException;
23  import org.eclipse.jetty.io.View;
24  import org.eclipse.jetty.util.log.Log;
25  
26  /* ------------------------------------------------------------ */
27  /**
28   * Abstract Generator. Builds HTTP Messages.
29   * 
30   * Currently this class uses a system parameter "jetty.direct.writers" to control
31   * two optional writer to byte conversions. buffer.writers=true will probably be 
32   * faster, but will consume more memory.   This option is just for testing and tuning.
33   * 
34   */
35  public abstract class AbstractGenerator implements Generator
36  {
37      // states
38      public final static int STATE_HEADER = 0;
39      public final static int STATE_CONTENT = 2;
40      public final static int STATE_FLUSHING = 3;
41      public final static int STATE_END = 4;
42      
43      public static final byte[] NO_BYTES = {};
44  
45      // data
46  
47      protected final Buffers _buffers; // source of buffers
48      protected final EndPoint _endp;
49      
50      protected int _state = STATE_HEADER;
51      
52      protected int _status = 0;
53      protected int _version = HttpVersions.HTTP_1_1_ORDINAL;
54      protected  Buffer _reason;
55      protected  Buffer _method;
56      protected  String _uri;
57  
58      protected long _contentWritten = 0;
59      protected long _contentLength = HttpTokens.UNKNOWN_CONTENT;
60      protected boolean _last = false;
61      protected boolean _head = false;
62      protected boolean _noContent = false;
63      protected Boolean _persistent = null;
64      
65      protected Buffer _header; // Buffer for HTTP header (and maybe small _content)
66      protected Buffer _buffer; // Buffer for copy of passed _content
67      protected Buffer _content; // Buffer passed to addContent
68      
69      protected Buffer _date;
70      
71      private boolean _sendServerVersion;
72  
73      
74      /* ------------------------------------------------------------------------------- */
75      /**
76       * Constructor.
77       * 
78       * @param buffers buffer pool
79       * @param io the end point
80       */
81      public AbstractGenerator(Buffers buffers, EndPoint io)
82      {
83          this._buffers = buffers;
84          this._endp = io;
85      }
86  
87      /* ------------------------------------------------------------------------------- */
88      public abstract boolean isRequest();
89      
90      /* ------------------------------------------------------------------------------- */
91      public abstract boolean isResponse();
92      
93      /* ------------------------------------------------------------------------------- */
94      public boolean isOpen()
95      {
96          return _endp.isOpen();
97      }
98      
99      /* ------------------------------------------------------------------------------- */
100     public void reset(boolean returnBuffers)
101     {
102         _state = STATE_HEADER;
103         _status = 0;
104         _version = HttpVersions.HTTP_1_1_ORDINAL;
105         _reason = null;
106         _last = false;
107         _head = false;
108         _noContent=false;
109         _persistent = null;
110         _contentWritten = 0;
111         _contentLength = HttpTokens.UNKNOWN_CONTENT;
112         _date = null;
113 
114         // always return the buffer
115         if (_buffer!=null)
116             _buffers.returnBuffer(_buffer);
117         _buffer=null;
118 
119         if (returnBuffers)
120         {
121             if (_header!=null)
122                 _buffers.returnBuffer(_header);
123             _header=null;
124         }
125         else if (_header != null) 
126             _header.clear();
127 
128         _content = null;
129         _method=null;
130     }
131 
132     /* ------------------------------------------------------------------------------- */
133     public void resetBuffer()
134     {                   
135         if(_state>=STATE_FLUSHING)
136             throw new IllegalStateException("Flushed");
137         
138         _last = false;
139         _persistent=null;
140         _contentWritten = 0;
141         _contentLength = HttpTokens.UNKNOWN_CONTENT;
142         _content=null;
143         if (_buffer!=null)
144             _buffer.clear();  
145     }
146 
147     /* ------------------------------------------------------------ */
148     /**
149      * @return Returns the contentBufferSize.
150      */
151     public int getContentBufferSize()
152     {
153         if (_buffer==null)
154             _buffer=_buffers.getBuffer();
155         return _buffer.capacity();
156     }
157 
158     /* ------------------------------------------------------------ */
159     /**
160      * @param contentBufferSize The contentBufferSize to set.
161      */
162     public void increaseContentBufferSize(int contentBufferSize)
163     {
164         if (_buffer==null)
165             _buffer=_buffers.getBuffer();
166         if (contentBufferSize > _buffer.capacity())
167         {
168             Buffer nb = _buffers.getBuffer(contentBufferSize);
169             nb.put(_buffer);
170             _buffers.returnBuffer(_buffer);
171             _buffer = nb;
172         }
173     }
174     
175     /* ------------------------------------------------------------ */    
176     public Buffer getUncheckedBuffer()
177     {
178         return _buffer;
179     }
180     
181     /* ------------------------------------------------------------ */    
182     public boolean getSendServerVersion ()
183     {
184         return _sendServerVersion;
185     }
186     
187     /* ------------------------------------------------------------ */    
188     public void setSendServerVersion (boolean sendServerVersion)
189     {
190         _sendServerVersion = sendServerVersion;
191     }
192     
193     /* ------------------------------------------------------------ */
194     public int getState()
195     {
196         return _state;
197     }
198 
199     /* ------------------------------------------------------------ */
200     public boolean isState(int state)
201     {
202         return _state == state;
203     }
204 
205     /* ------------------------------------------------------------ */
206     public boolean isComplete()
207     {
208         return _state == STATE_END;
209     }
210 
211     /* ------------------------------------------------------------ */
212     public boolean isIdle()
213     {
214         return _state == STATE_HEADER && _method==null && _status==0;
215     }
216 
217     /* ------------------------------------------------------------ */
218     public boolean isCommitted()
219     {
220         return _state != STATE_HEADER;
221     }
222 
223     /* ------------------------------------------------------------ */
224     /**
225      * @return Returns the head.
226      */
227     public boolean isHead()
228     {
229         return _head;
230     }
231 
232     /* ------------------------------------------------------------ */
233     public void setContentLength(long value)
234     {
235         if (value<0)
236             _contentLength=HttpTokens.UNKNOWN_CONTENT;
237         else
238             _contentLength=value;
239     }
240     
241     /* ------------------------------------------------------------ */
242     /**
243      * @param head The head to set.
244      */
245     public void setHead(boolean head)
246     {
247         _head = head;
248     }
249 
250     /* ------------------------------------------------------------ */
251     /**
252      * @return <code>false</code> if the connection should be closed after a request has been read,
253      * <code>true</code> if it should be used for additional requests.
254      */
255     public boolean isPersistent()
256     {
257         return _persistent!=null
258         ?_persistent.booleanValue()
259         :(isRequest()?true:_version>HttpVersions.HTTP_1_0_ORDINAL);
260     }
261     
262     /* ------------------------------------------------------------ */
263     public void setPersistent(boolean persistent)
264     {
265         _persistent=persistent;
266     }
267 
268     /* ------------------------------------------------------------ */
269     /**
270      * @param version The version of the client the response is being sent to (NB. Not the version
271      *            in the response, which is the version of the server).
272      */
273     public void setVersion(int version)
274     {
275         if (_state != STATE_HEADER) 
276             throw new IllegalStateException("STATE!=START "+_state);
277         _version = version;
278         if (_version==HttpVersions.HTTP_0_9_ORDINAL && _method!=null)
279             _noContent=true;
280     }
281 
282     /* ------------------------------------------------------------ */
283     public int getVersion()
284     {
285         return _version;
286     }
287     
288     /* ------------------------------------------------------------ */
289     /**
290      * @see org.eclipse.jetty.http.Generator#setDate(org.eclipse.jetty.io.Buffer)
291      */
292     public void setDate(Buffer timeStampBuffer)
293     {
294         _date=timeStampBuffer;
295     }
296 
297     /* ------------------------------------------------------------ */
298     /**
299      */
300     public void setRequest(String method, String uri)
301     {
302         if (method==null || HttpMethods.GET.equals(method) )
303             _method=HttpMethods.GET_BUFFER;
304         else
305             _method=HttpMethods.CACHE.lookup(method);
306         _uri=uri;
307         if (_version==HttpVersions.HTTP_0_9_ORDINAL)
308             _noContent=true;
309     }
310 
311     /* ------------------------------------------------------------ */
312     /**
313      * @param status The status code to send.
314      * @param reason the status message to send.
315      */
316     public void setResponse(int status, String reason)
317     {
318         if (_state != STATE_HEADER) throw new IllegalStateException("STATE!=START");
319         _method=null;
320         _status = status;
321         if (reason!=null)
322         {
323             int len=reason.length();
324             
325             // TODO don't hard code
326             if (len>1024)
327                 len=1024;
328             _reason=new ByteArrayBuffer(len);
329             for (int i=0;i<len;i++)
330             {
331                 char ch = reason.charAt(i);
332                 if (ch!='\r'&&ch!='\n')
333                     _reason.put((byte)ch);
334                 else
335                     _reason.put((byte)' ');
336             }
337         }
338     }
339 
340     /* ------------------------------------------------------------ */
341     /** Prepare buffer for unchecked writes.
342      * Prepare the generator buffer to receive unchecked writes
343      * @return the available space in the buffer.
344      * @throws IOException
345      */
346     public abstract int prepareUncheckedAddContent() throws IOException;
347 
348     /* ------------------------------------------------------------ */
349     void uncheckedAddContent(int b)
350     {
351         _buffer.put((byte)b);
352     }
353 
354     /* ------------------------------------------------------------ */
355     public void completeUncheckedAddContent()
356     {
357         if (_noContent)
358         {
359             if(_buffer!=null)
360                 _buffer.clear();
361         }
362         else 
363         {
364             _contentWritten+=_buffer.length();
365             if (_head)
366                 _buffer.clear();
367         }
368     }
369     
370     /* ------------------------------------------------------------ */
371     public boolean isBufferFull()
372     {
373         if (_buffer != null && _buffer.space()==0)
374         {
375             if (_buffer.length()==0 && !_buffer.isImmutable())
376                 _buffer.compact();
377             return _buffer.space()==0;
378         }
379 
380         return _content!=null && _content.length()>0;
381     }
382 
383     /* ------------------------------------------------------------ */
384     public boolean isWritten()
385     {
386         return _contentWritten>0;
387     }
388     
389     /* ------------------------------------------------------------ */
390     public boolean isAllContentWritten()
391     {
392         return _contentLength>=0 && _contentWritten>=_contentLength;
393     }
394     
395     /* ------------------------------------------------------------ */
396     public abstract void completeHeader(HttpFields fields, boolean allContentAdded) throws IOException;
397     
398     /* ------------------------------------------------------------ */
399     /**
400      * Complete the message.
401      * 
402      * @throws IOException
403      */
404     public void complete() throws IOException
405     {
406         if (_state == STATE_HEADER)
407         {
408             throw new IllegalStateException("State==HEADER");
409         }
410 
411         if (_contentLength >= 0 && _contentLength != _contentWritten && !_head)
412         {
413             if (Log.isDebugEnabled())
414                 Log.debug("ContentLength written=="+_contentWritten+" != contentLength=="+_contentLength);
415             _persistent = false;
416         }
417     }
418 
419     /* ------------------------------------------------------------ */
420     public abstract long flushBuffer() throws IOException;
421 
422     
423     /* ------------------------------------------------------------ */
424     public void flush(long maxIdleTime) throws IOException
425     {
426         // block until everything is flushed
427         Buffer content = _content;
428         Buffer buffer = _buffer;
429         if (content!=null && content.length()>0 || buffer!=null && buffer.length()>0 || isBufferFull())
430         {
431             flushBuffer();
432             
433             while ((content!=null && content.length()>0 ||buffer!=null && buffer.length()>0) && _endp.isOpen())
434                 blockForOutput(maxIdleTime);
435         }
436         
437         // make sure buffered data is also flushed
438         while (_endp.isBufferingOutput() && _endp.isOpen())
439         {
440             if (!_endp.isBlocking())
441                 _endp.blockWritable(maxIdleTime);
442             _endp.flush();
443         }
444     }
445 
446     /* ------------------------------------------------------------ */
447     /**
448      * Utility method to send an error response. If the builder is not committed, this call is
449      * equivalent to a setResponse, addContent and complete call.
450      * 
451      * @param code The error code
452      * @param reason The error reason
453      * @param content Contents of the error page
454      * @param close True if the connection should be closed
455      * @throws IOException if there is a problem flushing the response
456      */
457     public void sendError(int code, String reason, String content, boolean close) throws IOException
458     {
459         if (close)
460             _persistent=false;
461         if (!isCommitted())
462         {
463             setResponse(code, reason);
464             if (content != null) 
465             {
466                 completeHeader(null, false);
467                 addContent(new View(new ByteArrayBuffer(content)), Generator.LAST);
468             }
469             else
470             {
471                 completeHeader(null, true);
472             }
473             complete();
474         }
475     }
476 
477     /* ------------------------------------------------------------ */
478     /**
479      * @return Returns the contentWritten.
480      */
481     public long getContentWritten()
482     {
483         return _contentWritten;
484     }
485     
486 
487 
488     /* ------------------------------------------------------------ */
489     public void  blockForOutput(long maxIdleTime) throws IOException
490     {
491         if (_endp.isBlocking())
492         {
493             try
494             {
495                 flushBuffer();
496             }
497             catch(IOException e)
498             {
499                 _endp.close();
500                 throw e;
501             }
502         }
503         else
504         {
505             if (!_endp.blockWritable(maxIdleTime))
506             {
507                 _endp.close();
508                 throw new EofException("timeout");
509             }
510             
511             flushBuffer();
512         }
513     }
514     
515 }