View Javadoc

1   //
2   //  ========================================================================
3   //  Copyright (c) 1995-2013 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  
19  package org.eclipse.jetty.server;
20  
21  import java.io.EOFException;
22  import java.io.IOException;
23  import java.io.InputStream;
24  import java.nio.ByteBuffer;
25  import java.nio.channels.ReadableByteChannel;
26  import javax.servlet.RequestDispatcher;
27  import javax.servlet.ServletOutputStream;
28  import javax.servlet.ServletRequest;
29  import javax.servlet.ServletResponse;
30  
31  import org.eclipse.jetty.http.HttpContent;
32  import org.eclipse.jetty.http.HttpHeader;
33  import org.eclipse.jetty.util.BufferUtil;
34  import org.eclipse.jetty.util.log.Log;
35  import org.eclipse.jetty.util.log.Logger;
36  import org.eclipse.jetty.util.resource.Resource;
37  
38  /**
39   * <p>{@link HttpOutput} implements {@link ServletOutputStream}
40   * as required by the Servlet specification.</p>
41   * <p>{@link HttpOutput} buffers content written by the application until a
42   * further write will overflow the buffer, at which point it triggers a commit
43   * of the response.</p>
44   * <p>{@link HttpOutput} can be closed and reopened, to allow requests included
45   * via {@link RequestDispatcher#include(ServletRequest, ServletResponse)} to
46   * close the stream, to be reopened after the inclusion ends.</p>
47   */
48  public class HttpOutput extends ServletOutputStream
49  {
50      private static final boolean OUTPUT_BUFFER_DIRECT=false;
51      private static final boolean CHANNEL_BUFFER_DIRECT=true;
52      private static final boolean STREAM_BUFFER_DIRECT=false;
53      private static Logger LOG = Log.getLogger(HttpOutput.class);
54      private final HttpChannel<?> _channel;
55      private boolean _closed;
56      private long _written;
57      private ByteBuffer _aggregate;
58      private int _bufferSize;
59  
60      public HttpOutput(HttpChannel<?> channel)
61      {
62          _channel = channel;
63          _bufferSize = _channel.getHttpConfiguration().getOutputBufferSize();
64      }
65  
66      public boolean isWritten()
67      {
68          return _written > 0;
69      }
70  
71      public long getWritten()
72      {
73          return _written;
74      }
75  
76      public void reset()
77      {
78          _written = 0;
79          reopen();
80      }
81  
82      public void reopen()
83      {
84          _closed = false;
85      }
86  
87      /** Called by the HttpChannel if the output was closed
88       * externally (eg by a 500 exception handling).
89       */
90      void closed()
91      {
92          _closed = true;
93          releaseBuffer();
94      }
95  
96      @Override
97      public void close()
98      {
99          if (!isClosed())
100         {
101             try
102             {
103                 if (BufferUtil.hasContent(_aggregate))
104                     _channel.write(_aggregate, !_channel.getResponse().isIncluding());
105                 else
106                     _channel.write(BufferUtil.EMPTY_BUFFER, !_channel.getResponse().isIncluding());
107             }
108             catch(IOException e)
109             {
110                 _channel.getEndPoint().shutdownOutput();
111                 LOG.ignore(e);
112             }
113         }
114         closed();
115     }
116 
117     private void releaseBuffer()
118     {
119         if (_aggregate != null)
120         {
121             _channel.getConnector().getByteBufferPool().release(_aggregate);
122             _aggregate = null;
123         }
124     }
125 
126     public boolean isClosed()
127     {
128         return _closed;
129     }
130 
131     @Override
132     public void flush() throws IOException
133     {
134         if (isClosed())
135             return;
136 
137         if (BufferUtil.hasContent(_aggregate))
138             _channel.write(_aggregate, false);
139         else
140             _channel.write(BufferUtil.EMPTY_BUFFER, false);
141     }
142 
143     public boolean closeIfAllContentWritten() throws IOException
144     {
145         return _channel.getResponse().closeIfAllContentWritten(_written);
146     }
147 
148     @Override
149     public void write(byte[] b, int off, int len) throws IOException
150     {
151         if (isClosed())
152             throw new EOFException("Closed");
153 
154         // Do we have an aggregate buffer already ?
155         if (_aggregate == null)
156         {
157             // What size should the aggregate be ?
158             int size = getBufferSize();
159 
160             // If this write would fill more than half the aggregate, just write it directly
161             if (len > size / 2)
162             {
163                 _channel.write(ByteBuffer.wrap(b, off, len), false);
164                 _written += len;
165                 return;
166             }
167 
168             // Allocate an aggregate buffer
169             _aggregate = _channel.getByteBufferPool().acquire(size, OUTPUT_BUFFER_DIRECT);
170         }
171 
172         // Do we have space to aggregate ?
173         int space = BufferUtil.space(_aggregate);
174         if (len > space)
175         {
176             // No space so write the aggregate out if it is not empty
177             if (BufferUtil.hasContent(_aggregate))
178             {
179                 _channel.write(_aggregate, false);
180                 space = BufferUtil.space(_aggregate);
181             }
182         }
183 
184         // Do we have space to aggregate now ?
185         if (len > space)
186         {
187             // No space so write the content directly
188             _channel.write(ByteBuffer.wrap(b, off, len), false);
189             _written += len;
190             return;
191         }
192 
193         // Aggregate the content
194         BufferUtil.append(_aggregate, b, off, len);
195         _written += len;
196 
197         // Check if all written or full
198         if (!closeIfAllContentWritten() && BufferUtil.isFull(_aggregate))
199             _channel.write(_aggregate, false);
200     }
201 
202 
203     @Override
204     public void write(int b) throws IOException
205     {
206         if (isClosed())
207             throw new EOFException("Closed");
208 
209         if (_aggregate == null)
210             _aggregate = _channel.getByteBufferPool().acquire(getBufferSize(), OUTPUT_BUFFER_DIRECT);
211 
212         BufferUtil.append(_aggregate, (byte)b);
213         _written++;
214 
215         // Check if all written or full
216         if (!closeIfAllContentWritten() && BufferUtil.isFull(_aggregate))
217             _channel.write(_aggregate, false);
218     }
219 
220     @Override
221     public void print(String s) throws IOException
222     {
223         if (isClosed())
224             throw new IOException("Closed");
225 
226         write(s.getBytes(_channel.getResponse().getCharacterEncoding()));
227     }
228 
229     public void sendContent(Object content) throws IOException
230     {
231         if (isClosed())
232             throw new IOException("Closed");
233 
234         if (content instanceof HttpContent)
235         {
236             HttpContent httpContent = (HttpContent)content;
237             Response response = _channel.getResponse();
238             String contentType = httpContent.getContentType();
239             if (contentType != null && !response.getHttpFields().containsKey(HttpHeader.CONTENT_TYPE.asString()))
240                 response.getHttpFields().put(HttpHeader.CONTENT_TYPE, contentType);
241 
242             if (httpContent.getContentLength() > 0)
243                 response.getHttpFields().putLongField(HttpHeader.CONTENT_LENGTH, httpContent.getContentLength());
244 
245             String lm = httpContent.getLastModified();
246             if (lm != null)
247                 response.getHttpFields().put(HttpHeader.LAST_MODIFIED, lm);
248             else if (httpContent.getResource() != null)
249             {
250                 long lml = httpContent.getResource().lastModified();
251                 if (lml != -1)
252                     response.getHttpFields().putDateField(HttpHeader.LAST_MODIFIED, lml);
253             }
254 
255             String etag=httpContent.getETag();
256             if (etag!=null)
257                 response.getHttpFields().put(HttpHeader.ETAG,etag);
258 
259             content = httpContent.getDirectBuffer();
260             if (content == null)
261                 content = httpContent.getIndirectBuffer();
262             if (content == null)
263                 content = httpContent.getReadableByteChannel();
264             if (content == null)
265                 content = httpContent.getInputStream();
266         }
267         else if (content instanceof Resource)
268         {
269             Resource resource = (Resource)content;
270             _channel.getResponse().getHttpFields().putDateField(HttpHeader.LAST_MODIFIED, resource.lastModified());
271             content = resource.getInputStream();
272         }
273 
274         // Process content.
275         if (content instanceof ByteBuffer)
276         {
277             _channel.write((ByteBuffer)content, true);
278             _closed=true;
279         }
280         else if (content instanceof ReadableByteChannel)
281         {
282             ReadableByteChannel channel = (ReadableByteChannel)content;
283             ByteBuffer buffer = _channel.getByteBufferPool().acquire(getBufferSize(), CHANNEL_BUFFER_DIRECT);
284             try
285             {
286                 while(channel.isOpen())
287                 {
288                     int pos = BufferUtil.flipToFill(buffer);
289                     int len=channel.read(buffer);
290                     if (len<0)
291                         break;
292                     BufferUtil.flipToFlush(buffer,pos);
293                     _channel.write(buffer,false);
294                 }
295             }
296             finally
297             {
298                 close();
299                 _channel.getByteBufferPool().release(buffer);
300             }
301         }
302         else if (content instanceof InputStream)
303         {
304             InputStream in = (InputStream)content;
305             ByteBuffer buffer = _channel.getByteBufferPool().acquire(getBufferSize(), STREAM_BUFFER_DIRECT);
306             byte[] array = buffer.array();
307             int offset=buffer.arrayOffset();
308             int size=array.length-offset;
309             try
310             {
311                 while(true)
312                 {
313                     int len=in.read(array,offset,size);
314                     if (len<0)
315                         break;
316                     buffer.position(0);
317                     buffer.limit(len);
318                     _channel.write(buffer,false);
319                 }
320             }
321             finally
322             {
323                 close();
324                 _channel.getByteBufferPool().release(buffer);
325             }
326         }
327         else
328             throw new IllegalArgumentException("unknown content type "+content.getClass());
329     }
330 
331     public int getBufferSize()
332     {
333         return _bufferSize;
334     }
335 
336     public void setBufferSize(int size)
337     {
338         this._bufferSize = size;
339     }
340 
341     public void resetBuffer()
342     {
343         if (BufferUtil.hasContent(_aggregate))
344             BufferUtil.clear(_aggregate);
345     }
346 }