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