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