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