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;
22import java.io.InterruptedIOException;
23
24import org.eclipse.jetty.io.Buffer;
25import org.eclipse.jetty.io.BufferCache.CachedBuffer;
26import org.eclipse.jetty.io.BufferUtil;
27import org.eclipse.jetty.io.Buffers;
28import org.eclipse.jetty.io.ByteArrayBuffer;
29import org.eclipse.jetty.io.EndPoint;
30import org.eclipse.jetty.io.EofException;
31import org.eclipse.jetty.util.StringUtil;
32import org.eclipse.jetty.util.log.Log;
33import org.eclipse.jetty.util.log.Logger;
34
35/* ------------------------------------------------------------ */
36/**
37 * HttpGenerator. Builds HTTP Messages.
38 *
39 *
40 *
41 */
42public class HttpGenerator extends AbstractGenerator
43{
44    private static final Logger LOG = Log.getLogger(HttpGenerator.class);
45
46    // Build cache of response lines for status
47    private static class Status
48    {
49        Buffer _reason;
50        Buffer _schemeCode;
51        Buffer _responseLine;
52    }
53    private static final Status[] __status = new Status[HttpStatus.MAX_CODE+1];
54    static
55    {
56        int versionLength=HttpVersions.HTTP_1_1_BUFFER.length();
57
58        for (int i=0;i<__status.length;i++)
59        {
60            HttpStatus.Code code = HttpStatus.getCode(i);
61            if (code==null)
62                continue;
63            String reason=code.getMessage();
64            byte[] bytes=new byte[versionLength+5+reason.length()+2];
65            HttpVersions.HTTP_1_1_BUFFER.peek(0,bytes, 0, versionLength);
66            bytes[versionLength+0]=' ';
67            bytes[versionLength+1]=(byte)('0'+i/100);
68            bytes[versionLength+2]=(byte)('0'+(i%100)/10);
69            bytes[versionLength+3]=(byte)('0'+(i%10));
70            bytes[versionLength+4]=' ';
71            for (int j=0;j<reason.length();j++)
72                bytes[versionLength+5+j]=(byte)reason.charAt(j);
73            bytes[versionLength+5+reason.length()]=HttpTokens.CARRIAGE_RETURN;
74            bytes[versionLength+6+reason.length()]=HttpTokens.LINE_FEED;
75
76            __status[i] = new Status();
77            __status[i]._reason=new ByteArrayBuffer(bytes,versionLength+5,bytes.length-versionLength-7,Buffer.IMMUTABLE);
78            __status[i]._schemeCode=new ByteArrayBuffer(bytes,0,versionLength+5,Buffer.IMMUTABLE);
79            __status[i]._responseLine=new ByteArrayBuffer(bytes,0,bytes.length,Buffer.IMMUTABLE);
80        }
81    }
82
83    /* ------------------------------------------------------------------------------- */
84    public static Buffer getReasonBuffer(int code)
85    {
86        Status status = code<__status.length?__status[code]:null;
87        if (status!=null)
88            return status._reason;
89        return null;
90    }
91
92
93    // common _content
94    private static final byte[] LAST_CHUNK =
95    { (byte) '0', (byte) '\015', (byte) '\012', (byte) '\015', (byte) '\012'};
96    private static final byte[] CONTENT_LENGTH_0 = StringUtil.getBytes("Content-Length: 0\015\012");
97    private static final byte[] CONNECTION_KEEP_ALIVE = StringUtil.getBytes("Connection: keep-alive\015\012");
98    private static final byte[] CONNECTION_CLOSE = StringUtil.getBytes("Connection: close\015\012");
99    private static final byte[] CONNECTION_ = StringUtil.getBytes("Connection: ");
100    private static final byte[] CRLF = StringUtil.getBytes("\015\012");
101    private static final byte[] TRANSFER_ENCODING_CHUNKED = StringUtil.getBytes("Transfer-Encoding: chunked\015\012");
102    private static byte[] SERVER = StringUtil.getBytes("Server: Jetty(7.0.x)\015\012");
103
104    // other statics
105    private static final int CHUNK_SPACE = 12;
106
107    public static void setServerVersion(String version)
108    {
109        SERVER=StringUtil.getBytes("Server: Jetty("+version+")\015\012");
110    }
111
112    // data
113    protected boolean _bypass = false; // True if _content buffer can be written directly to endp and bypass the content buffer
114    private boolean _needCRLF = false;
115    private boolean _needEOC = false;
116    private boolean _bufferChunked = false;
117
118
119    /* ------------------------------------------------------------------------------- */
120    /**
121     * Constructor.
122     *
123     * @param buffers buffer pool
124     * @param io the end point to use
125     */
126    public HttpGenerator(Buffers buffers, EndPoint io)
127    {
128        super(buffers,io);
129    }
130
131    /* ------------------------------------------------------------------------------- */
132    @Override
133    public void reset()
134    {
135        if (_persistent!=null && !_persistent && _endp!=null && !_endp.isOutputShutdown())
136        {
137            try
138            {
139                _endp.shutdownOutput();
140            }
141            catch(IOException e)
142            {
143                LOG.ignore(e);
144            }
145        }
146        super.reset();
147        if (_buffer!=null)
148            _buffer.clear();
149        if (_header!=null)
150            _header.clear();
151        if (_content!=null)
152            _content=null;
153        _bypass = false;
154        _needCRLF = false;
155        _needEOC = false;
156        _bufferChunked=false;
157        _method=null;
158        _uri=null;
159        _noContent=false;
160    }
161
162    /* ------------------------------------------------------------ */
163    /**
164     * Add content.
165     *
166     * @param content
167     * @param last
168     * @throws IllegalArgumentException if <code>content</code> is {@link Buffer#isImmutable immutable}.
169     * @throws IllegalStateException If the request is not expecting any more content,
170     *   or if the buffers are full and cannot be flushed.
171     * @throws IOException if there is a problem flushing the buffers.
172     */
173    public void addContent(Buffer content, boolean last) throws IOException
174    {
175        if (_noContent)
176            throw new IllegalStateException("NO CONTENT");
177
178        if (_last || _state==STATE_END)
179        {
180            LOG.warn("Ignoring extra content {}",content);
181            content.clear();
182            return;
183        }
184        _last = last;
185
186        // Handle any unfinished business?
187        if (_content!=null && _content.length()>0 || _bufferChunked)
188        {
189            if (_endp.isOutputShutdown())
190                throw new EofException();
191            flushBuffer();
192            if (_content != null && _content.length()>0)
193            {
194                if (_bufferChunked)
195                {
196                    Buffer nc=_buffers.getBuffer(_content.length()+CHUNK_SPACE+content.length());
197                    nc.put(_content);
198                    nc.put(HttpTokens.CRLF);
199                    BufferUtil.putHexInt(nc, content.length());
200                    nc.put(HttpTokens.CRLF);
201                    nc.put(content);
202                    content=nc;
203                }
204                else
205                {
206                    Buffer nc=_buffers.getBuffer(_content.length()+content.length());
207                    nc.put(_content);
208                    nc.put(content);
209                    content=nc;
210                }
211            }
212        }
213
214        _content = content;
215        _contentWritten += content.length();
216
217        // Handle the _content
218        if (_head)
219        {
220            content.clear();
221            _content=null;
222        }
223        else if (_endp != null && (_buffer==null || _buffer.length()==0) && _content.length() > 0 && (_last || isCommitted() && _content.length()>1024))
224        {
225            _bypass = true;
226        }
227        else if (!_bufferChunked)
228        {
229            // Yes - so we better check we have a buffer
230            if (_buffer == null)
231                _buffer = _buffers.getBuffer();
232
233            // Copy _content to buffer;
234            int len=_buffer.put(_content);
235            _content.skip(len);
236            if (_content.length() == 0)
237                _content = null;
238        }
239    }
240
241    /* ------------------------------------------------------------ */
242    /**
243     * send complete response.
244     *
245     * @param response
246     */
247    public void sendResponse(Buffer response) throws IOException
248    {
249        if (_noContent || _state!=STATE_HEADER || _content!=null && _content.length()>0 || _bufferChunked || _head )
250            throw new IllegalStateException();
251
252        _last = true;
253
254        _content = response;
255        _bypass = true;
256        _state = STATE_FLUSHING;
257
258        // TODO this is not exactly right, but should do.
259        _contentLength =_contentWritten = response.length();
260
261    }
262
263    /* ------------------------------------------------------------ */
264    /** Prepare buffer for unchecked writes.
265     * Prepare the generator buffer to receive unchecked writes
266     * @return the available space in the buffer.
267     * @throws IOException
268     */
269    @Override
270    public int prepareUncheckedAddContent() throws IOException
271    {
272        if (_noContent)
273            return -1;
274
275        if (_last || _state==STATE_END)
276            return -1;
277
278        // Handle any unfinished business?
279        Buffer content = _content;
280        if (content != null && content.length()>0 || _bufferChunked)
281        {
282            flushBuffer();
283            if (content != null && content.length()>0 || _bufferChunked)
284                throw new IllegalStateException("FULL");
285        }
286
287        // we better check we have a buffer
288        if (_buffer == null)
289            _buffer = _buffers.getBuffer();
290
291        _contentWritten-=_buffer.length();
292
293        // Handle the _content
294        if (_head)
295            return Integer.MAX_VALUE;
296
297        return _buffer.space()-(_contentLength == HttpTokens.CHUNKED_CONTENT?CHUNK_SPACE:0);
298    }
299
300    /* ------------------------------------------------------------ */
301    @Override
302    public boolean isBufferFull()
303    {
304        // Should we flush the buffers?
305        return super.isBufferFull() || _bufferChunked || _bypass  || (_contentLength == HttpTokens.CHUNKED_CONTENT && _buffer != null && _buffer.space() < CHUNK_SPACE);
306    }
307
308    /* ------------------------------------------------------------ */
309    public void send1xx(int code) throws IOException
310    {
311        if (_state != STATE_HEADER)
312            return;
313
314        if (code<100||code>199)
315            throw new IllegalArgumentException("!1xx");
316        Status status=__status[code];
317        if (status==null)
318            throw new IllegalArgumentException(code+"?");
319
320        // get a header buffer
321        if (_header == null)
322            _header = _buffers.getHeader();
323
324        _header.put(status._responseLine);
325        _header.put(HttpTokens.CRLF);
326
327        try
328        {
329            // nasty semi busy flush!
330            while(_header.length()>0)
331            {
332                int len = _endp.flush(_header);
333                if (len<0)
334                    throw new EofException();
335                if (len==0)
336                    Thread.sleep(100);
337            }
338        }
339        catch(InterruptedException e)
340        {
341            LOG.debug(e);
342            throw new InterruptedIOException(e.toString());
343        }
344    }
345
346    /* ------------------------------------------------------------ */
347    @Override
348    public boolean isRequest()
349    {
350        return _method!=null;
351    }
352
353    /* ------------------------------------------------------------ */
354    @Override
355    public boolean isResponse()
356    {
357        return _method==null;
358    }
359
360    /* ------------------------------------------------------------ */
361    @Override
362    public void completeHeader(HttpFields fields, boolean allContentAdded) throws IOException
363    {
364        if (_state != STATE_HEADER)
365            return;
366
367        // handle a reset
368        if (isResponse() && _status==0)
369            throw new EofException();
370
371        if (_last && !allContentAdded)
372            throw new IllegalStateException("last?");
373        _last = _last | allContentAdded;
374
375        // get a header buffer
376        if (_header == null)
377            _header = _buffers.getHeader();
378
379        boolean has_server = false;
380
381        try
382        {
383            if (isRequest())
384            {
385                _persistent=true;
386
387                if (_version == HttpVersions.HTTP_0_9_ORDINAL)
388                {
389                    _contentLength = HttpTokens.NO_CONTENT;
390                    _header.put(_method);
391                    _header.put((byte)' ');
392                    _header.put(_uri.getBytes("UTF-8")); // TODO check
393                    _header.put(HttpTokens.CRLF);
394                    _state = STATE_FLUSHING;
395                    _noContent=true;
396                    return;
397                }
398                else
399                {
400                    _header.put(_method);
401                    _header.put((byte)' ');
402                    _header.put(_uri.getBytes("UTF-8")); // TODO check
403                    _header.put((byte)' ');
404                    _header.put(_version==HttpVersions.HTTP_1_0_ORDINAL?HttpVersions.HTTP_1_0_BUFFER:HttpVersions.HTTP_1_1_BUFFER);
405                    _header.put(HttpTokens.CRLF);
406                }
407            }
408            else
409            {
410                // Responses
411                if (_version == HttpVersions.HTTP_0_9_ORDINAL)
412                {
413                    _persistent = false;
414                    _contentLength = HttpTokens.EOF_CONTENT;
415                    _state = STATE_CONTENT;
416                    return;
417                }
418                else
419                {
420                    if (_persistent==null)
421                        _persistent= (_version > HttpVersions.HTTP_1_0_ORDINAL);
422
423                    // add response line
424                    Status status = _status<__status.length?__status[_status]:null;
425
426                    if (status==null)
427                    {
428                        _header.put(HttpVersions.HTTP_1_1_BUFFER);
429                        _header.put((byte) ' ');
430                        _header.put((byte) ('0' + _status / 100));
431                        _header.put((byte) ('0' + (_status % 100) / 10));
432                        _header.put((byte) ('0' + (_status % 10)));
433                        _header.put((byte) ' ');
434                        if (_reason==null)
435                        {
436                            _header.put((byte) ('0' + _status / 100));
437                            _header.put((byte) ('0' + (_status % 100) / 10));
438                            _header.put((byte) ('0' + (_status % 10)));
439                        }
440                        else
441                            _header.put(_reason);
442                        _header.put(HttpTokens.CRLF);
443                    }
444                    else
445                    {
446                        if (_reason==null)
447                            _header.put(status._responseLine);
448                        else
449                        {
450                            _header.put(status._schemeCode);
451                            _header.put(_reason);
452                            _header.put(HttpTokens.CRLF);
453                        }
454                    }
455
456                    if (_status<200 && _status>=100 )
457                    {
458                        _noContent=true;
459                        _content=null;
460                        if (_buffer!=null)
461                            _buffer.clear();
462                        // end the header.
463
464                        if (_status!=101 )
465                        {
466                            _header.put(HttpTokens.CRLF);
467                            _state = STATE_CONTENT;
468                            return;
469                        }
470                    }
471                    else if (_status==204 || _status==304)
472                    {
473                        _noContent=true;
474                        _content=null;
475                        if (_buffer!=null)
476                            _buffer.clear();
477                    }
478                }
479            }
480
481            // Add headers
482            if (_status>=200 && _date!=null)
483            {
484                _header.put(HttpHeaders.DATE_BUFFER);
485                _header.put((byte)':');
486                _header.put((byte)' ');
487                _header.put(_date);
488                _header.put(CRLF);
489            }
490
491            // key field values
492            HttpFields.Field content_length = null;
493            HttpFields.Field transfer_encoding = null;
494            boolean keep_alive = false;
495            boolean close=false;
496            boolean content_type=false;
497            StringBuilder connection = null;
498
499            if (fields != null)
500            {
501                int s=fields.size();
502                for (int f=0;f<s;f++)
503                {
504                    HttpFields.Field field = fields.getField(f);
505                    if (field==null)
506                        continue;
507
508                    switch (field.getNameOrdinal())
509                    {
510                        case HttpHeaders.CONTENT_LENGTH_ORDINAL:
511                            content_length = field;
512                            _contentLength = field.getLongValue();
513
514                            if (_contentLength < _contentWritten || _last && _contentLength != _contentWritten)
515                                content_length = null;
516
517                            // write the field to the header buffer
518                            field.putTo(_header);
519                            break;
520
521                        case HttpHeaders.CONTENT_TYPE_ORDINAL:
522                            if (BufferUtil.isPrefix(MimeTypes.MULTIPART_BYTERANGES_BUFFER, field.getValueBuffer())) _contentLength = HttpTokens.SELF_DEFINING_CONTENT;
523
524                            // write the field to the header buffer
525                            content_type=true;
526                            field.putTo(_header);
527                            break;
528
529                        case HttpHeaders.TRANSFER_ENCODING_ORDINAL:
530                            if (_version == HttpVersions.HTTP_1_1_ORDINAL)
531                                transfer_encoding = field;
532                            // Do NOT add yet!
533                            break;
534
535                        case HttpHeaders.CONNECTION_ORDINAL:
536                            if (isRequest())
537                                field.putTo(_header);
538
539                            int connection_value = field.getValueOrdinal();
540                            switch (connection_value)
541                            {
542                                case -1:
543                                {
544                                    String[] values = field.getValue().split(",");
545                                    for  (int i=0;values!=null && i<values.length;i++)
546                                    {
547                                        CachedBuffer cb = HttpHeaderValues.CACHE.get(values[i].trim());
548
549                                        if (cb!=null)
550                                        {
551                                            switch(cb.getOrdinal())
552                                            {
553                                                case HttpHeaderValues.CLOSE_ORDINAL:
554                                                    close=true;
555                                                    if (isResponse())
556                                                        _persistent=false;
557                                                    keep_alive=false;
558                                                    if (!_persistent && isResponse() && _contentLength == HttpTokens.UNKNOWN_CONTENT)
559                                                        _contentLength = HttpTokens.EOF_CONTENT;
560                                                    break;
561
562                                                case HttpHeaderValues.KEEP_ALIVE_ORDINAL:
563                                                    if (_version == HttpVersions.HTTP_1_0_ORDINAL)
564                                                    {
565                                                        keep_alive = true;
566                                                        if (isResponse())
567                                                            _persistent = true;
568                                                    }
569                                                    break;
570
571                                                default:
572                                                    if (connection==null)
573                                                        connection=new StringBuilder();
574                                                    else
575                                                        connection.append(',');
576                                                    connection.append(values[i]);
577                                            }
578                                        }
579                                        else
580                                        {
581                                            if (connection==null)
582                                                connection=new StringBuilder();
583                                            else
584                                                connection.append(',');
585                                            connection.append(values[i]);
586                                        }
587                                    }
588
589                                    break;
590                                }
591                                case HttpHeaderValues.UPGRADE_ORDINAL:
592                                {
593                                    // special case for websocket connection ordering
594                                    if (isResponse())
595                                    {
596                                        field.putTo(_header);
597                                        continue;
598                                    }
599                                }
600                                case HttpHeaderValues.CLOSE_ORDINAL:
601                                {
602                                    close=true;
603                                    if (isResponse())
604                                        _persistent=false;
605                                    if (!_persistent && isResponse() && _contentLength == HttpTokens.UNKNOWN_CONTENT)
606                                        _contentLength = HttpTokens.EOF_CONTENT;
607                                    break;
608                                }
609                                case HttpHeaderValues.KEEP_ALIVE_ORDINAL:
610                                {
611                                    if (_version == HttpVersions.HTTP_1_0_ORDINAL)
612                                    {
613                                        keep_alive = true;
614                                        if (isResponse())
615                                            _persistent=true;
616                                    }
617                                    break;
618                                }
619                                default:
620                                {
621                                    if (connection==null)
622                                        connection=new StringBuilder();
623                                    else
624                                        connection.append(',');
625                                    connection.append(field.getValue());
626                                }
627                            }
628
629                            // Do NOT add yet!
630                            break;
631
632                        case HttpHeaders.SERVER_ORDINAL:
633                            if (getSendServerVersion())
634                            {
635                                has_server=true;
636                                field.putTo(_header);
637                            }
638                            break;
639
640                        default:
641                            // write the field to the header buffer
642                            field.putTo(_header);
643                    }
644                }
645            }
646
647            // Calculate how to end _content and connection, _content length and transfer encoding
648            // settings.
649            // From RFC 2616 4.4:
650            // 1. No body for 1xx, 204, 304 & HEAD response
651            // 2. Force _content-length?
652            // 3. If Transfer-Encoding!=identity && HTTP/1.1 && !HttpConnection==close then chunk
653            // 4. Content-Length
654            // 5. multipart/byteranges
655            // 6. close
656            switch ((int) _contentLength)
657            {
658                case HttpTokens.UNKNOWN_CONTENT:
659                    // It may be that we have no _content, or perhaps _content just has not been
660                    // written yet?
661
662                    // Response known not to have a body
663                    if (_contentWritten == 0 && isResponse() && (_status < 200 || _status == 204 || _status == 304))
664                        _contentLength = HttpTokens.NO_CONTENT;
665                    else if (_last)
666                    {
667                        // we have seen all the _content there is
668                        _contentLength = _contentWritten;
669                        if (content_length == null && (isResponse() || _contentLength>0 || content_type ) && !_noContent)
670                        {
671                            // known length but not actually set.
672                            _header.put(HttpHeaders.CONTENT_LENGTH_BUFFER);
673                            _header.put(HttpTokens.COLON);
674                            _header.put((byte) ' ');
675                            BufferUtil.putDecLong(_header, _contentLength);
676                            _header.put(HttpTokens.CRLF);
677                        }
678                    }
679                    else
680                    {
681                        // No idea, so we must assume that a body is coming
682                        _contentLength = (!_persistent || _version < HttpVersions.HTTP_1_1_ORDINAL ) ? HttpTokens.EOF_CONTENT : HttpTokens.CHUNKED_CONTENT;
683                        if (isRequest() && _contentLength==HttpTokens.EOF_CONTENT)
684                        {
685                            _contentLength=HttpTokens.NO_CONTENT;
686                            _noContent=true;
687                        }
688                    }
689                    break;
690
691                case HttpTokens.NO_CONTENT:
692                    if (content_length == null && isResponse() && _status >= 200 && _status != 204 && _status != 304)
693                        _header.put(CONTENT_LENGTH_0);
694                    break;
695
696                case HttpTokens.EOF_CONTENT:
697                    _persistent = isRequest();
698                    break;
699
700                case HttpTokens.CHUNKED_CONTENT:
701                    break;
702
703                default:
704                    // TODO - maybe allow forced chunking by setting te ???
705                    break;
706            }
707
708            // Add transfer_encoding if needed
709            if (_contentLength == HttpTokens.CHUNKED_CONTENT)
710            {
711                // try to use user supplied encoding as it may have other values.
712                if (transfer_encoding != null && HttpHeaderValues.CHUNKED_ORDINAL != transfer_encoding.getValueOrdinal())
713                {
714                    String c = transfer_encoding.getValue();
715                    if (c.endsWith(HttpHeaderValues.CHUNKED))
716                        transfer_encoding.putTo(_header);
717                    else
718                        throw new IllegalArgumentException("BAD TE");
719                }
720                else
721                    _header.put(TRANSFER_ENCODING_CHUNKED);
722            }
723
724            // Handle connection if need be
725            if (_contentLength==HttpTokens.EOF_CONTENT)
726            {
727                keep_alive=false;
728                _persistent=false;
729            }
730
731            if (isResponse())
732            {
733                if (!_persistent && (close || _version > HttpVersions.HTTP_1_0_ORDINAL))
734                {
735                    _header.put(CONNECTION_CLOSE);
736                    if (connection!=null)
737                    {
738                        _header.setPutIndex(_header.putIndex()-2);
739                        _header.put((byte)',');
740                        _header.put(connection.toString().getBytes());
741                        _header.put(CRLF);
742                    }
743                }
744                else if (keep_alive)
745                {
746                    _header.put(CONNECTION_KEEP_ALIVE);
747                    if (connection!=null)
748                    {
749                        _header.setPutIndex(_header.putIndex()-2);
750                        _header.put((byte)',');
751                        _header.put(connection.toString().getBytes());
752                        _header.put(CRLF);
753                    }
754                }
755                else if (connection!=null)
756                {
757                    _header.put(CONNECTION_);
758                    _header.put(connection.toString().getBytes());
759                    _header.put(CRLF);
760                }
761            }
762
763            if (!has_server && _status>199 && getSendServerVersion())
764                _header.put(SERVER);
765
766            // end the header.
767            _header.put(HttpTokens.CRLF);
768            _state = STATE_CONTENT;
769
770        }
771        catch(ArrayIndexOutOfBoundsException e)
772        {
773            throw new RuntimeException("Header>"+_header.capacity(),e);
774        }
775    }
776
777    /* ------------------------------------------------------------ */
778    /**
779     * Complete the message.
780     *
781     * @throws IOException
782     */
783    @Override
784    public void complete() throws IOException
785    {
786        if (_state == STATE_END)
787            return;
788
789        super.complete();
790
791        if (_state < STATE_FLUSHING)
792        {
793            _state = STATE_FLUSHING;
794            if (_contentLength == HttpTokens.CHUNKED_CONTENT)
795                _needEOC = true;
796        }
797
798        flushBuffer();
799    }
800
801    /* ------------------------------------------------------------ */
802    @Override
803    public int flushBuffer() throws IOException
804    {
805        try
806        {
807
808            if (_state == STATE_HEADER)
809                throw new IllegalStateException("State==HEADER");
810
811            prepareBuffers();
812
813            if (_endp == null)
814            {
815                if (_needCRLF && _buffer!=null)
816                    _buffer.put(HttpTokens.CRLF);
817                if (_needEOC && _buffer!=null && !_head)
818                    _buffer.put(LAST_CHUNK);
819                _needCRLF=false;
820                _needEOC=false;
821                return 0;
822            }
823
824            int total= 0;
825
826            int len = -1;
827            int to_flush = flushMask();
828            int last_flush;
829
830            do
831            {
832                last_flush=to_flush;
833                switch (to_flush)
834                {
835                    case 7:
836                        throw new IllegalStateException(); // should never happen!
837                    case 6:
838                        len = _endp.flush(_header, _buffer, null);
839                        break;
840                    case 5:
841                        len = _endp.flush(_header, _content, null);
842                        break;
843                    case 4:
844                        len = _endp.flush(_header);
845                        break;
846                    case 3:
847                        len = _endp.flush(_buffer, _content, null);
848                        break;
849                    case 2:
850                        len = _endp.flush(_buffer);
851                        break;
852                    case 1:
853                        len = _endp.flush(_content);
854                        break;
855                    case 0:
856                    {
857                        len=0;
858                        // Nothing more we can write now.
859                        if (_header != null)
860                            _header.clear();
861
862                        _bypass = false;
863                        _bufferChunked = false;
864
865                        if (_buffer != null)
866                        {
867                            _buffer.clear();
868                            if (_contentLength == HttpTokens.CHUNKED_CONTENT)
869                            {
870                                // reserve some space for the chunk header
871                                _buffer.setPutIndex(CHUNK_SPACE);
872                                _buffer.setGetIndex(CHUNK_SPACE);
873
874                                // Special case handling for small left over buffer from
875                                // an addContent that caused a buffer flush.
876                                if (_content != null && _content.length() < _buffer.space() && _state != STATE_FLUSHING)
877                                {
878                                    _buffer.put(_content);
879                                    _content.clear();
880                                    _content=null;
881                                }
882                            }
883                        }
884
885                        // Are we completely finished for now?
886                        if (!_needCRLF && !_needEOC && (_content==null || _content.length()==0))
887                        {
888                            if (_state == STATE_FLUSHING)
889                                _state = STATE_END;
890
891                            if (_state==STATE_END && _persistent != null && !_persistent && _status!=100 && _method==null)
892                                _endp.shutdownOutput();
893                        }
894                        else
895                            // Try to prepare more to write.
896                            prepareBuffers();
897                    }
898
899                }
900
901                if (len > 0)
902                    total+=len;
903
904                to_flush = flushMask();
905            }
906            // loop while progress is being made (OR we have prepared some buffers that might make progress)
907            while (len>0 || (to_flush!=0 && last_flush==0));
908
909            return total;
910        }
911        catch (IOException e)
912        {
913            LOG.ignore(e);
914            throw (e instanceof EofException) ? e:new EofException(e);
915        }
916    }
917
918    /* ------------------------------------------------------------ */
919    private int flushMask()
920    {
921        return  ((_header != null && _header.length() > 0)?4:0)
922        | ((_buffer != null && _buffer.length() > 0)?2:0)
923        | ((_bypass && _content != null && _content.length() > 0)?1:0);
924    }
925
926    /* ------------------------------------------------------------ */
927    private void prepareBuffers()
928    {
929        // if we are not flushing an existing chunk
930        if (!_bufferChunked)
931        {
932            // Refill buffer if possible
933            if (!_bypass && _content != null && _content.length() > 0 && _buffer != null && _buffer.space() > 0)
934            {
935                int len = _buffer.put(_content);
936                _content.skip(len);
937                if (_content.length() == 0)
938                    _content = null;
939            }
940
941            // Chunk buffer if need be
942            if (_contentLength == HttpTokens.CHUNKED_CONTENT)
943            {
944                if (_bypass && (_buffer==null||_buffer.length()==0) && _content!=null)
945                {
946                    // this is a bypass write
947                    int size = _content.length();
948                    _bufferChunked = true;
949
950                    if (_header == null)
951                        _header = _buffers.getHeader();
952
953                    // if we need CRLF add this to header
954                    if (_needCRLF)
955                    {
956                        if (_header.length() > 0) throw new IllegalStateException("EOC");
957                        _header.put(HttpTokens.CRLF);
958                        _needCRLF = false;
959                    }
960                    // Add the chunk size to the header
961                    BufferUtil.putHexInt(_header, size);
962                    _header.put(HttpTokens.CRLF);
963
964                    // Need a CRLF after the content
965                    _needCRLF=true;
966                }
967                else if (_buffer!=null)
968                {
969                    int size = _buffer.length();
970                    if (size > 0)
971                    {
972                        // Prepare a chunk!
973                        _bufferChunked = true;
974
975                        // Did we leave space at the start of the buffer.
976                        //noinspection ConstantConditions
977                        if (_buffer.getIndex() == CHUNK_SPACE)
978                        {
979                            // Oh yes, goodie! let's use it then!
980                            _buffer.poke(_buffer.getIndex() - 2, HttpTokens.CRLF, 0, 2);
981                            _buffer.setGetIndex(_buffer.getIndex() - 2);
982                            BufferUtil.prependHexInt(_buffer, size);
983
984                            if (_needCRLF)
985                            {
986                                _buffer.poke(_buffer.getIndex() - 2, HttpTokens.CRLF, 0, 2);
987                                _buffer.setGetIndex(_buffer.getIndex() - 2);
988                                _needCRLF = false;
989                            }
990                        }
991                        else
992                        {
993                            // No space so lets use a header buffer.
994                            if (_header == null)
995                                _header = _buffers.getHeader();
996
997                            if (_needCRLF)
998                            {
999                                if (_header.length() > 0) throw new IllegalStateException("EOC");
1000                                _header.put(HttpTokens.CRLF);
1001                                _needCRLF = false;
1002                            }
1003                            BufferUtil.putHexInt(_header, size);
1004                            _header.put(HttpTokens.CRLF);
1005                        }
1006
1007                        // Add end chunk trailer.
1008                        if (_buffer.space() >= 2)
1009                            _buffer.put(HttpTokens.CRLF);
1010                        else
1011                            _needCRLF = true;
1012                    }
1013                }
1014
1015                // If we need EOC and everything written
1016                if (_needEOC && (_content == null || _content.length() == 0))
1017                {
1018                    if (_header == null && _buffer == null)
1019                        _header = _buffers.getHeader();
1020
1021                    if (_needCRLF)
1022                    {
1023                        if (_buffer == null && _header != null && _header.space() >= HttpTokens.CRLF.length)
1024                        {
1025                            _header.put(HttpTokens.CRLF);
1026                            _needCRLF = false;
1027                        }
1028                        else if (_buffer!=null && _buffer.space() >= HttpTokens.CRLF.length)
1029                        {
1030                            _buffer.put(HttpTokens.CRLF);
1031                            _needCRLF = false;
1032                        }
1033                    }
1034
1035                    if (!_needCRLF && _needEOC)
1036                    {
1037                        if (_buffer == null && _header != null && _header.space() >= LAST_CHUNK.length)
1038                        {
1039                            if (!_head)
1040                            {
1041                                _header.put(LAST_CHUNK);
1042                                _bufferChunked=true;
1043                            }
1044                            _needEOC = false;
1045                        }
1046                        else if (_buffer!=null && _buffer.space() >= LAST_CHUNK.length)
1047                        {
1048                            if (!_head)
1049                            {
1050                                _buffer.put(LAST_CHUNK);
1051                                _bufferChunked=true;
1052                            }
1053                            _needEOC = false;
1054                        }
1055                    }
1056                }
1057            }
1058        }
1059
1060        if (_content != null && _content.length() == 0)
1061            _content = null;
1062
1063    }
1064
1065    public int getBytesBuffered()
1066    {
1067        return(_header==null?0:_header.length())+
1068        (_buffer==null?0:_buffer.length())+
1069        (_content==null?0:_content.length());
1070    }
1071
1072    public boolean isEmpty()
1073    {
1074        return (_header==null||_header.length()==0) &&
1075        (_buffer==null||_buffer.length()==0) &&
1076        (_content==null||_content.length()==0);
1077    }
1078
1079    @Override
1080    public String toString()
1081    {
1082        Buffer header=_header;
1083        Buffer buffer=_buffer;
1084        Buffer content=_content;
1085        return String.format("%s{s=%d,h=%d,b=%d,c=%d}",
1086                getClass().getSimpleName(),
1087                _state,
1088                header == null ? -1 : header.length(),
1089                buffer == null ? -1 : buffer.length(),
1090                content == null ? -1 : content.length());
1091    }
1092}
1093