View Javadoc

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  
19  package org.eclipse.jetty.websocket.common.message;
20  
21  import java.io.IOException;
22  import java.io.OutputStream;
23  import java.nio.ByteBuffer;
24  
25  import org.eclipse.jetty.io.ByteBufferPool;
26  import org.eclipse.jetty.util.BufferUtil;
27  import org.eclipse.jetty.util.log.Log;
28  import org.eclipse.jetty.util.log.Logger;
29  import org.eclipse.jetty.websocket.api.WriteCallback;
30  import org.eclipse.jetty.websocket.api.extensions.OutgoingFrames;
31  import org.eclipse.jetty.websocket.common.BlockingWriteCallback;
32  import org.eclipse.jetty.websocket.common.WebSocketSession;
33  import org.eclipse.jetty.websocket.common.frames.BinaryFrame;
34  
35  /**
36   * Support for writing a single WebSocket BINARY message via a {@link OutputStream}
37   */
38  public class MessageOutputStream extends OutputStream
39  {
40      private static final Logger LOG = Log.getLogger(MessageOutputStream.class);
41      private final OutgoingFrames outgoing;
42      private final ByteBufferPool bufferPool;
43      private final BlockingWriteCallback blocker;
44      private long frameCount = 0;
45      private BinaryFrame frame;
46      private ByteBuffer buffer;
47      private WriteCallback callback;
48      private boolean closed = false;
49  
50      public MessageOutputStream(OutgoingFrames outgoing, int bufferSize, ByteBufferPool bufferPool)
51      {
52          this.outgoing = outgoing;
53          this.bufferPool = bufferPool;
54          this.blocker = new BlockingWriteCallback();
55          this.buffer = bufferPool.acquire(bufferSize,true);
56          BufferUtil.flipToFill(buffer);
57          this.frame = new BinaryFrame();
58      }
59  
60      public MessageOutputStream(WebSocketSession session)
61      {
62          this(session.getOutgoingHandler(),session.getPolicy().getMaxBinaryMessageBufferSize(),session.getBufferPool());
63      }
64  
65      private void assertNotClosed() throws IOException
66      {
67          if (closed)
68          {
69              IOException e = new IOException("Stream is closed");
70              notifyFailure(e);
71              throw e;
72          }
73      }
74  
75      @Override
76      public synchronized void close() throws IOException
77      {
78          assertNotClosed();
79          LOG.debug("close()");
80  
81          // finish sending whatever in the buffer with FIN=true
82          flush(true);
83  
84          // close stream
85          LOG.debug("Sent Frame Count: {}",frameCount);
86          closed = true;
87          try
88          {
89              if (callback != null)
90              {
91                  callback.writeSuccess();
92              }
93              super.close();
94              bufferPool.release(buffer);
95              LOG.debug("closed");
96          }
97          catch (IOException e)
98          {
99              notifyFailure(e);
100             throw e;
101         }
102     }
103 
104     @Override
105     public synchronized void flush() throws IOException
106     {
107         LOG.debug("flush()");
108         assertNotClosed();
109 
110         // flush whatever is in the buffer with FIN=false
111         flush(false);
112         try
113         {
114             super.flush();
115             LOG.debug("flushed");
116         }
117         catch (IOException e)
118         {
119             notifyFailure(e);
120             throw e;
121         }
122     }
123 
124     /**
125      * Flush whatever is in the buffer.
126      * 
127      * @param fin
128      *            fin flag
129      * @throws IOException
130      */
131     private synchronized void flush(boolean fin) throws IOException
132     {
133         BufferUtil.flipToFlush(buffer,0);
134         LOG.debug("flush({}): {}",fin,BufferUtil.toDetailString(buffer));
135         frame.setPayload(buffer);
136         frame.setFin(fin);
137 
138         try
139         {
140             outgoing.outgoingFrame(frame,blocker);
141             // block on write
142             blocker.block();
143             // block success
144             frameCount++;
145             frame.setIsContinuation();
146             BufferUtil.flipToFill(buffer);
147         }
148         catch (IOException e)
149         {
150             notifyFailure(e);
151             throw e;
152         }
153     }
154 
155     private void notifyFailure(IOException e)
156     {
157         if (callback != null)
158         {
159             callback.writeFailed(e);
160         }
161     }
162 
163     public void setCallback(WriteCallback callback)
164     {
165         this.callback = callback;
166     }
167 
168     @Override
169     public synchronized void write(byte[] b) throws IOException
170     {
171         try
172         {
173             this.write(b,0,b.length);
174         }
175         catch (IOException e)
176         {
177             notifyFailure(e);
178             throw e;
179         }
180     }
181 
182     @Override
183     public synchronized void write(byte[] b, int off, int len) throws IOException
184     {
185         LOG.debug("write(byte[{}], {}, {})",b.length,off,len);
186         int left = len; // bytes left to write
187         int offset = off; // offset within provided array
188         while (left > 0)
189         {
190             if (LOG.isDebugEnabled())
191             {
192                 LOG.debug("buffer: {}",BufferUtil.toDetailString(buffer));
193             }
194             int space = buffer.remaining();
195             assert (space > 0);
196             int size = Math.min(space,left);
197             buffer.put(b,offset,size);
198             assert (size > 0);
199             left -= size; // decrement bytes left
200             if (left > 0)
201             {
202                 flush(false);
203             }
204             offset += size; // increment offset
205         }
206     }
207 
208     @Override
209     public synchronized void write(int b) throws IOException
210     {
211         assertNotClosed();
212 
213         // buffer up to limit, flush once buffer reached.
214         buffer.put((byte)b);
215         if (buffer.remaining() <= 0)
216         {
217             flush(false);
218         }
219     }
220 }