View Javadoc

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