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   * 
36   */
37  public abstract class AbstractGenerator implements Generator
38  {
39      // states
40      public final static int STATE_HEADER = 0;
41      public final static int STATE_CONTENT = 2;
42      public final static int STATE_FLUSHING = 3;
43      public final static int STATE_END = 4;
44      
45      public static final byte[] NO_BYTES = {};
46      public static final int MAX_OUTPUT_CHARS = 512; 
47  
48      // data
49  
50      protected final Buffers _buffers; // source of buffers
51      protected final EndPoint _endp;
52      protected final int _headerBufferSize;
53      protected int _contentBufferSize;
54      
55      protected int _state = STATE_HEADER;
56      
57      protected int _status = 0;
58      protected int _version = HttpVersions.HTTP_1_1_ORDINAL;
59      protected  Buffer _reason;
60      protected  Buffer _method;
61      protected  String _uri;
62  
63      protected long _contentWritten = 0;
64      protected long _contentLength = HttpTokens.UNKNOWN_CONTENT;
65      protected boolean _last = false;
66      protected boolean _head = false;
67      protected boolean _noContent = false;
68      protected boolean _close = false;
69  
70      
71      protected Buffer _header; // Buffer for HTTP header (and maybe small _content)
72      protected Buffer _buffer; // Buffer for copy of passed _content
73      protected Buffer _content; // Buffer passed to addContent
74      
75      private boolean _sendServerVersion;
76  
77      
78      /* ------------------------------------------------------------------------------- */
79      /**
80       * Constructor.
81       * 
82       * @param buffers buffer pool
83       * @param headerBufferSize Size of the buffer to allocate for HTTP header
84       * @param contentBufferSize Size of the buffer to allocate for HTTP content
85       */
86      public AbstractGenerator(Buffers buffers, EndPoint io, int headerBufferSize, int contentBufferSize)
87      {
88          this._buffers = buffers;
89          this._endp = io;
90          _headerBufferSize=headerBufferSize;
91          _contentBufferSize=contentBufferSize;
92      }
93  
94      /* ------------------------------------------------------------------------------- */
95      public boolean isOpen()
96      {
97          return _endp.isOpen();
98      }
99      
100     /* ------------------------------------------------------------------------------- */
101     public void reset(boolean returnBuffers)
102     {
103         _state = STATE_HEADER;
104         _status = 0;
105         _version = HttpVersions.HTTP_1_1_ORDINAL;
106         _reason = null;
107         _last = false;
108         _head = false;
109         _noContent=false;
110         _close = false;
111         _contentWritten = 0;
112         _contentLength = HttpTokens.UNKNOWN_CONTENT;
113 
114         synchronized(this)
115         {
116             if (returnBuffers)
117             {
118                 if (_header != null) 
119                     _buffers.returnBuffer(_header);
120                 _header = null;
121                 if (_buffer != null) 
122                     _buffers.returnBuffer(_buffer);
123                 _buffer = null;
124             }
125             else
126             {
127                 if (_header != null) 
128                     _header.clear();
129 
130                 if (_buffer != null)
131                 {
132                     _buffers.returnBuffer(_buffer);
133                     _buffer = null;
134                 }
135             }
136         }
137         _content = null;
138         _method=null;
139     }
140 
141     /* ------------------------------------------------------------------------------- */
142     public void resetBuffer()
143     {                   
144         if(_state>=STATE_FLUSHING)
145             throw new IllegalStateException("Flushed");
146         
147         _last = false;
148         _close = false;
149         _contentWritten = 0;
150         _contentLength = HttpTokens.UNKNOWN_CONTENT;
151         _content=null;
152         if (_buffer!=null)
153             _buffer.clear();  
154     }
155 
156     /* ------------------------------------------------------------ */
157     /**
158      * @return Returns the contentBufferSize.
159      */
160     public int getContentBufferSize()
161     {
162         return _contentBufferSize;
163     }
164 
165     /* ------------------------------------------------------------ */
166     /**
167      * @param contentBufferSize The contentBufferSize to set.
168      */
169     public void increaseContentBufferSize(int contentBufferSize)
170     {
171         if (contentBufferSize > _contentBufferSize)
172         {
173             _contentBufferSize = contentBufferSize;
174             if (_buffer != null)
175             {
176                 Buffer nb = _buffers.getBuffer(_contentBufferSize);
177                 nb.put(_buffer);
178                 _buffers.returnBuffer(_buffer);
179                 _buffer = nb;
180             }
181         }
182     }
183     
184     /* ------------------------------------------------------------ */    
185     public Buffer getUncheckedBuffer()
186     {
187         return _buffer;
188     }
189     
190     /* ------------------------------------------------------------ */    
191     public boolean getSendServerVersion ()
192     {
193         return _sendServerVersion;
194     }
195     
196     /* ------------------------------------------------------------ */    
197     public void setSendServerVersion (boolean sendServerVersion)
198     {
199         _sendServerVersion = sendServerVersion;
200     }
201     
202     /* ------------------------------------------------------------ */
203     public int getState()
204     {
205         return _state;
206     }
207 
208     /* ------------------------------------------------------------ */
209     public boolean isState(int state)
210     {
211         return _state == state;
212     }
213 
214     /* ------------------------------------------------------------ */
215     public boolean isComplete()
216     {
217         return _state == STATE_END;
218     }
219 
220     /* ------------------------------------------------------------ */
221     public boolean isIdle()
222     {
223         return _state == STATE_HEADER && _method==null && _status==0;
224     }
225 
226     /* ------------------------------------------------------------ */
227     public boolean isCommitted()
228     {
229         return _state != STATE_HEADER;
230     }
231 
232     /* ------------------------------------------------------------ */
233     /**
234      * @return Returns the head.
235      */
236     public boolean isHead()
237     {
238         return _head;
239     }
240 
241     /* ------------------------------------------------------------ */
242     public void setContentLength(long value)
243     {
244         if (value<0)
245             _contentLength=HttpTokens.UNKNOWN_CONTENT;
246         else
247             _contentLength=value;
248     }
249     
250     /* ------------------------------------------------------------ */
251     /**
252      * @param head The head to set.
253      */
254     public void setHead(boolean head)
255     {
256         _head = head;
257     }
258 
259     /* ------------------------------------------------------------ */
260     /**
261      * @return <code>false</code> if the connection should be closed after a request has been read,
262      * <code>true</code> if it should be used for additional requests.
263      */
264     public boolean isPersistent()
265     {
266         return !_close;
267     }
268 
269     /* ------------------------------------------------------------ */
270     public void setPersistent(boolean persistent)
271     {
272         _close=!persistent;
273     }
274 
275     /* ------------------------------------------------------------ */
276     /**
277      * @param version The version of the client the response is being sent to (NB. Not the version
278      *            in the response, which is the version of the server).
279      */
280     public void setVersion(int version)
281     {
282         if (_state != STATE_HEADER) 
283             throw new IllegalStateException("STATE!=START "+_state);
284         _version = version;
285         if (_version==HttpVersions.HTTP_0_9_ORDINAL && _method!=null)
286             _noContent=true;
287     }
288 
289     /* ------------------------------------------------------------ */
290     public int getVersion()
291     {
292         return _version;
293     }
294     
295     /* ------------------------------------------------------------ */
296     /**
297      */
298     public void setRequest(String method, String uri)
299     {
300         if (method==null || HttpMethods.GET.equals(method) )
301             _method=HttpMethods.GET_BUFFER;
302         else
303             _method=HttpMethods.CACHE.lookup(method);
304         _uri=uri;
305         if (_version==HttpVersions.HTTP_0_9_ORDINAL)
306             _noContent=true;
307     }
308 
309     /* ------------------------------------------------------------ */
310     /**
311      * @param status The status code to send.
312      * @param reason the status message to send.
313      */
314     public void setResponse(int status, String reason)
315     {
316         if (_state != STATE_HEADER) throw new IllegalStateException("STATE!=START");
317         _method=null;
318         _status = status;
319         if (reason!=null)
320         {
321             int len=reason.length();
322             if (len>_headerBufferSize/2)
323                 len=_headerBufferSize/2;
324             _reason=new ByteArrayBuffer(len);
325             for (int i=0;i<len;i++)
326             {
327                 char ch = reason.charAt(i);
328                 if (ch!='\r'&&ch!='\n')
329                     _reason.put((byte)ch);
330                 else
331                     _reason.put((byte)' ');
332             }
333         }
334     }
335 
336     /* ------------------------------------------------------------ */
337     /** Prepare buffer for unchecked writes.
338      * Prepare the generator buffer to receive unchecked writes
339      * @return the available space in the buffer.
340      * @throws IOException
341      */
342     public abstract int prepareUncheckedAddContent() throws IOException;
343 
344     /* ------------------------------------------------------------ */
345     void uncheckedAddContent(int b)
346     {
347         _buffer.put((byte)b);
348     }
349 
350     /* ------------------------------------------------------------ */
351     public void completeUncheckedAddContent()
352     {
353         if (_noContent)
354         {
355             if(_buffer!=null)
356                 _buffer.clear();
357             return;
358         }
359         else 
360         {
361             _contentWritten+=_buffer.length();
362             if (_head)
363                 _buffer.clear();
364         }
365     }
366     
367     /* ------------------------------------------------------------ */
368     public boolean isBufferFull()
369     {
370         if (_buffer != null && _buffer.space()==0)
371         {
372             if (_buffer.length()==0 && !_buffer.isImmutable())
373                 _buffer.compact();
374             return _buffer.space()==0;
375         }
376 
377         return _content!=null && _content.length()>0;
378     }
379     
380     /* ------------------------------------------------------------ */
381     public boolean isContentWritten()
382     {
383         return _contentLength>=0 && _contentWritten>=_contentLength;
384     }
385     
386     /* ------------------------------------------------------------ */
387     public abstract void completeHeader(HttpFields fields, boolean allContentAdded) throws IOException;
388     
389     /* ------------------------------------------------------------ */
390     /**
391      * Complete the message.
392      * 
393      * @throws IOException
394      */
395     public void complete() throws IOException
396     {
397         if (_state == STATE_HEADER)
398         {
399             throw new IllegalStateException("State==HEADER");
400         }
401 
402         if (_contentLength >= 0 && _contentLength != _contentWritten && !_head)
403         {
404             if (Log.isDebugEnabled())
405                 Log.debug("ContentLength written=="+_contentWritten+" != contentLength=="+_contentLength);
406             _close = true;
407         }
408     }
409 
410     /* ------------------------------------------------------------ */
411     public abstract long flushBuffer() throws IOException;
412 
413     
414     /* ------------------------------------------------------------ */
415     public void flush(long maxIdleTime) throws IOException
416     {
417         // block until everything is flushed
418         Buffer content = _content;
419         Buffer buffer = _buffer;
420         if (content!=null && content.length()>0 || buffer!=null && buffer.length()>0 || isBufferFull())
421         {
422             flushBuffer();
423             
424             while ((content!=null && content.length()>0 ||buffer!=null && buffer.length()>0) && _endp.isOpen())
425                 blockForOutput(maxIdleTime);
426         }
427     }
428 
429     /* ------------------------------------------------------------ */
430     /**
431      * Utility method to send an error response. If the builder is not committed, this call is
432      * equivalent to a setResponse, addcontent and complete call.
433      * 
434      * @param code
435      * @param reason
436      * @param content
437      * @param close
438      * @throws IOException
439      */
440     public void sendError(int code, String reason, String content, boolean close) throws IOException
441     {
442         if (!isCommitted())
443         {
444             setResponse(code, reason);
445             _close = close;
446             completeHeader(null, false);
447             if (content != null) 
448                 addContent(new View(new ByteArrayBuffer(content)), Generator.LAST);
449             complete();
450         }
451     }
452 
453     /* ------------------------------------------------------------ */
454     /**
455      * @return Returns the contentWritten.
456      */
457     public long getContentWritten()
458     {
459         return _contentWritten;
460     }
461     
462 
463 
464     /* ------------------------------------------------------------ */
465     public void  blockForOutput(long maxIdleTime) throws IOException
466     {
467         if (_endp.isBlocking())
468         {
469             try
470             {
471                 flushBuffer();
472             }
473             catch(IOException e)
474             {
475                 _endp.close();
476                 throw e;
477             }
478         }
479         else
480         {
481             if (!_endp.blockWritable(maxIdleTime))
482             {
483                 _endp.close();
484                 throw new EofException("timeout");
485             }
486             
487             flushBuffer();
488         }
489     }
490     
491 }