View | Details | Raw Unified | Return to bug 52918
Collapse All | Expand All

(-)java/org/apache/coyote/http11/Http11Processor.java (+14 lines)
Lines 25-30 Link Here
25
import java.util.regex.Pattern;
25
import java.util.regex.Pattern;
26
import java.util.regex.PatternSyntaxException;
26
import java.util.regex.PatternSyntaxException;
27
27
28
import javax.servlet.http.HttpServletResponse;
29
28
import org.apache.coyote.ActionCode;
30
import org.apache.coyote.ActionCode;
29
import org.apache.coyote.ActionHook;
31
import org.apache.coyote.ActionHook;
30
import org.apache.coyote.Adapter;
32
import org.apache.coyote.Adapter;
Lines 1135-1140 Link Here
1135
            InternalInputBuffer internalBuffer = (InternalInputBuffer)
1137
            InternalInputBuffer internalBuffer = (InternalInputBuffer)
1136
                request.getInputBuffer();
1138
                request.getInputBuffer();
1137
            internalBuffer.addActiveFilter(savedBody);
1139
            internalBuffer.addActiveFilter(savedBody);
1140
        } else if (actionCode == ActionCode.ACTION_PROTOCOL_SWITCH) {
1141
            inputBuffer.lastActiveFilter = -1;
1142
            outputBuffer.lastActiveFilter = -1;
1143
            //this is to trick, since it's BIO, let them discover data as they read
1144
            request.setAvailable(1);
1138
        }
1145
        }
1139
1146
1140
    }
1147
    }
Lines 1569-1574 Link Here
1569
                (outputFilters[Constants.IDENTITY_FILTER]);
1576
                (outputFilters[Constants.IDENTITY_FILTER]);
1570
            contentDelimitation = true;
1577
            contentDelimitation = true;
1571
        } else {
1578
        } else {
1579
            if (statusCode == HttpServletResponse.SC_SWITCHING_PROTOCOLS) {
1580
                outputBuffer.addActiveFilter
1581
                (outputFilters[Constants.IDENTITY_FILTER]);
1582
            } else
1572
            if (entityBody && http11) {
1583
            if (entityBody && http11) {
1573
                outputBuffer.addActiveFilter
1584
                outputBuffer.addActiveFilter
1574
                    (outputFilters[Constants.CHUNKED_FILTER]);
1585
                    (outputFilters[Constants.CHUNKED_FILTER]);
Lines 1614-1619 Link Here
1614
        // If we know that the request is bad this early, add the
1625
        // If we know that the request is bad this early, add the
1615
        // Connection: close header.
1626
        // Connection: close header.
1616
        keepAlive = keepAlive && !statusDropsConnection(statusCode);
1627
        keepAlive = keepAlive && !statusDropsConnection(statusCode);
1628
        if (statusCode == HttpServletResponse.SC_SWITCHING_PROTOCOLS) {
1629
            keepAlive = false;
1630
        } else
1617
        if (!keepAlive) {
1631
        if (!keepAlive) {
1618
            headers.addValue(Constants.CONNECTION).setString(Constants.CLOSE);
1632
            headers.addValue(Constants.CONNECTION).setString(Constants.CLOSE);
1619
        } else if (!http11 && !error) {
1633
        } else if (!http11 && !error) {
(-)java/org/apache/coyote/http11/Http11AprProcessor.java (+12 lines)
Lines 26-31 Link Here
26
import java.security.cert.CertificateFactory;
26
import java.security.cert.CertificateFactory;
27
import java.security.cert.X509Certificate;
27
import java.security.cert.X509Certificate;
28
28
29
import javax.servlet.http.HttpServletResponse;
30
29
import org.apache.coyote.ActionCode;
31
import org.apache.coyote.ActionCode;
30
import org.apache.coyote.ActionHook;
32
import org.apache.coyote.ActionHook;
31
import org.apache.coyote.Adapter;
33
import org.apache.coyote.Adapter;
Lines 1265-1270 Link Here
1265
            //no op
1267
            //no op
1266
        } else if (actionCode == ActionCode.ACTION_COMET_SETTIMEOUT) {
1268
        } else if (actionCode == ActionCode.ACTION_COMET_SETTIMEOUT) {
1267
            //no op
1269
            //no op
1270
        } else if (actionCode == ActionCode.ACTION_PROTOCOL_SWITCH) {
1271
            inputBuffer.lastActiveFilter = -1;
1272
            outputBuffer.lastActiveFilter = -1;
1268
        }
1273
        }
1269
1274
1270
    }
1275
    }
Lines 1708-1713 Link Here
1708
                (outputFilters[Constants.IDENTITY_FILTER]);
1713
                (outputFilters[Constants.IDENTITY_FILTER]);
1709
            contentDelimitation = true;
1714
            contentDelimitation = true;
1710
        } else {
1715
        } else {
1716
            if (statusCode == HttpServletResponse.SC_SWITCHING_PROTOCOLS) {
1717
                outputBuffer.addActiveFilter
1718
                (outputFilters[Constants.IDENTITY_FILTER]);
1719
            } else
1711
            if (entityBody && http11) {
1720
            if (entityBody && http11) {
1712
                outputBuffer.addActiveFilter
1721
                outputBuffer.addActiveFilter
1713
                    (outputFilters[Constants.CHUNKED_FILTER]);
1722
                    (outputFilters[Constants.CHUNKED_FILTER]);
Lines 1753-1758 Link Here
1753
        // If we know that the request is bad this early, add the
1762
        // If we know that the request is bad this early, add the
1754
        // Connection: close header.
1763
        // Connection: close header.
1755
        keepAlive = keepAlive && !statusDropsConnection(statusCode);
1764
        keepAlive = keepAlive && !statusDropsConnection(statusCode);
1765
        if (statusCode == HttpServletResponse.SC_SWITCHING_PROTOCOLS) {
1766
            keepAlive = false;
1767
        } else
1756
        if (!keepAlive) {
1768
        if (!keepAlive) {
1757
            headers.addValue(Constants.CONNECTION).setString(Constants.CLOSE);
1769
            headers.addValue(Constants.CONNECTION).setString(Constants.CLOSE);
1758
        } else if (!http11 && !error) {
1770
        } else if (!http11 && !error) {
(-)java/org/apache/coyote/http11/Http11NioProcessor.java (-1 / +12 lines)
Lines 25-30 Link Here
25
import java.util.regex.Pattern;
25
import java.util.regex.Pattern;
26
import java.util.regex.PatternSyntaxException;
26
import java.util.regex.PatternSyntaxException;
27
27
28
import javax.servlet.http.HttpServletResponse;
29
28
import org.apache.coyote.ActionCode;
30
import org.apache.coyote.ActionCode;
29
import org.apache.coyote.ActionHook;
31
import org.apache.coyote.ActionHook;
30
import org.apache.coyote.Adapter;
32
import org.apache.coyote.Adapter;
Lines 328-334 Link Here
328
     */
330
     */
329
    protected String server = null;
331
    protected String server = null;
330
332
331
332
    // ------------------------------------------------------------- Properties
333
    // ------------------------------------------------------------- Properties
333
334
334
335
Lines 1254-1259 Link Here
1254
            RequestInfo rp = request.getRequestProcessor();
1255
            RequestInfo rp = request.getRequestProcessor();
1255
            if ( rp.getStage() != org.apache.coyote.Constants.STAGE_SERVICE ) //async handling
1256
            if ( rp.getStage() != org.apache.coyote.Constants.STAGE_SERVICE ) //async handling
1256
                attach.setTimeout(timeout);
1257
                attach.setTimeout(timeout);
1258
        } else if (actionCode == ActionCode.ACTION_PROTOCOL_SWITCH) {
1259
            inputBuffer.lastActiveFilter = -1;
1260
            outputBuffer.lastActiveFilter = -1;
1257
        }
1261
        }
1258
1262
1259
    }
1263
    }
Lines 1703-1708 Link Here
1703
                (outputFilters[Constants.IDENTITY_FILTER]);
1707
                (outputFilters[Constants.IDENTITY_FILTER]);
1704
            contentDelimitation = true;
1708
            contentDelimitation = true;
1705
        } else {
1709
        } else {
1710
            if (statusCode == HttpServletResponse.SC_SWITCHING_PROTOCOLS) {
1711
                outputBuffer.addActiveFilter
1712
                (outputFilters[Constants.IDENTITY_FILTER]);
1713
            } else 
1706
            if (entityBody && http11) {
1714
            if (entityBody && http11) {
1707
                outputBuffer.addActiveFilter
1715
                outputBuffer.addActiveFilter
1708
                    (outputFilters[Constants.CHUNKED_FILTER]);
1716
                    (outputFilters[Constants.CHUNKED_FILTER]);
Lines 1748-1753 Link Here
1748
        // If we know that the request is bad this early, add the
1756
        // If we know that the request is bad this early, add the
1749
        // Connection: close header.
1757
        // Connection: close header.
1750
        keepAlive = keepAlive && !statusDropsConnection(statusCode);
1758
        keepAlive = keepAlive && !statusDropsConnection(statusCode);
1759
        if (statusCode == HttpServletResponse.SC_SWITCHING_PROTOCOLS) {
1760
            keepAlive = false;
1761
        } else
1751
        if (!keepAlive) {
1762
        if (!keepAlive) {
1752
            headers.addValue(Constants.CONNECTION).setString(Constants.CLOSE);
1763
            headers.addValue(Constants.CONNECTION).setString(Constants.CLOSE);
1753
        } else if (!http11 && !error) {
1764
        } else if (!http11 && !error) {
(-)java/org/apache/coyote/ActionCode.java (+6 lines)
Lines 161-166 Link Here
161
     */
161
     */
162
    public static final ActionCode ACTION_COMET_SETTIMEOUT = new ActionCode(25);
162
    public static final ActionCode ACTION_COMET_SETTIMEOUT = new ActionCode(25);
163
    
163
    
164
    
165
    /**
166
     * Clears input and output filters from input and output
167
     */
168
    public static final ActionCode ACTION_PROTOCOL_SWITCH = new ActionCode(26);
169
    
164
    // ----------------------------------------------------------- Constructors
170
    // ----------------------------------------------------------- Constructors
165
    int code;
171
    int code;
166
172
(-)java/org/apache/tomcat/util/buf/B2CConverter.java (+14 lines)
Lines 47-52 Link Here
47
    private static final Map<String, Charset> encodingToCharsetCache =
47
    private static final Map<String, Charset> encodingToCharsetCache =
48
        new HashMap<String, Charset>();
48
        new HashMap<String, Charset>();
49
    
49
    
50
    public static final Charset ISO_8859_1;
51
    public static final Charset UTF_8;
52
50
    static {
53
    static {
51
        for (Charset charset: Charset.availableCharsets().values()) {
54
        for (Charset charset: Charset.availableCharsets().values()) {
52
            encodingToCharsetCache.put(
55
            encodingToCharsetCache.put(
Lines 56-62 Link Here
56
                        alias.toLowerCase(Locale.US), charset);
59
                        alias.toLowerCase(Locale.US), charset);
57
            }
60
            }
58
        }
61
        }
62
        Charset iso88591 = null;
63
        Charset utf8 = null;
64
        try {
65
            iso88591 = getCharset("ISO-8859-1");
66
            utf8 = getCharset("UTF-8");
67
        } catch (UnsupportedEncodingException e) {
68
            // Impossible. All JVMs must support these.
69
            e.printStackTrace();
59
    }
70
    }
71
        ISO_8859_1 = iso88591;
72
        UTF_8 = utf8;
73
    }
60
74
61
    public static Charset getCharset(String enc)
75
    public static Charset getCharset(String enc)
62
            throws UnsupportedEncodingException {
76
            throws UnsupportedEncodingException {
(-)java/org/apache/catalina/websocket/LocalStrings.properties (+27 lines)
Line 0 Link Here
1
# Licensed to the Apache Software Foundation (ASF) under one or more
2
# contributor license agreements.  See the NOTICE file distributed with
3
# this work for additional information regarding copyright ownership.
4
# The ASF licenses this file to You under the Apache License, Version 2.0
5
# (the "License"); you may not use this file except in compliance with
6
# the License.  You may obtain a copy of the License at
7
#
8
#     http://www.apache.org/licenses/LICENSE-2.0
9
#
10
# Unless required by applicable law or agreed to in writing, software
11
# distributed under the License is distributed on an "AS IS" BASIS,
12
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
# See the License for the specific language governing permissions and
14
# limitations under the License.
15
16
frame.eos=The end of the stream was reached before the expected number of payload bytes could be read
17
frame.invalidUtf8=A sequence of bytes was received that did not represent valid UTF-8
18
frame.readFailed=Failed to read the first byte of the next WebSocket frame. The return value from the read was [{0}]
19
frame.readEos=The end of the stream was reached when trying to read the first byte of a new WebSocket frame
20
frame.notMasked=The client frame was not masked but all client frames must be masked
21
22
is.notContinuation=A frame with the OpCode [{0}] was received when a continuation frame was expected
23
is.unknownOpCode=A frame with the unrecognized OpCode [{0}] was received
24
25
message.bufferTooSmall=The buffer is not big enough to contain the message currently being processed
26
27
outbound.closed=The WebSocket connection has been closed
(-)java/org/apache/catalina/websocket/MessageInbound.java (+182 lines)
Line 0 Link Here
1
/*
2
 * Licensed to the Apache Software Foundation (ASF) under one or more
3
 * contributor license agreements.  See the NOTICE file distributed with
4
 * this work for additional information regarding copyright ownership.
5
 * The ASF licenses this file to You under the Apache License, Version 2.0
6
 * (the "License"); you may not use this file except in compliance with
7
 * the License.  You may obtain a copy of the License at
8
 *
9
 *      http://www.apache.org/licenses/LICENSE-2.0
10
 *
11
 * Unless required by applicable law or agreed to in writing, software
12
 * distributed under the License is distributed on an "AS IS" BASIS,
13
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
 * See the License for the specific language governing permissions and
15
 * limitations under the License.
16
 */
17
package org.apache.catalina.websocket;
18
19
import java.io.IOException;
20
import java.io.InputStream;
21
import java.io.Reader;
22
import java.nio.ByteBuffer;
23
import java.nio.CharBuffer;
24
25
import org.apache.tomcat.util.res.StringManager;
26
27
/**
28
 * Base implementation of the class used to process WebSocket connections based
29
 * on messages. Applications should extend this class to provide application
30
 * specific functionality. Applications that wish to operate on a stream basis
31
 * rather than a message basis should use {@link StreamInbound}.
32
 */
33
public abstract class MessageInbound extends StreamInbound {
34
35
    private static final StringManager sm =
36
            StringManager.getManager(Constants.Package);
37
38
39
    // 2MB - like maxPostSize
40
    private int byteBufferMaxSize = 2097152;
41
    private int charBufferMaxSize = 2097152;
42
43
    private ByteBuffer bb = ByteBuffer.allocate(8192);
44
    private CharBuffer cb = CharBuffer.allocate(8192);
45
46
47
    @Override
48
    protected final void onBinaryData(InputStream is) throws IOException {
49
        int read = 0;
50
        while (read > -1) {
51
            bb.position(bb.position() + read);
52
            if (bb.remaining() == 0) {
53
                resizeByteBuffer();
54
            }
55
            read = is.read(bb.array(), bb.position(), bb.remaining());
56
        }
57
        bb.flip();
58
        onBinaryMessage(bb);
59
        bb.clear();
60
    }
61
62
63
    @Override
64
    protected final void onTextData(Reader r) throws IOException {
65
        int read = 0;
66
        while (read > -1) {
67
            cb.position(cb.position() + read);
68
            if (cb.remaining() == 0) {
69
                resizeCharBuffer();
70
            }
71
            read = r.read(cb.array(), cb.position(), cb.remaining());
72
        }
73
        cb.flip();
74
        onTextMessage(cb);
75
        cb.clear();
76
    }
77
78
79
    private void resizeByteBuffer() throws IOException {
80
        int maxSize = getByteBufferMaxSize();
81
        if (bb.limit() >= maxSize) {
82
            throw new IOException(sm.getString("message.bufferTooSmall"));
83
        }
84
85
        long newSize = bb.limit() * 2;
86
        if (newSize > maxSize) {
87
            newSize = maxSize;
88
        }
89
90
        // Cast is safe. newSize < maxSize and maxSize is an int
91
        ByteBuffer newBuffer = ByteBuffer.allocate((int) newSize);
92
        bb.rewind();
93
        newBuffer.put(bb);
94
        bb = newBuffer;
95
    }
96
97
98
    private void resizeCharBuffer() throws IOException {
99
        int maxSize = getCharBufferMaxSize();
100
        if (cb.limit() >= maxSize) {
101
            throw new IOException(sm.getString("message.bufferTooSmall"));
102
        }
103
104
        long newSize = cb.limit() * 2;
105
        if (newSize > maxSize) {
106
            newSize = maxSize;
107
        }
108
109
        // Cast is safe. newSize < maxSize and maxSize is an int
110
        CharBuffer newBuffer = CharBuffer.allocate((int) newSize);
111
        cb.rewind();
112
        newBuffer.put(cb);
113
        cb = newBuffer;
114
    }
115
116
117
    /**
118
     * Obtain the current maximum size (in bytes) of the buffer used for binary
119
     * messages.
120
     */
121
    public final int getByteBufferMaxSize() {
122
        return byteBufferMaxSize;
123
    }
124
125
126
    /**
127
     * Set the maximum size (in bytes) of the buffer used for binary messages.
128
     */
129
    public final void setByteBufferMaxSize(int byteBufferMaxSize) {
130
        this.byteBufferMaxSize = byteBufferMaxSize;
131
    }
132
133
134
    /**
135
     * Obtain the current maximum size (in characters) of the buffer used for
136
     * binary messages.
137
     */
138
    public final int getCharBufferMaxSize() {
139
        return charBufferMaxSize;
140
    }
141
142
143
    /**
144
     * Set the maximum size (in characters) of the buffer used for textual
145
     * messages.
146
     */
147
    public final void setCharBufferMaxSize(int charBufferMaxSize) {
148
        this.charBufferMaxSize = charBufferMaxSize;
149
    }
150
151
152
    /**
153
     * This method is called when there is a binary WebSocket message available
154
     * to process. The message is presented via a ByteBuffer and may have been
155
     * formed from one or more frames. The number of frames used to transmit the
156
     * message is not made visible to the application.
157
     *
158
     * @param message       The WebSocket message
159
     *
160
     * @throws IOException  If a problem occurs processing the message. Any
161
     *                      exception will trigger the closing of the WebSocket
162
     *                      connection.
163
     */
164
    protected abstract void onBinaryMessage(ByteBuffer message)
165
            throws IOException;
166
167
168
    /**
169
     * This method is called when there is a textual WebSocket message available
170
     * to process. The message is presented via a CharBuffer and may have been
171
     * formed from one or more frames. The number of frames used to transmit the
172
     * message is not made visible to the application.
173
     *
174
     * @param message       The WebSocket message
175
     *
176
     * @throws IOException  If a problem occurs processing the message. Any
177
     *                      exception will trigger the closing of the WebSocket
178
     *                      connection.
179
     */
180
    protected abstract void onTextMessage(CharBuffer message)
181
            throws IOException;
182
}
(-)java/org/apache/catalina/websocket/WsFrame.java (+232 lines)
Line 0 Link Here
1
/*
2
 * Licensed to the Apache Software Foundation (ASF) under one or more
3
 * contributor license agreements.  See the NOTICE file distributed with
4
 * this work for additional information regarding copyright ownership.
5
 * The ASF licenses this file to You under the Apache License, Version 2.0
6
 * (the "License"); you may not use this file except in compliance with
7
 * the License.  You may obtain a copy of the License at
8
 *
9
 *      http://www.apache.org/licenses/LICENSE-2.0
10
 *
11
 * Unless required by applicable law or agreed to in writing, software
12
 * distributed under the License is distributed on an "AS IS" BASIS,
13
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
 * See the License for the specific language governing permissions and
15
 * limitations under the License.
16
 */
17
package org.apache.catalina.websocket;
18
19
import java.io.EOFException;
20
import java.io.IOException;
21
import java.nio.ByteBuffer;
22
import java.nio.CharBuffer;
23
import java.nio.charset.CoderResult;
24
25
import org.apache.catalina.CometEvent;
26
import org.apache.catalina.util.Conversions;
27
import org.apache.tomcat.util.res.StringManager;
28
29
/**
30
 * Represents a complete WebSocket frame with the exception of the payload for
31
 * non-control frames.
32
 */
33
public class WsFrame {
34
35
    private static final StringManager sm =
36
            StringManager.getManager(Constants.Package);
37
38
39
    private final boolean fin;
40
    private final int rsv;
41
    private final byte opCode;
42
    private byte[] mask = new byte[4];
43
    private long payloadLength;
44
    private ByteBuffer payload;
45
46
    /**
47
     * Create the new WebSocket frame, reading data from the processor as
48
     * necessary.
49
     *
50
     * @param first     First byte of data for this frame
51
     * @param processor Processor associated with the WebSocket connection on
52
     *                  which the frame has been sent
53
     *
54
     * @throws IOException  If a problem occurs processing the frame. Any
55
     *                      exception will trigger the closing of the WebSocket
56
     *                      connection.
57
     */
58
    private WsFrame(byte first,
59
            CometEvent event) throws IOException {
60
61
        int b = first & 0xFF;
62
        fin = (b & 0x80) > 0;
63
        rsv = (b & 0x70) >>> 4;
64
        opCode = (byte) (b & 0x0F);
65
66
        b = blockingRead(event);
67
        // Client data must be masked
68
        if ((b & 0x80) == 0) {
69
            throw new IOException(sm.getString("frame.notMasked"));
70
        }
71
72
        payloadLength = b & 0x7F;
73
        if (payloadLength == 126) {
74
            byte[] extended = new byte[2];
75
            blockingRead(event, extended);
76
            payloadLength = Conversions.byteArrayToLong(extended);
77
        } else if (payloadLength == 127) {
78
            byte[] extended = new byte[8];
79
            blockingRead(event, extended);
80
            payloadLength = Conversions.byteArrayToLong(extended);
81
        }
82
83
        if (isControl()) {
84
            if (payloadLength > 125) {
85
                throw new IOException();
86
            }
87
            if (!fin) {
88
                throw new IOException();
89
            }
90
        }
91
92
        blockingRead(event, mask);
93
94
        if (isControl()) {
95
            // Note: Payload limited to <= 125 bytes by test above
96
            payload = ByteBuffer.allocate((int) payloadLength);
97
            blockingRead(event, payload);
98
99
            if (opCode == Constants.OPCODE_CLOSE && payloadLength > 2) {
100
                // Check close payload - if present - is valid UTF-8
101
                CharBuffer cb = CharBuffer.allocate((int) payloadLength);
102
                Utf8Decoder decoder = new Utf8Decoder();
103
                payload.position(2);
104
                CoderResult cr = decoder.decode(payload, cb, true);
105
                payload.position(0);
106
                if (cr.isError()) {
107
                    throw new IOException(sm.getString("frame.invalidUtf8"));
108
                }
109
            }
110
        } else {
111
            payload = null;
112
        }
113
    }
114
115
    public boolean getFin() {
116
        return fin;
117
    }
118
119
    public int getRsv() {
120
        return rsv;
121
    }
122
123
    public byte getOpCode() {
124
        return opCode;
125
    }
126
127
    public boolean isControl() {
128
        return (opCode & 0x08) > 0;
129
    }
130
131
    public byte[] getMask() {
132
        return mask;
133
    }
134
135
    public long getPayLoadLength() {
136
        return payloadLength;
137
    }
138
139
    public ByteBuffer getPayLoad() {
140
        return payload;
141
    }
142
143
144
    /*
145
     * Blocks until a aingle byte has been read
146
     */
147
    private int blockingRead(CometEvent event)
148
            throws IOException {
149
        int result = event.getHttpServletRequest().getInputStream().read();
150
        if (result == -1) {
151
            throw new IOException(sm.getString("frame.eos"));
152
        }
153
        return result;
154
    }
155
156
157
    /*
158
     * Blocks until the byte array has been filled.
159
     */
160
    private void blockingRead(CometEvent event, byte[] bytes)
161
            throws IOException {
162
        int read = 0;
163
        int last = 0;
164
        while (read < bytes.length) {
165
            last = event.getHttpServletRequest().getInputStream().read(bytes, read, bytes.length - read);
166
            if (last == -1) {
167
                throw new IOException(sm.getString("frame.eos"));
168
            }
169
            read += last;
170
        }
171
    }
172
173
174
    /*
175
     * Intended to read whole payload and blocks until it has. Therefore able to
176
     * unmask the payload data.
177
     */
178
    private void blockingRead(CometEvent event, ByteBuffer bb)
179
            throws IOException {
180
        int last = 0;
181
        while (bb.hasRemaining()) {
182
            last = event.getHttpServletRequest().getInputStream().read();
183
            if (last == -1) {
184
                throw new IOException(sm.getString("frame.eos"));
185
            }
186
            bb.put((byte) (last ^ mask[bb.position() % 4]));
187
        }
188
        bb.flip();
189
    }
190
191
192
    /**
193
     * Read the next WebSocket frame, reading data from the processor as
194
     * necessary.
195
     *
196
     * @param processor Processor associated with the WebSocket connection on
197
     *                  which the frame has been sent
198
     *
199
     * @param block Should this method block until a frame is presented if no
200
     *              data is currently available to process. Note that is a
201
     *              single byte is available, this method will block until the
202
     *              complete frame (excluding payload for non-control frames) is
203
     *              available.
204
     *
205
     * @throws IOException  If a problem occurs processing the frame. Any
206
     *                      exception will trigger the closing of the WebSocket
207
     *                      connection.
208
     */
209
    public static WsFrame nextFrame(CometEvent event,
210
            boolean block) throws IOException {
211
212
        byte[] first = new byte[1];
213
        
214
        if (!block) {
215
            if (event.getHttpServletRequest().getInputStream().available() == 0) {
216
                return null;
217
            }
218
        }
219
        
220
        int read = event.getHttpServletRequest().getInputStream().read(first, 0, 1);
221
        if (read == 1) {
222
            return new WsFrame(first[0], event);
223
        } else if (read == 0) {
224
            return null;
225
        } else if (read == -1) {
226
            throw new EOFException(sm.getString("frame.readEos"));
227
        } else {
228
            throw new IOException(
229
                    sm.getString("frame.readFailed", Integer.valueOf(read)));
230
        }
231
    }
232
}
(-)java/org/apache/catalina/websocket/WsInputStream.java (+158 lines)
Line 0 Link Here
1
/*
2
 * Licensed to the Apache Software Foundation (ASF) under one or more
3
 * contributor license agreements.  See the NOTICE file distributed with
4
 * this work for additional information regarding copyright ownership.
5
 * The ASF licenses this file to You under the Apache License, Version 2.0
6
 * (the "License"); you may not use this file except in compliance with
7
 * the License.  You may obtain a copy of the License at
8
 *
9
 *      http://www.apache.org/licenses/LICENSE-2.0
10
 *
11
 * Unless required by applicable law or agreed to in writing, software
12
 * distributed under the License is distributed on an "AS IS" BASIS,
13
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
 * See the License for the specific language governing permissions and
15
 * limitations under the License.
16
 */
17
package org.apache.catalina.websocket;
18
19
import java.io.IOException;
20
import java.io.InputStream;
21
22
import org.apache.catalina.CometEvent;
23
import org.apache.tomcat.util.res.StringManager;
24
25
/**
26
 * This class is used to read WebSocket frames from the underlying socket and
27
 * makes the payload available for reading as an {@link InputStream}. It only
28
 * makes the number of bytes declared in the payload length available for
29
 * reading even if more bytes are available from the socket.
30
 */
31
public class WsInputStream extends InputStream {
32
33
    private static final StringManager sm =
34
            StringManager.getManager(Constants.Package);
35
36
37
    private CometEvent event;
38
    private WsOutbound outbound;
39
40
    private WsFrame frame;
41
    private long remaining;
42
    private long readThisFragment;
43
44
    private String error = null;
45
46
47
    public WsInputStream(CometEvent event, WsOutbound outbound) {
48
        this.event = event;
49
        this.outbound = outbound;
50
    }
51
52
53
    /**
54
     * Process the next WebSocket frame.
55
     *
56
     * @param block Should this method block until a frame is presented if no
57
     *              data is currently available to process. Note that is a
58
     *              single byte is available, this method will block until the
59
     *              complete frame (excluding payload for non-control frames) is
60
     *              available.
61
     *
62
     * @return  The next frame to be processed or <code>null</code> if block is
63
     *          <code>false</code> and there is no data to be processed.
64
     *
65
     * @throws IOException  If a problem occurs reading the data for the frame.
66
     */
67
    public WsFrame nextFrame(boolean block) throws IOException {
68
        frame = WsFrame.nextFrame(event, block);
69
        if (frame != null) {
70
            readThisFragment = 0;
71
            remaining = frame.getPayLoadLength();
72
        }
73
        return frame;
74
    }
75
76
77
    // ----------------------------------------------------- InputStream methods
78
79
    @Override
80
    public int read() throws IOException {
81
82
        makePayloadDataAvailable();
83
84
        if (remaining == 0) {
85
            return -1;
86
        }
87
88
        remaining--;
89
        readThisFragment++;
90
91
        int masked = event.getHttpServletRequest().getInputStream().available();
92
        if(masked == -1 || masked == 0) {
93
            return -1;
94
        }
95
        masked = event.getHttpServletRequest().getInputStream().read();
96
        return masked ^
97
                (frame.getMask()[(int) ((readThisFragment - 1) % 4)] & 0xFF);
98
    }
99
100
101
    @Override
102
    public int read(byte b[], int off, int len) throws IOException {
103
104
        makePayloadDataAvailable();
105
106
        if (remaining == 0) {
107
            return -1;
108
        }
109
110
        if (len > remaining) {
111
            len = (int) remaining;
112
        }
113
        int result = event.getHttpServletRequest().getInputStream().read(b, off, len);
114
        if(result == -1) {
115
            return -1;
116
        }
117
118
        for (int i = off; i < off + result; i++) {
119
            b[i] = (byte) (b[i] ^
120
                    frame.getMask()[(int) ((readThisFragment + i - off) % 4)]);
121
        }
122
        remaining -= result;
123
        readThisFragment += result;
124
        return result;
125
    }
126
127
128
    /*
129
     * Ensures that there is payload data ready to read.
130
     */
131
    private void makePayloadDataAvailable() throws IOException {
132
        if (error != null) {
133
            throw new IOException(error);
134
        }
135
        while (remaining == 0 && !frame.getFin()) {
136
            // Need more data - process next frame
137
            nextFrame(true);
138
            while (frame.isControl()) {
139
                if (frame.getOpCode() == Constants.OPCODE_PING) {
140
                    outbound.pong(frame.getPayLoad());
141
                } else if (frame.getOpCode() == Constants.OPCODE_PONG) {
142
                    // NO-OP. Swallow it.
143
                } else if (frame.getOpCode() == Constants.OPCODE_CLOSE) {
144
                    outbound.close(frame);
145
                } else{
146
                    throw new IOException(sm.getString("is.unknownOpCode",
147
                            Byte.valueOf(frame.getOpCode())));
148
                }
149
                nextFrame(true);
150
            }
151
            if (frame.getOpCode() != Constants.OPCODE_CONTINUATION) {
152
                error = sm.getString("is.notContinuation",
153
                        Byte.valueOf(frame.getOpCode()));
154
                throw new IOException(error);
155
            }
156
        }
157
    }
158
}
(-)java/org/apache/catalina/websocket/WsOutbound.java (+407 lines)
Line 0 Link Here
1
/*
2
 * Licensed to the Apache Software Foundation (ASF) under one or more
3
 * contributor license agreements.  See the NOTICE file distributed with
4
 * this work for additional information regarding copyright ownership.
5
 * The ASF licenses this file to You under the Apache License, Version 2.0
6
 * (the "License"); you may not use this file except in compliance with
7
 * the License.  You may obtain a copy of the License at
8
 *
9
 *      http://www.apache.org/licenses/LICENSE-2.0
10
 *
11
 * Unless required by applicable law or agreed to in writing, software
12
 * distributed under the License is distributed on an "AS IS" BASIS,
13
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
 * See the License for the specific language governing permissions and
15
 * limitations under the License.
16
 */
17
package org.apache.catalina.websocket;
18
19
import java.io.IOException;
20
import java.nio.ByteBuffer;
21
import java.nio.CharBuffer;
22
import java.nio.charset.CharsetEncoder;
23
import java.nio.charset.CoderResult;
24
25
import org.apache.catalina.CometEvent;
26
import org.apache.tomcat.util.buf.B2CConverter;
27
import org.apache.tomcat.util.res.StringManager;
28
29
/**
30
 * Provides the means to write WebSocket messages to the client. All methods
31
 * that write to the client (or update a buffer that is later written to the
32
 * client) are synchronized to prevent multiple threads trying to write to the
33
 * client at the same time.
34
 */
35
public class WsOutbound {
36
37
    private static final StringManager sm =
38
            StringManager.getManager(Constants.Package);
39
    public static final int DEFAULT_BUFFER_SIZE = 8192;
40
41
    private CometEvent event;
42
    private ByteBuffer bb;
43
    private CharBuffer cb;
44
    private boolean closed = false;
45
    private Boolean text = null;
46
    private boolean firstFrame = true;
47
48
49
    public WsOutbound(CometEvent event) {
50
        this(event, DEFAULT_BUFFER_SIZE, DEFAULT_BUFFER_SIZE);
51
    }
52
53
54
    public WsOutbound(CometEvent event, int byteBufferSize,
55
            int charBufferSize) {
56
        this.event = event;
57
        this.bb = ByteBuffer.allocate(byteBufferSize);
58
        this.cb = CharBuffer.allocate(charBufferSize);
59
    }
60
61
62
    /**
63
     * Adds the data to the buffer for binary data. If a textual message is
64
     * currently in progress that message will be completed and a new binary
65
     * message started. If the buffer for binary data is full, the buffer will
66
     * be flushed and a new binary continuation fragment started.
67
     *
68
     * @param b The byte (only the least significant byte is used) of data to
69
     *          send to the client.
70
     *
71
     * @throws IOException  If a flush is required and an error occurs writing
72
     *                      the WebSocket frame to the client
73
     */
74
    public synchronized void writeBinaryData(int b) throws IOException {
75
        if (closed) {
76
            throw new IOException(sm.getString("outbound.closed"));
77
        }
78
79
        if (bb.position() == bb.capacity()) {
80
            doFlush(false);
81
        }
82
        if (text == null) {
83
            text = Boolean.FALSE;
84
        } else if (text == Boolean.TRUE) {
85
            // Flush the character data
86
            flush();
87
            text = Boolean.FALSE;
88
        }
89
        bb.put((byte) (b & 0xFF));
90
    }
91
92
93
    /**
94
     * Adds the data to the buffer for textual data. If a binary message is
95
     * currently in progress that message will be completed and a new textual
96
     * message started. If the buffer for textual data is full, the buffer will
97
     * be flushed and a new textual continuation fragment started.
98
     *
99
     * @param c The character to send to the client.
100
     *
101
     * @throws IOException  If a flush is required and an error occurs writing
102
     *                      the WebSocket frame to the client
103
     */
104
    public synchronized void writeTextData(char c) throws IOException {
105
        if (closed) {
106
            throw new IOException(sm.getString("outbound.closed"));
107
        }
108
109
        if (cb.position() == cb.capacity()) {
110
            doFlush(false);
111
        }
112
113
        if (text == null) {
114
            text = Boolean.TRUE;
115
        } else if (text == Boolean.FALSE) {
116
            // Flush the binary data
117
            flush();
118
            text = Boolean.TRUE;
119
        }
120
        cb.append(c);
121
    }
122
123
124
    /**
125
     * Flush any message (binary or textual) that may be buffered and then send
126
     * a WebSocket binary message as a single frame with the provided buffer as
127
     * the payload of the message.
128
     *
129
     * @param msgBb The buffer containing the payload
130
     *
131
     * @throws IOException  If an error occurs writing to the client
132
     */
133
    public synchronized void writeBinaryMessage(ByteBuffer msgBb)
134
            throws IOException {
135
136
        if (closed) {
137
            throw new IOException(sm.getString("outbound.closed"));
138
        }
139
140
        if (text != null) {
141
            // Empty the buffer
142
            flush();
143
        }
144
        text = Boolean.FALSE;
145
        doWriteBytes(msgBb, true);
146
    }
147
148
149
    /**
150
     * Flush any message (binary or textual) that may be buffered and then send
151
     * a WebSocket text message as a single frame with the provided buffer as
152
     * the payload of the message.
153
     *
154
     * @param msgCb The buffer containing the payload
155
     *
156
     * @throws IOException  If an error occurs writing to the client
157
     */
158
    public synchronized void writeTextMessage(CharBuffer msgCb)
159
            throws IOException {
160
161
        if (closed) {
162
            throw new IOException(sm.getString("outbound.closed"));
163
        }
164
165
        if (text != null) {
166
            // Empty the buffer
167
            flush();
168
        }
169
        text = Boolean.TRUE;
170
        doWriteText(msgCb, true);
171
    }
172
173
174
    /**
175
     * Flush any message (binary or textual) that may be buffered.
176
     *
177
     * @throws IOException  If an error occurs writing to the client
178
     */
179
    public synchronized void flush() throws IOException {
180
        if (closed) {
181
            throw new IOException(sm.getString("outbound.closed"));
182
        }
183
        doFlush(true);
184
    }
185
186
187
    private void doFlush(boolean finalFragment) throws IOException {
188
        if (text == null) {
189
            // No data
190
            return;
191
        }
192
        if (text.booleanValue()) {
193
            cb.flip();
194
            doWriteText(cb, finalFragment);
195
        } else {
196
            bb.flip();
197
            doWriteBytes(bb, finalFragment);
198
        }
199
    }
200
201
202
    /**
203
     * Respond to a client close by sending a close that echoes the status code
204
     * and message.
205
     *
206
     * @param frame The close frame received from a client
207
     *
208
     * @throws IOException  If an error occurs writing to the client
209
     */
210
    protected void close(WsFrame frame) throws IOException {
211
        if (frame.getPayLoadLength() > 0) {
212
            // Must be status (2 bytes) plus optional message
213
            if (frame.getPayLoadLength() == 1) {
214
                throw new IOException();
215
            }
216
            int status = (frame.getPayLoad().get() & 0xFF) << 8;
217
            status += frame.getPayLoad().get() & 0xFF;
218
219
            if (validateCloseStatus(status)) {
220
                // Echo the status back to the client
221
                close(status, frame.getPayLoad());
222
            } else {
223
                // Invalid close code
224
                close(Constants.STATUS_PROTOCOL_ERROR, null);
225
            }
226
        } else {
227
            // No status
228
            close(0, null);
229
        }
230
    }
231
232
233
    private boolean validateCloseStatus(int status) {
234
235
        if (status == Constants.STATUS_CLOSE_NORMAL ||
236
                status == Constants.STATUS_SHUTDOWN ||
237
                status == Constants.STATUS_PROTOCOL_ERROR ||
238
                status == Constants.STATUS_UNEXPECTED_DATA_TYPE ||
239
                status == Constants.STATUS_BAD_DATA ||
240
                status == Constants.STATUS_POLICY_VIOLATION ||
241
                status == Constants.STATUS_MESSAGE_TOO_LARGE ||
242
                status == Constants.STATUS_REQUIRED_EXTENSION ||
243
                status == Constants.STATUS_UNEXPECTED_CONDITION ||
244
                (status > 2999 && status < 5000)) {
245
            // Other 1xxx reserved / not permitted
246
            // 2xxx reserved
247
            // 3xxx framework defined
248
            // 4xxx application defined
249
            return true;
250
        }
251
        // <1000 unused
252
        // >4999 undefined
253
        return false;
254
    }
255
256
257
    /**
258
     * Send a close message to the client
259
     *
260
     * @param status    Must be a valid status code or zero to send no code
261
     * @param data      Optional message. If message is defined, a valid status
262
     *                  code must be provided.
263
     *
264
     * @throws IOException  If an error occurs writing to the client
265
     */
266
    public synchronized void close(int status, ByteBuffer data)
267
            throws IOException {
268
269
        if (closed) {
270
            return;
271
        }
272
        closed = true;
273
274
        event.getHttpServletResponse().getOutputStream().write(0x88);
275
        if (status == 0) {
276
            event.getHttpServletResponse().getOutputStream().write(0);
277
        } else if (data == null || data.position() == data.limit()) {
278
            event.getHttpServletResponse().getOutputStream().write(2);
279
            event.getHttpServletResponse().getOutputStream().write(status >>> 8);
280
            event.getHttpServletResponse().getOutputStream().write(status);
281
        } else {
282
            event.getHttpServletResponse().getOutputStream().write(2 + data.limit() - data.position());
283
            event.getHttpServletResponse().getOutputStream().write(status >>> 8);
284
            event.getHttpServletResponse().getOutputStream().write(status);
285
            event.getHttpServletResponse().getOutputStream().write(data.array(), data.position(),
286
                    data.limit() - data.position());
287
        }
288
        event.getHttpServletResponse().flushBuffer();
289
290
        bb = null;
291
        cb = null;
292
        event = null;
293
    }
294
295
296
    /**
297
     * Send a pong message to the client
298
     *
299
     * @param data      Optional message.
300
     *
301
     * @throws IOException  If an error occurs writing to the client
302
     */
303
    public synchronized void pong(ByteBuffer data) throws IOException {
304
305
        if (closed) {
306
            throw new IOException(sm.getString("outbound.closed"));
307
        }
308
309
        doFlush(true);
310
311
        event.getHttpServletResponse().getOutputStream().write(0x8A);
312
        if (data == null) {
313
            event.getHttpServletResponse().getOutputStream().write(0);
314
        } else {
315
            event.getHttpServletResponse().getOutputStream().write(data.limit() - data.position());
316
            event.getHttpServletResponse().getOutputStream().write(data.array(), data.position(),
317
                    data.limit() - data.position());
318
        }
319
320
        event.getHttpServletResponse().flushBuffer();
321
    }
322
323
324
    /**
325
     * Writes the provided bytes as the payload in a new WebSocket frame.
326
     *
327
     * @param buffer        The bytes to include in the payload.
328
     * @param finalFragment Do these bytes represent the final fragment of a
329
     *                      WebSocket message?
330
     * @throws IOException
331
     */
332
    private void doWriteBytes(ByteBuffer buffer, boolean finalFragment)
333
            throws IOException {
334
335
        // Work out the first byte
336
        int first = 0x00;
337
        if (finalFragment) {
338
            first = first + 0x80;
339
        }
340
        if (firstFrame) {
341
            if (text.booleanValue()) {
342
                first = first + 0x1;
343
            } else {
344
                first = first + 0x2;
345
            }
346
        }
347
        // Continuation frame is OpCode 0
348
        event.getHttpServletResponse().getOutputStream().write(first);
349
350
        if (buffer.limit() < 126) {
351
            event.getHttpServletResponse().getOutputStream().write(buffer.limit());
352
        } else if (buffer.limit() < 65536) {
353
            event.getHttpServletResponse().getOutputStream().write(126);
354
            event.getHttpServletResponse().getOutputStream().write(buffer.limit() >>> 8);
355
            event.getHttpServletResponse().getOutputStream().write(buffer.limit() & 0xFF);
356
        } else {
357
            // Will never be more than 2^31-1
358
            event.getHttpServletResponse().getOutputStream().write(127);
359
            event.getHttpServletResponse().getOutputStream().write(0);
360
            event.getHttpServletResponse().getOutputStream().write(0);
361
            event.getHttpServletResponse().getOutputStream().write(0);
362
            event.getHttpServletResponse().getOutputStream().write(0);
363
            event.getHttpServletResponse().getOutputStream().write(buffer.limit() >>> 24);
364
            event.getHttpServletResponse().getOutputStream().write(buffer.limit() >>> 16);
365
            event.getHttpServletResponse().getOutputStream().write(buffer.limit() >>> 8);
366
            event.getHttpServletResponse().getOutputStream().write(buffer.limit() & 0xFF);
367
368
        }
369
370
        // Write the content
371
        event.getHttpServletResponse().getOutputStream().write(buffer.array(), 0, buffer.limit());
372
        event.getHttpServletResponse().flushBuffer();
373
374
        // Reset
375
        if (finalFragment) {
376
            text = null;
377
            firstFrame = true;
378
        } else {
379
            firstFrame = false;
380
        }
381
        bb.clear();
382
    }
383
384
385
    /*
386
     * Convert the textual message to bytes and then output it.
387
     */
388
    private void doWriteText(CharBuffer buffer, boolean finalFragment)
389
            throws IOException {
390
        CharsetEncoder encoder = B2CConverter.UTF_8.newEncoder();
391
        do {
392
            CoderResult cr = encoder.encode(buffer, bb, true);
393
            if (cr.isError()) {
394
                cr.throwException();
395
            }
396
            bb.flip();
397
            if (buffer.hasRemaining()) {
398
                doWriteBytes(bb, false);
399
            } else {
400
                doWriteBytes(bb, finalFragment);
401
            }
402
        } while (buffer.hasRemaining());
403
404
        // Reset - bb will be cleared in doWriteBytes()
405
        cb.clear();
406
    }
407
}
(-)java/org/apache/catalina/websocket/StreamInbound.java (+227 lines)
Line 0 Link Here
1
/*
2
 * Licensed to the Apache Software Foundation (ASF) under one or more
3
 * contributor license agreements.  See the NOTICE file distributed with
4
 * this work for additional information regarding copyright ownership.
5
 * The ASF licenses this file to You under the Apache License, Version 2.0
6
 * (the "License"); you may not use this file except in compliance with
7
 * the License.  You may obtain a copy of the License at
8
 *
9
 *      http://www.apache.org/licenses/LICENSE-2.0
10
 *
11
 * Unless required by applicable law or agreed to in writing, software
12
 * distributed under the License is distributed on an "AS IS" BASIS,
13
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
 * See the License for the specific language governing permissions and
15
 * limitations under the License.
16
 */
17
package org.apache.catalina.websocket;
18
19
import java.io.IOException;
20
import java.io.InputStream;
21
import java.io.InputStreamReader;
22
import java.io.Reader;
23
import java.nio.ByteBuffer;
24
import java.nio.charset.MalformedInputException;
25
import java.nio.charset.UnmappableCharacterException;
26
27
import org.apache.catalina.CometEvent;
28
29
/**
30
 * Base implementation of the class used to process WebSocket connections based
31
 * on streams. Applications should extend this class to provide application
32
 * specific functionality. Applications that wish to operate on a message basis
33
 * rather than a stream basis should use {@link MessageInbound}.
34
 */
35
public abstract class StreamInbound  {
36
37
    private CometEvent event;
38
    private WsOutbound outbound;
39
    private int outboundByteBufferSize = WsOutbound.DEFAULT_BUFFER_SIZE;
40
    private int outboundCharBufferSize = WsOutbound.DEFAULT_BUFFER_SIZE;
41
42
43
44
    public int getOutboundByteBufferSize() {
45
        return outboundByteBufferSize;
46
    }
47
48
49
    /**
50
     * This only applies to the {@link WsOutbound} instance returned from
51
     * {@link #getWsOutbound()} created by a subsequent call to
52
     * {@link #setUpgradeOutbound(UpgradeOutbound)}. The current
53
     * {@link WsOutbound} instance, if any, is not affected.
54
     *
55
     * @param outboundByteBufferSize
56
     */
57
    public void setOutboundByteBufferSize(int outboundByteBufferSize) {
58
        this.outboundByteBufferSize = outboundByteBufferSize;
59
    }
60
61
62
    public int getOutboundCharBufferSize() {
63
        return outboundCharBufferSize;
64
    }
65
66
67
    /**
68
     * This only applies to the {@link WsOutbound} instance returned from
69
     * {@link #getWsOutbound()} created by a subsequent call to
70
     * {@link #setUpgradeOutbound(UpgradeOutbound)}. The current
71
     * {@link WsOutbound} instance, if any, is not affected.
72
     *
73
     * @param outboundCharBufferSize
74
     */
75
    public void setOutboundCharBufferSize(int outboundCharBufferSize) {
76
        this.outboundCharBufferSize = outboundCharBufferSize;
77
    }
78
79
80
    public final void setUpgradeOutbound(CometEvent event) {
81
        outbound = new WsOutbound(event, outboundByteBufferSize,
82
                outboundCharBufferSize);
83
    }
84
85
86
    public final void setUpgradeProcessor(CometEvent event) {
87
        this.event = event;
88
    }
89
90
91
    /**
92
     * Obtain the outbound side of this WebSocket connection used for writing
93
     * data to the client.
94
     */
95
    public final WsOutbound getWsOutbound() {
96
        return outbound;
97
    }
98
99
100
    public final CometEvent.EventType onData() throws IOException {
101
        // Must be start the start of a message (which may consist of multiple
102
        // frames)
103
        WsInputStream wsIs = new WsInputStream(event, getWsOutbound());
104
105
        try {
106
            WsFrame frame = wsIs.nextFrame(true);
107
108
            while (frame != null) {
109
                // TODO User defined extensions may define values for rsv
110
                if (frame.getRsv() > 0) {
111
                    closeOutboundConnection(
112
                            Constants.STATUS_PROTOCOL_ERROR, null);
113
                    return CometEvent.EventType.END;
114
                }
115
116
                byte opCode = frame.getOpCode();
117
118
                if (opCode == Constants.OPCODE_BINARY) {
119
                    onBinaryData(wsIs);
120
                } else if (opCode == Constants.OPCODE_TEXT) {
121
                    InputStreamReader r =
122
                            new InputStreamReader(wsIs, new Utf8Decoder());
123
                    onTextData(r);
124
                } else if (opCode == Constants.OPCODE_CLOSE){
125
                    closeOutboundConnection(frame);
126
                    return CometEvent.EventType.END;
127
                } else if (opCode == Constants.OPCODE_PING) {
128
                    getWsOutbound().pong(frame.getPayLoad());
129
                } else if (opCode == Constants.OPCODE_PONG) {
130
                    // NO-OP
131
                } else {
132
                    // Unknown OpCode
133
                    closeOutboundConnection(
134
                            Constants.STATUS_PROTOCOL_ERROR, null);
135
                    return CometEvent.EventType.END;
136
                }
137
                frame = wsIs.nextFrame(false);
138
            }
139
        } catch (MalformedInputException mie) {
140
            // Invalid UTF-8
141
            closeOutboundConnection(Constants.STATUS_BAD_DATA, null);
142
            return CometEvent.EventType.END;
143
        } catch (UnmappableCharacterException uce) {
144
            // Invalid UTF-8
145
            closeOutboundConnection(Constants.STATUS_BAD_DATA, null);
146
            return CometEvent.EventType.END;
147
        } catch (IOException ioe) {
148
            // Given something must have gone to reach this point, this
149
            // might not work but try it anyway.
150
            closeOutboundConnection(Constants.STATUS_PROTOCOL_ERROR, null);
151
            return CometEvent.EventType.END;
152
        }
153
        return CometEvent.EventType.READ;
154
    }
155
156
    private void closeOutboundConnection(int status, ByteBuffer data) throws IOException {
157
        try {
158
            getWsOutbound().close(status, data);
159
        } finally {
160
            onClose(status);
161
        }
162
    }
163
164
    private void closeOutboundConnection(WsFrame frame) throws IOException {
165
        try {
166
            getWsOutbound().close(frame);
167
        } finally {
168
            onClose(Constants.OPCODE_CLOSE);
169
        }
170
    }
171
172
    public void onUpgradeComplete() {
173
        onOpen(outbound);
174
    }
175
176
    /**
177
     * Intended to be overridden by sub-classes that wish to be notified
178
     * when the outbound connection is established. The default implementation
179
     * is a NO-OP.
180
     *
181
     * @param outbound    The outbound WebSocket connection.
182
     */
183
    protected void onOpen(WsOutbound outbound) {
184
        // NO-OP
185
    }
186
187
    /**
188
     * Intended to be overridden by sub-classes that wish to be notified
189
     * when the outbound connection is closed. The default implementation
190
     * is a NO-OP.
191
     *
192
     * @param status    The status code of the close reason.
193
     */
194
    protected void onClose(int status) {
195
        // NO-OP
196
    }
197
198
199
    /**
200
     * This method is called when there is a binary WebSocket message available
201
     * to process. The message is presented via a stream and may be formed from
202
     * one or more frames. The number of frames used to transmit the message is
203
     * not made visible to the application.
204
     *
205
     * @param is    The WebSocket message
206
     *
207
     * @throws IOException  If a problem occurs processing the message. Any
208
     *                      exception will trigger the closing of the WebSocket
209
     *                      connection.
210
     */
211
    protected abstract void onBinaryData(InputStream is) throws IOException;
212
213
214
    /**
215
     * This method is called when there is a textual WebSocket message available
216
     * to process. The message is presented via a reader and may be formed from
217
     * one or more frames. The number of frames used to transmit the message is
218
     * not made visible to the application.
219
     *
220
     * @param r     The WebSocket message
221
     *
222
     * @throws IOException  If a problem occurs processing the message. Any
223
     *                      exception will trigger the closing of the WebSocket
224
     *                      connection.
225
     */
226
    protected abstract void onTextData(Reader r) throws IOException;
227
}
(-)java/org/apache/catalina/websocket/WebSocketServlet.java (+308 lines)
Line 0 Link Here
1
/*
2
 * Licensed to the Apache Software Foundation (ASF) under one or more
3
 * contributor license agreements.  See the NOTICE file distributed with
4
 * this work for additional information regarding copyright ownership.
5
 * The ASF licenses this file to You under the Apache License, Version 2.0
6
 * (the "License"); you may not use this file except in compliance with
7
 * the License.  You may obtain a copy of the License at
8
 *
9
 *      http://www.apache.org/licenses/LICENSE-2.0
10
 *
11
 * Unless required by applicable law or agreed to in writing, software
12
 * distributed under the License is distributed on an "AS IS" BASIS,
13
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
 * See the License for the specific language governing permissions and
15
 * limitations under the License.
16
 */
17
package org.apache.catalina.websocket;
18
19
import java.io.IOException;
20
import java.lang.reflect.Field;
21
import java.security.MessageDigest;
22
import java.security.NoSuchAlgorithmException;
23
import java.util.ArrayList;
24
import java.util.Collections;
25
import java.util.Enumeration;
26
import java.util.List;
27
28
import javax.servlet.ServletException;
29
import javax.servlet.http.HttpServlet;
30
import javax.servlet.http.HttpServletRequest;
31
import javax.servlet.http.HttpServletResponse;
32
33
import org.apache.catalina.CometEvent;
34
import org.apache.catalina.CometEvent.EventSubType;
35
import org.apache.catalina.CometEvent.EventType;
36
import org.apache.catalina.CometProcessor;
37
import org.apache.catalina.connector.CometEventImpl;
38
import org.apache.catalina.connector.Request;
39
import org.apache.catalina.connector.RequestFacade;
40
import org.apache.catalina.connector.Response;
41
import org.apache.catalina.util.Base64;
42
import org.apache.tomcat.util.buf.B2CConverter;
43
44
/**
45
 * Provides the base implementation of a Servlet for processing WebSocket
46
 * connections as per RFC6455. It is expected that applications will extend this
47
 * implementation and provide application specific functionality.
48
 */
49
public abstract class WebSocketServlet extends HttpServlet implements CometProcessor {
50
51
    private static final long serialVersionUID = 1L;
52
    private static final byte[] WS_ACCEPT =
53
            "258EAFA5-E914-47DA-95CA-C5AB0DC85B11".getBytes(
54
                    B2CConverter.ISO_8859_1);
55
56
    private MessageDigest sha1Helper;
57
58
59
    @Override
60
    public void init() throws ServletException {
61
        super.init();
62
63
        try {
64
            sha1Helper = MessageDigest.getInstance("SHA1");
65
        } catch (NoSuchAlgorithmException e) {
66
            throw new ServletException(e);
67
        }
68
    }
69
    
70
    @Override
71
    public void event(CometEvent event) throws IOException, ServletException {
72
        try {
73
            if (event.getEventType() == CometEvent.EventType.BEGIN) {
74
                doBegin(event);
75
            } else if (event.getEventType() == CometEvent.EventType.READ) {
76
                doRead(event);
77
            } else if (event.getEventType() == CometEvent.EventType.END) {
78
                event.close();
79
            } else if (event.getEventType() == CometEvent.EventType.ERROR) {
80
                event.close();
81
            }
82
        } catch (IOException x) {
83
            event.close();
84
        } catch (ServletException x) {
85
            event.close();
86
        }
87
    }
88
    
89
    private Object getField(String name, Object o) throws IllegalAccessException, NoSuchFieldException{
90
        Field f = o.getClass().getDeclaredField(name);
91
        f.setAccessible(true);
92
        Object result = f.get(o);
93
        f.setAccessible(false);
94
        return result;
95
    }
96
    
97
    protected CometEvent getCometEvent(HttpServletRequest req, HttpServletResponse resp) throws ServletException {
98
        try {
99
            Request request = (Request)getField("request", req);
100
            Response response = (Response)getField("response",resp);
101
            return new CometEventImpl(request, response);
102
        }catch (Exception x) {
103
            throw new ServletException(x);
104
        }
105
    }
106
    
107
    @Override
108
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
109
        CometEvent event = getCometEvent(req,resp);
110
        try {
111
            doBegin(event);
112
            CometEvent.EventType result = CometEvent.EventType.READ;
113
            while (result == CometEvent.EventType.READ) {
114
                result = doRead(event);
115
            }
116
        } catch (Exception x) {
117
            if (event!=null) event.close();
118
            throw new ServletException(x);
119
        }
120
    }
121
    
122
    protected CometEvent.EventType doRead(CometEvent event)
123
            throws ServletException, IOException {
124
        StreamInbound inbound = (StreamInbound)event.getHttpServletRequest().getAttribute("org.apache.catalina.comet.inbound");
125
        final CometEvent.EventType result = inbound.onData();
126
        if (result != CometEvent.EventType.READ) {
127
            event.close();
128
        }
129
        return result;
130
        
131
    }
132
133
134
    protected void doBegin(CometEvent event)
135
            throws ServletException, IOException {
136
137
        HttpServletRequest req = event.getHttpServletRequest();
138
        HttpServletResponse resp = event.getHttpServletResponse();
139
        
140
        // Information required to send the server handshake message
141
        String key;
142
        String subProtocol = null;
143
        List<String> extensions = Collections.emptyList();
144
145
        if (!headerContainsToken(req, "upgrade", "websocket")) {
146
            resp.sendError(HttpServletResponse.SC_BAD_REQUEST);
147
            return;
148
        }
149
150
        if (!headerContainsToken(req, "connection", "upgrade")) {
151
            resp.sendError(HttpServletResponse.SC_BAD_REQUEST);
152
            return;
153
        }
154
155
        if (!headerContainsToken(req, "sec-websocket-version", "13")) {
156
            resp.setStatus(426);
157
            resp.setHeader("Sec-WebSocket-Version", "13");
158
            return;
159
        }
160
161
        key = req.getHeader("Sec-WebSocket-Key");
162
        if (key == null) {
163
            resp.sendError(HttpServletResponse.SC_BAD_REQUEST);
164
            return;
165
        }
166
167
        String origin = req.getHeader("Origin");
168
        if (!verifyOrigin(origin)) {
169
            resp.sendError(HttpServletResponse.SC_FORBIDDEN);
170
            return;
171
        }
172
173
        List<String> subProtocols = getTokensFromHeader(req,
174
                "Sec-WebSocket-Protocol-Client");
175
        if (!subProtocols.isEmpty()) {
176
            subProtocol = selectSubProtocol(subProtocols);
177
178
        }
179
180
        // TODO Read client handshake - Sec-WebSocket-Extensions
181
182
        // TODO Extensions require the ability to specify something (API TBD)
183
        //      that can be passed to the Tomcat internals and process extension
184
        //      data present when the frame is fragmented.
185
186
        // If we got this far, all is good. Accept the connection.
187
        resp.setHeader("upgrade", "websocket");
188
        resp.setHeader("connection", "upgrade");
189
        resp.setHeader("Sec-WebSocket-Accept", getWebSocketAccept(key));
190
        if (subProtocol != null) {
191
            resp.setHeader("Sec-WebSocket-Protocol", subProtocol);
192
        }
193
        if (!extensions.isEmpty()) {
194
            // TODO
195
        }
196
        
197
        resp.setStatus(HttpServletResponse.SC_SWITCHING_PROTOCOLS);
198
        resp.flushBuffer();
199
200
        //here we need to stop using filters
201
        //TODO - stop using filters
202
        try {
203
            ((Request)getField("request",event)).protocolSwitch();
204
        }catch (Exception x) {
205
            throw new ServletException(x);
206
        }
207
        
208
        
209
        // Small hack until the Servlet API provides a way to do this.
210
        StreamInbound inbound = createWebSocketInbound(subProtocol);
211
        inbound.setUpgradeProcessor(event);
212
        inbound.setUpgradeOutbound(event);
213
        req.setAttribute("org.apache.catalina.comet.inbound", inbound);
214
    }
215
    
216
    
217
    /*
218
     * This only works for tokens. Quoted strings need more sophisticated
219
     * parsing.
220
     */
221
    private boolean headerContainsToken(HttpServletRequest req,
222
            String headerName, String target) {
223
        Enumeration<String> headers = req.getHeaders(headerName);
224
        while (headers.hasMoreElements()) {
225
            String header = headers.nextElement();
226
            String[] tokens = header.split(",");
227
            for (String token : tokens) {
228
                if (target.equalsIgnoreCase(token.trim())) {
229
                    return true;
230
                }
231
            }
232
        }
233
        return true;
234
    }
235
236
237
    /*
238
     * This only works for tokens. Quoted strings need more sophisticated
239
     * parsing.
240
     */
241
    private List<String> getTokensFromHeader(HttpServletRequest req,
242
            String headerName) {
243
        List<String> result = new ArrayList<String>();
244
245
        Enumeration<String> headers = req.getHeaders(headerName);
246
        while (headers.hasMoreElements()) {
247
            String header = headers.nextElement();
248
            String[] tokens = header.split(",");
249
            for (String token : tokens) {
250
                result.add(token.trim());
251
            }
252
        }
253
        return result;
254
    }
255
256
257
    private String getWebSocketAccept(String key) {
258
        synchronized (sha1Helper) {
259
            sha1Helper.reset();
260
            sha1Helper.update(key.getBytes(B2CConverter.ISO_8859_1));
261
            return new String(Base64.encode(sha1Helper.digest(WS_ACCEPT)), B2CConverter.ISO_8859_1); 
262
        }
263
    }
264
265
266
    /**
267
     * Intended to be overridden by sub-classes that wish to verify the origin
268
     * of a WebSocket request before processing it.
269
     *
270
     * @param origin    The value of the origin header from the request which
271
     *                  may be <code>null</code>
272
     *
273
     * @return  <code>true</code> to accept the request. <code>false</code> to
274
     *          reject it. This default implementation always returns
275
     *          <code>true</code>.
276
     */
277
    protected boolean verifyOrigin(String origin) {
278
        return true;
279
    }
280
281
282
    /**
283
     * Intended to be overridden by sub-classes that wish to select a
284
     * sub-protocol if the client provides a list of supported protocols.
285
     *
286
     * @param subProtocols  The list of sub-protocols supported by the client
287
     *                      in client preference order. The server is under no
288
     *                      obligation to respect the declared preference
289
     * @return  <code>null</code> if no sub-protocol is selected or the name of
290
     *          the protocol which <b>must</b> be one of the protocols listed by
291
     *          the client. This default implementation always returns
292
     *          <code>null</code>.
293
     */
294
    protected String selectSubProtocol(List<String> subProtocols) {
295
        return null;
296
    }
297
298
299
    /**
300
     * Create the instance that will process this inbound connection.
301
     * Applications must provide a new instance for each connection.
302
     *
303
     * @param subProtocol   The sub-protocol agreed between the client and
304
     *                      server or <code>null</code> if none was agreed
305
     * @return
306
     */
307
    protected abstract StreamInbound createWebSocketInbound(String subProtocol);
308
}
(-)java/org/apache/catalina/websocket/Constants.java (+121 lines)
Line 0 Link Here
1
/*
2
 * Licensed to the Apache Software Foundation (ASF) under one or more
3
 * contributor license agreements.  See the NOTICE file distributed with
4
 * this work for additional information regarding copyright ownership.
5
 * The ASF licenses this file to You under the Apache License, Version 2.0
6
 * (the "License"); you may not use this file except in compliance with
7
 * the License.  You may obtain a copy of the License at
8
 *
9
 *      http://www.apache.org/licenses/LICENSE-2.0
10
 *
11
 * Unless required by applicable law or agreed to in writing, software
12
 * distributed under the License is distributed on an "AS IS" BASIS,
13
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
 * See the License for the specific language governing permissions and
15
 * limitations under the License.
16
 */
17
package org.apache.catalina.websocket;
18
19
/**
20
 * Constants for this Java package.
21
 */
22
public class Constants {
23
    public static final String Package = "org.apache.catalina.websocket";
24
25
    // OP Codes
26
    public static final byte OPCODE_CONTINUATION = 0x00;
27
    public static final byte OPCODE_TEXT = 0x01;
28
    public static final byte OPCODE_BINARY = 0x02;
29
    public static final byte OPCODE_CLOSE = 0x08;
30
    public static final byte OPCODE_PING = 0x09;
31
    public static final byte OPCODE_PONG = 0x0A;
32
33
    // Status Codes
34
    // Definitions as per RFC 6455 (http://tools.ietf.org/html/rfc6455)
35
    /**
36
     * 1000 indicates a normal closure, meaning whatever purpose the
37
     * connection was established for has been fulfilled.
38
     */
39
    public static final int STATUS_CLOSE_NORMAL = 1000;
40
41
    /**
42
     * 1001 indicates that an endpoint is "going away", such as a server
43
     * going down, or a browser having navigated away from a page.
44
     */
45
    public static final int STATUS_SHUTDOWN = 1001;
46
47
    /**
48
     * 1002 indicates that an endpoint is terminating the connection due
49
     * to a protocol error.
50
     */
51
    public static final int STATUS_PROTOCOL_ERROR = 1002;
52
53
    /**
54
     * 1003 indicates that an endpoint is terminating the connection
55
     * because it has received a type of data it cannot accept (e.g. an
56
     * endpoint that understands only text data MAY send this if it
57
     * receives a binary message).
58
     */
59
    public static final int STATUS_UNEXPECTED_DATA_TYPE = 1003;
60
61
    // 1004 is reserved. The specific meaning might be defined in the future.
62
63
    /**
64
     * 1005 is a reserved value and MUST NOT be set as a status code in a
65
     * Close control frame by an endpoint.  It is designated for use in
66
     * applications expecting a status code to indicate that no status
67
     * code was actually present.
68
     */
69
    public static final int STATUS_CODE_MISSING = 1005;
70
71
    /**
72
     * 1006 is a reserved value and MUST NOT be set as a status code in a
73
     * Close control frame by an endpoint.  It is designated for use in
74
     * applications expecting a status code to indicate that the
75
     * connection was closed abnormally, e.g. without sending or
76
     * receiving a Close control frame.
77
     */
78
    public static final int STATUS_CLOSED_UNEXPECTEDLY = 1006;
79
80
    /**
81
     * 1007 indicates that an endpoint is terminating the connection
82
     * because it has received data within a message that was not
83
     * consistent with the type of the message (e.g., non-UTF-8 [RFC3629]
84
     * data within a text message).
85
     */
86
    public static final int STATUS_BAD_DATA = 1007;
87
88
    /**
89
     * 1008 indicates that an endpoint is terminating the connection
90
     * because it has received a message that violates its policy.  This
91
     * is a generic status code that can be returned when there is no
92
     * other more suitable status code (e.g. 1003 or 1009), or if there
93
     * is a need to hide specific details about the policy.
94
     */
95
    public static final int STATUS_POLICY_VIOLATION = 1008;
96
97
    /**
98
     * 1009 indicates that an endpoint is terminating the connection
99
     * because it has received a message which is too big for it to
100
     * process.
101
     */
102
    public static final int STATUS_MESSAGE_TOO_LARGE = 1009;
103
104
    /**
105
     * 1010 indicates that an endpoint (client) is terminating the
106
     * connection because it has expected the server to negotiate one or
107
     * more extension, but the server didn't return them in the response
108
     * message of the WebSocket handshake.  The list of extensions which
109
     * are needed SHOULD appear in the /reason/ part of the Close frame.
110
     * Note that this status code is not used by the server, because it
111
     * can fail the WebSocket handshake instead.
112
     */
113
    public static final int STATUS_REQUIRED_EXTENSION = 1010;
114
115
    /**
116
     * 1011 indicates that a server is terminating the connection because it
117
     * encountered an unexpected condition that prevented it from fulfilling the
118
     * request.
119
     */
120
    public static final int STATUS_UNEXPECTED_CONDITION = 1011;
121
}
(-)java/org/apache/catalina/websocket/Utf8Decoder.java (+207 lines)
Line 0 Link Here
1
/*
2
 * Licensed to the Apache Software Foundation (ASF) under one or more
3
 * contributor license agreements.  See the NOTICE file distributed with
4
 * this work for additional information regarding copyright ownership.
5
 * The ASF licenses this file to You under the Apache License, Version 2.0
6
 * (the "License"); you may not use this file except in compliance with
7
 * the License.  You may obtain a copy of the License at
8
 *
9
 *      http://www.apache.org/licenses/LICENSE-2.0
10
 *
11
 * Unless required by applicable law or agreed to in writing, software
12
 * distributed under the License is distributed on an "AS IS" BASIS,
13
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
 * See the License for the specific language governing permissions and
15
 * limitations under the License.
16
 */
17
package org.apache.catalina.websocket;
18
19
import java.nio.ByteBuffer;
20
import java.nio.CharBuffer;
21
import java.nio.charset.CharsetDecoder;
22
import java.nio.charset.CoderResult;
23
24
import org.apache.tomcat.util.buf.B2CConverter;
25
26
/**
27
 * Decodes bytes to UTF-8. Extracted from Apache Harmony and modified to reject
28
 * code points from U+D800 to U+DFFF as per RFC3629. The standard Java decoder
29
 * does not reject these.
30
 */
31
public class Utf8Decoder extends CharsetDecoder {
32
33
    // The next table contains information about UTF-8 charset and
34
    // correspondence of 1st byte to the length of sequence
35
    // For information please visit http://www.ietf.org/rfc/rfc3629.txt
36
    //
37
    // Please note, o means 0, actually.
38
    // -------------------------------------------------------------------
39
    // 0         1         2         3          Value
40
    // -------------------------------------------------------------------
41
    // oxxxxxxx                                 00000000 00000000 0xxxxxxx
42
    // 11oyyyyy  1oxxxxxx                       00000000 00000yyy yyxxxxxx
43
    // 111ozzzz  1oyyyyyy  1oxxxxxx             00000000 zzzzyyyy yyxxxxxx
44
    // 1111ouuu  1ouuzzzz  1oyyyyyy  1oxxxxxx   000uuuuu zzzzyyyy yyxxxxxx
45
46
    private static final int remainingBytes[] = {
47
            // 1owwwwww
48
            -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
49
            -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
50
            -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
51
            -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
52
            // 11oyyyyy
53
            1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
54
            1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
55
            // 111ozzzz
56
            2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
57
            // 1111ouuu
58
            3, 3, 3, 3, 3, 3, 3, 3,
59
            // > 11110111
60
            -1, -1, -1, -1, -1, -1, -1, -1 };
61
62
    private static final int remainingNumbers[] = {
63
                   0, //                0                 1                2           3
64
                4224, // (01o00000b <<  6)+(1o000000b)
65
              401536, // (011o0000b << 12)+(1o000000b <<  6)+(1o000000b)
66
            29892736  // (0111o000b << 18)+(1o000000b << 12)+(1o000000b << 6)+(1o000000b)
67
    };
68
69
    private static final int lowerEncodingLimit[] = { -1, 0x80, 0x800, 0x10000 };
70
71
    public Utf8Decoder() {
72
        super(B2CConverter.UTF_8, 1.0f, 1.0f);
73
    }
74
75
    @Override
76
    protected CoderResult decodeLoop(ByteBuffer in, CharBuffer out) {
77
        if (in.hasArray() && out.hasArray()) {
78
            return decodeHasArray(in, out);
79
        }
80
        return decodeNotHasArray(in, out);
81
    }
82
83
    private CoderResult decodeNotHasArray(ByteBuffer in, CharBuffer out) {
84
        int outRemaining = out.remaining();
85
        int pos = in.position();
86
        int limit = in.limit();
87
        try {
88
            while (pos < limit) {
89
                if (outRemaining == 0) {
90
                    return CoderResult.OVERFLOW;
91
                }
92
93
                int jchar = in.get();
94
                if (jchar < 0) {
95
                    jchar = jchar & 0x7F;
96
                    int tail = remainingBytes[jchar];
97
                    if (tail == -1) {
98
                        return CoderResult.malformedForLength(1);
99
                    }
100
                    if (limit - pos < 1 + tail) {
101
                        return CoderResult.UNDERFLOW;
102
                    }
103
104
                    int nextByte;
105
                    for (int i = 0; i < tail; i++) {
106
                        nextByte = in.get() & 0xFF;
107
                        if ((nextByte & 0xC0) != 0x80) {
108
                            return CoderResult
109
                                    .malformedForLength(1 + i);
110
                        }
111
                        jchar = (jchar << 6) + nextByte;
112
                    }
113
                    jchar -= remainingNumbers[tail];
114
                    if (jchar < lowerEncodingLimit[tail]) {
115
                        // Should have been encoded in a fewer octets
116
                        return CoderResult.malformedForLength(1);
117
                    }
118
                    pos += tail;
119
                }
120
                if (jchar <= 0xffff) {
121
                  out.put((char) jchar);
122
                  outRemaining--;
123
                } else {
124
                  if (outRemaining < 2) {
125
                      return CoderResult.OVERFLOW;
126
                  }
127
                  out.put((char) ((jchar >> 0xA) + 0xD7C0));
128
                  out.put((char) ((jchar & 0x3FF) + 0xDC00));
129
                  outRemaining -= 2;
130
                }
131
                pos++;
132
            }
133
            return CoderResult.UNDERFLOW;
134
        } finally {
135
            in.position(pos);
136
        }
137
    }
138
139
    private CoderResult decodeHasArray(ByteBuffer in, CharBuffer out) {
140
        int outRemaining = out.remaining();
141
        int pos = in.position();
142
        int limit = in.limit();
143
        final byte[] bArr = in.array();
144
        final char[] cArr = out.array();
145
        final int inIndexLimit = limit + in.arrayOffset();
146
147
        int inIndex = pos + in.arrayOffset();
148
        int outIndex = out.position() + out.arrayOffset();
149
150
        // if someone would change the limit in process,
151
        // he would face consequences
152
        for (; inIndex < inIndexLimit && outRemaining > 0; inIndex++) {
153
            int jchar = bArr[inIndex];
154
            if (jchar < 0) {
155
                jchar = jchar & 0x7F;
156
                int tail = remainingBytes[jchar];
157
158
                if (tail == -1) {
159
                    in.position(inIndex - in.arrayOffset());
160
                    out.position(outIndex - out.arrayOffset());
161
                    return CoderResult.malformedForLength(1);
162
                }
163
                if (inIndexLimit - inIndex < 1 + tail) {
164
                    break;
165
                }
166
167
                for (int i = 0; i < tail; i++) {
168
                    int nextByte = bArr[inIndex + i + 1] & 0xFF;
169
                    if ((nextByte & 0xC0) != 0x80) {
170
                        in.position(inIndex - in.arrayOffset());
171
                        out.position(outIndex - out.arrayOffset());
172
                        return CoderResult.malformedForLength(1 + i);
173
                    }
174
                    jchar = (jchar << 6) + nextByte;
175
                }
176
                jchar -= remainingNumbers[tail];
177
                if (jchar < lowerEncodingLimit[tail]) {
178
                    // Should have been encoded in fewer octets
179
                    in.position(inIndex - in.arrayOffset());
180
                    out.position(outIndex - out.arrayOffset());
181
                    return CoderResult.malformedForLength(1);
182
                }
183
                inIndex += tail;
184
            }
185
            // Note: This is the additional test added
186
            if (jchar >= 0xD800 && jchar <=0xDFFF) {
187
                return CoderResult.unmappableForLength(3);
188
            }
189
            if (jchar <= 0xffff) {
190
              cArr[outIndex++] = (char) jchar;
191
              outRemaining--;
192
            } else {
193
              if (outRemaining < 2) {
194
                  return CoderResult.OVERFLOW;
195
              }
196
              cArr[outIndex++] = (char) ((jchar >> 0xA) + 0xD7C0);
197
              cArr[outIndex++] = (char) ((jchar & 0x3FF) + 0xDC00);
198
              outRemaining -= 2;
199
            }
200
        }
201
        in.position(inIndex - in.arrayOffset());
202
        out.position(outIndex - out.arrayOffset());
203
        return (outRemaining == 0 && inIndex < inIndexLimit) ?
204
                CoderResult.OVERFLOW :
205
                CoderResult.UNDERFLOW;
206
    }
207
}
(-)java/org/apache/catalina/connector/Request.java (+4 lines)
Lines 2393-2398 Link Here
2393
        coyoteRequest.action(ActionCode.ACTION_COMET_SETTIMEOUT,new Long(timeout));
2393
        coyoteRequest.action(ActionCode.ACTION_COMET_SETTIMEOUT,new Long(timeout));
2394
    }
2394
    }
2395
    
2395
    
2396
    public void protocolSwitch() {
2397
        coyoteRequest.action(ActionCode.ACTION_PROTOCOL_SWITCH, null);
2398
    }
2399
    
2396
    // ------------------------------------------------------ Protected Methods
2400
    // ------------------------------------------------------ Protected Methods
2397
2401
2398
2402
(-)java/org/apache/catalina/connector/CometEventImpl.java (+1 lines)
Lines 30-35 Link Here
30
30
31
public class CometEventImpl implements CometEvent {
31
public class CometEventImpl implements CometEvent {
32
32
33
    public static final int PROTOCOL_SWITCH_ID = -10;
33
34
34
    /**
35
    /**
35
     * The string manager for this package.
36
     * The string manager for this package.
(-)java/org/apache/catalina/util/Conversions.java (+42 lines)
Line 0 Link Here
1
/*
2
 * Licensed to the Apache Software Foundation (ASF) under one or more
3
 * contributor license agreements.  See the NOTICE file distributed with
4
 * this work for additional information regarding copyright ownership.
5
 * The ASF licenses this file to You under the Apache License, Version 2.0
6
 * (the "License"); you may not use this file except in compliance with
7
 * the License.  You may obtain a copy of the License at
8
 *
9
 *      http://www.apache.org/licenses/LICENSE-2.0
10
 *
11
 * Unless required by applicable law or agreed to in writing, software
12
 * distributed under the License is distributed on an "AS IS" BASIS,
13
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
 * See the License for the specific language governing permissions and
15
 * limitations under the License.
16
 */
17
package org.apache.catalina.util;
18
19
import java.io.IOException;
20
21
public class Conversions {
22
23
    private Conversions() {
24
        // Utility class. Hide default constructor.
25
    }
26
27
    public static long byteArrayToLong(byte[] input) throws IOException {
28
        if (input.length > 8) {
29
            // TODO: Better message
30
            throw new IOException();
31
        }
32
33
        int shift = 0;
34
        long result = 0;
35
        for (int i = input.length - 1; i >= 0; i--) {
36
            result = result + ((input[i] & 0xFF) << shift);
37
            shift += 8;
38
        }
39
40
        return result;
41
    }
42
}
(-)webapps/examples/WEB-INF/web.xml (+36 lines)
Lines 303-307 Link Here
303
      <env-entry-type>java.lang.Integer</env-entry-type>
303
      <env-entry-type>java.lang.Integer</env-entry-type>
304
      <env-entry-value>10</env-entry-value>
304
      <env-entry-value>10</env-entry-value>
305
    </env-entry>
305
    </env-entry>
306
    <!-- WebSocket Examples -->
307
    <servlet>
308
      <servlet-name>wsEchoStream</servlet-name>
309
      <servlet-class>websocket.EchoStream</servlet-class>
310
    </servlet>
311
    <servlet-mapping>
312
      <servlet-name>wsEchoStream</servlet-name>
313
      <url-pattern>/websocket/echoStream</url-pattern>
314
    </servlet-mapping>
315
    <servlet>
316
      <servlet-name>wsEchoMessage</servlet-name>
317
      <servlet-class>websocket.EchoMessage</servlet-class>
318
      <!-- Uncomment the following block to increase the default maximum
319
           WebSocket buffer size from 2MB to 20MB which is required for the
320
           Autobahn test suite to pass fully. -->
321
      <init-param>
322
        <param-name>byteBufferMaxSize</param-name>
323
        <param-value>20971520</param-value>
324
      </init-param>
325
      <init-param>
326
        <param-name>charBufferMaxSize</param-name>
327
        <param-value>20971520</param-value>
328
      </init-param>
329
    </servlet>
330
    <servlet-mapping>
331
      <servlet-name>wsEchoMessage</servlet-name>
332
      <url-pattern>/websocket/echoMessage</url-pattern>
333
    </servlet-mapping>
334
    <servlet>
335
      <servlet-name>wsSnake</servlet-name>
336
      <servlet-class>websocket.snake.SnakeWebSocketServlet</servlet-class>
337
    </servlet>
338
    <servlet-mapping>
339
      <servlet-name>wsSnake</servlet-name>
340
      <url-pattern>/websocket/snake</url-pattern>
341
    </servlet-mapping>
306
342
307
</web-app>
343
</web-app>
(-)webapps/examples/WEB-INF/classes/websocket/snake/Direction.java (+21 lines)
Line 0 Link Here
1
/*
2
 * Licensed to the Apache Software Foundation (ASF) under one or more
3
 * contributor license agreements.  See the NOTICE file distributed with
4
 * this work for additional information regarding copyright ownership.
5
 * The ASF licenses this file to You under the Apache License, Version 2.0
6
 * (the "License"); you may not use this file except in compliance with
7
 * the License.  You may obtain a copy of the License at
8
 *
9
 *      http://www.apache.org/licenses/LICENSE-2.0
10
 *
11
 * Unless required by applicable law or agreed to in writing, software
12
 * distributed under the License is distributed on an "AS IS" BASIS,
13
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
 * See the License for the specific language governing permissions and
15
 * limitations under the License.
16
 */
17
package websocket.snake;
18
19
public enum Direction {
20
    NONE, NORTH, SOUTH, EAST, WEST
21
}
(-)webapps/examples/WEB-INF/classes/websocket/snake/Snake.java (+134 lines)
Line 0 Link Here
1
/*
2
 * Licensed to the Apache Software Foundation (ASF) under one or more
3
 * contributor license agreements.  See the NOTICE file distributed with
4
 * this work for additional information regarding copyright ownership.
5
 * The ASF licenses this file to You under the Apache License, Version 2.0
6
 * (the "License"); you may not use this file except in compliance with
7
 * the License.  You may obtain a copy of the License at
8
 *
9
 *      http://www.apache.org/licenses/LICENSE-2.0
10
 *
11
 * Unless required by applicable law or agreed to in writing, software
12
 * distributed under the License is distributed on an "AS IS" BASIS,
13
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
 * See the License for the specific language governing permissions and
15
 * limitations under the License.
16
 */
17
package websocket.snake;
18
19
import java.io.IOException;
20
import java.nio.CharBuffer;
21
import java.util.ArrayDeque;
22
import java.util.Collection;
23
import java.util.Deque;
24
25
import org.apache.catalina.websocket.WsOutbound;
26
27
public class Snake {
28
29
    private static final int DEFAULT_LENGTH = 5;
30
31
    private final int id;
32
    private final WsOutbound outbound;
33
34
    private Direction direction;
35
    private int length = DEFAULT_LENGTH;
36
    private Location head;
37
    private Deque<Location> tail = new ArrayDeque<Location>();
38
    private String hexColor;
39
40
    public Snake(int id, WsOutbound outbound) {
41
        this.id = id;
42
        this.outbound = outbound;
43
        this.hexColor = SnakeWebSocketServlet.getRandomHexColor();
44
        resetState();
45
    }
46
47
    private void resetState() {
48
        this.direction = Direction.NONE;
49
        this.head = SnakeWebSocketServlet.getRandomLocation();
50
        this.tail.clear();
51
        this.length = DEFAULT_LENGTH;
52
    }
53
54
    private synchronized void kill() {
55
        resetState();
56
        try {
57
            CharBuffer response = CharBuffer.wrap("{'type': 'dead'}");
58
            outbound.writeTextMessage(response);
59
        } catch (IOException ioe) {
60
            // Ignore
61
        }
62
    }
63
64
    private synchronized void reward() {
65
        length++;
66
        try {
67
            CharBuffer response = CharBuffer.wrap("{'type': 'kill'}");
68
            outbound.writeTextMessage(response);
69
        } catch (IOException ioe) {
70
            // Ignore
71
        }
72
    }
73
74
    public synchronized void update(Collection<Snake> snakes) {
75
        Location nextLocation = head.getAdjacentLocation(direction);
76
        if (nextLocation.x >= SnakeWebSocketServlet.PLAYFIELD_WIDTH) {
77
            nextLocation.x = 0;
78
        }
79
        if (nextLocation.y >= SnakeWebSocketServlet.PLAYFIELD_HEIGHT) {
80
            nextLocation.y = 0;
81
        }
82
        if (nextLocation.x < 0) {
83
            nextLocation.x = SnakeWebSocketServlet.PLAYFIELD_WIDTH;
84
        }
85
        if (nextLocation.y < 0) {
86
            nextLocation.y = SnakeWebSocketServlet.PLAYFIELD_HEIGHT;
87
        }
88
        if (direction != Direction.NONE) {
89
            tail.addFirst(head);
90
            if (tail.size() > length) {
91
                tail.removeLast();
92
            }
93
            head = nextLocation;
94
        }
95
96
        for (Snake snake : snakes) {
97
            if (snake.getTail().contains(head)) {
98
                kill();
99
                if (id != snake.id) {
100
                    snake.reward();
101
                }
102
            }
103
        }
104
    }
105
106
    public synchronized Collection<Location> getTail() {
107
        return tail;
108
    }
109
110
    public synchronized void setDirection(Direction direction) {
111
        this.direction = direction;
112
    }
113
114
    public synchronized String getLocationsJson() {
115
        StringBuilder sb = new StringBuilder();
116
        sb.append(String.format("{x: %d, y: %d}",
117
                Integer.valueOf(head.x), Integer.valueOf(head.y)));
118
        for (Location location : tail) {
119
            sb.append(',');
120
            sb.append(String.format("{x: %d, y: %d}",
121
                    Integer.valueOf(location.x), Integer.valueOf(location.y)));
122
        }
123
        return String.format("{'id':%d,'body':[%s]}",
124
                Integer.valueOf(id), sb.toString());
125
    }
126
127
    public int getId() {
128
        return id;
129
    }
130
131
    public String getHexColor() {
132
        return hexColor;
133
    }
134
}
(-)webapps/examples/WEB-INF/classes/websocket/snake/Location.java (+65 lines)
Line 0 Link Here
1
/*
2
 * Licensed to the Apache Software Foundation (ASF) under one or more
3
 * contributor license agreements.  See the NOTICE file distributed with
4
 * this work for additional information regarding copyright ownership.
5
 * The ASF licenses this file to You under the Apache License, Version 2.0
6
 * (the "License"); you may not use this file except in compliance with
7
 * the License.  You may obtain a copy of the License at
8
 *
9
 *      http://www.apache.org/licenses/LICENSE-2.0
10
 *
11
 * Unless required by applicable law or agreed to in writing, software
12
 * distributed under the License is distributed on an "AS IS" BASIS,
13
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
 * See the License for the specific language governing permissions and
15
 * limitations under the License.
16
 */
17
package websocket.snake;
18
19
public class Location {
20
21
    public int x;
22
    public int y;
23
24
    public Location(int x, int y) {
25
        this.x = x;
26
        this.y = y;
27
    }
28
29
    public Location getAdjacentLocation(Direction direction) {
30
        switch (direction) {
31
            case NORTH:
32
                return new Location(x, y - SnakeWebSocketServlet.GRID_SIZE);
33
            case SOUTH:
34
                return new Location(x, y + SnakeWebSocketServlet.GRID_SIZE);
35
            case EAST:
36
                return new Location(x + SnakeWebSocketServlet.GRID_SIZE, y);
37
            case WEST:
38
                return new Location(x - SnakeWebSocketServlet.GRID_SIZE, y);
39
            case NONE:
40
                // fall through
41
            default:
42
                return this;
43
        }
44
    }
45
46
    @Override
47
    public boolean equals(Object o) {
48
        if (this == o) return true;
49
        if (o == null || getClass() != o.getClass()) return false;
50
51
        Location location = (Location) o;
52
53
        if (x != location.x) return false;
54
        if (y != location.y) return false;
55
56
        return true;
57
    }
58
59
    @Override
60
    public int hashCode() {
61
        int result = x;
62
        result = 31 * result + y;
63
        return result;
64
    }
65
}
(-)webapps/examples/WEB-INF/classes/websocket/snake/SnakeWebSocketServlet.java (+211 lines)
Line 0 Link Here
1
/*
2
 * Licensed to the Apache Software Foundation (ASF) under one or more
3
 * contributor license agreements.  See the NOTICE file distributed with
4
 * this work for additional information regarding copyright ownership.
5
 * The ASF licenses this file to You under the Apache License, Version 2.0
6
 * (the "License"); you may not use this file except in compliance with
7
 * the License.  You may obtain a copy of the License at
8
 *
9
 *      http://www.apache.org/licenses/LICENSE-2.0
10
 *
11
 * Unless required by applicable law or agreed to in writing, software
12
 * distributed under the License is distributed on an "AS IS" BASIS,
13
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
 * See the License for the specific language governing permissions and
15
 * limitations under the License.
16
 */
17
package websocket.snake;
18
19
import java.awt.Color;
20
import java.io.IOException;
21
import java.nio.ByteBuffer;
22
import java.nio.CharBuffer;
23
import java.util.Collection;
24
import java.util.Collections;
25
import java.util.Iterator;
26
import java.util.Random;
27
import java.util.Timer;
28
import java.util.TimerTask;
29
import java.util.concurrent.ConcurrentHashMap;
30
import java.util.concurrent.atomic.AtomicInteger;
31
32
import javax.servlet.ServletException;
33
34
import org.apache.catalina.websocket.MessageInbound;
35
import org.apache.catalina.websocket.StreamInbound;
36
import org.apache.catalina.websocket.WebSocketServlet;
37
import org.apache.catalina.websocket.WsOutbound;
38
import org.apache.juli.logging.Log;
39
import org.apache.juli.logging.LogFactory;
40
41
/**
42
 * Example web socket servlet for simple multiplayer snake.
43
 */
44
public class SnakeWebSocketServlet extends WebSocketServlet {
45
46
    private static final long serialVersionUID = 1L;
47
48
    private static final Log log =
49
            LogFactory.getLog(SnakeWebSocketServlet.class);
50
51
    public static final int PLAYFIELD_WIDTH = 640;
52
    public static final int PLAYFIELD_HEIGHT = 480;
53
    public static final int GRID_SIZE = 10;
54
55
    private static final long TICK_DELAY = 100;
56
57
    private static final Random random = new Random();
58
59
    private final Timer gameTimer =
60
            new Timer(SnakeWebSocketServlet.class.getSimpleName() + " Timer");
61
62
    private final AtomicInteger connectionIds = new AtomicInteger(0);
63
    private final ConcurrentHashMap<Integer, Snake> snakes =
64
            new ConcurrentHashMap<Integer, Snake>();
65
    private final ConcurrentHashMap<Integer, SnakeMessageInbound> connections =
66
            new ConcurrentHashMap<Integer, SnakeMessageInbound>();
67
68
    @Override
69
    public void init() throws ServletException {
70
        super.init();
71
        gameTimer.scheduleAtFixedRate(new TimerTask() {
72
            @Override
73
            public void run() {
74
                try {
75
                    tick();
76
                } catch (RuntimeException e) {
77
                    log.error("Caught to prevent timer from shutting down", e);
78
                }
79
            }
80
        }, TICK_DELAY, TICK_DELAY);
81
    }
82
83
    private void tick() {
84
        StringBuilder sb = new StringBuilder();
85
        for (Iterator<Snake> iterator = getSnakes().iterator();
86
                iterator.hasNext();) {
87
            Snake snake = iterator.next();
88
            snake.update(getSnakes());
89
            sb.append(snake.getLocationsJson());
90
            if (iterator.hasNext()) {
91
                sb.append(',');
92
            }
93
        }
94
        broadcast(String.format("{'type': 'update', 'data' : [%s]}",
95
                sb.toString()));
96
    }
97
98
    private void broadcast(String message) {
99
        for (SnakeMessageInbound connection : getConnections()) {
100
            try {
101
                CharBuffer response = CharBuffer.wrap(message);
102
                connection.getWsOutbound().writeTextMessage(response);
103
            } catch (IOException ignore) {
104
                // Ignore
105
            }
106
        }
107
    }
108
109
    private Collection<SnakeMessageInbound> getConnections() {
110
        return Collections.unmodifiableCollection(connections.values());
111
    }
112
113
    private Collection<Snake> getSnakes() {
114
        return Collections.unmodifiableCollection(snakes.values());
115
    }
116
117
    public static String getRandomHexColor() {
118
        float hue = random.nextFloat();
119
        // sat between 0.1 and 0.3
120
        float saturation = (random.nextInt(2000) + 1000) / 10000f;
121
        float luminance = 0.9f;
122
        Color color = Color.getHSBColor(hue, saturation, luminance);
123
        return '#' + Integer.toHexString(
124
                (color.getRGB() & 0xffffff) | 0x1000000).substring(1);
125
    }
126
127
    public static Location getRandomLocation() {
128
        int x = roundByGridSize(
129
                random.nextInt(SnakeWebSocketServlet.PLAYFIELD_WIDTH));
130
        int y = roundByGridSize(
131
                random.nextInt(SnakeWebSocketServlet.PLAYFIELD_HEIGHT));
132
        return new Location(x, y);
133
    }
134
135
    private static int roundByGridSize(int value) {
136
        value = value + (SnakeWebSocketServlet.GRID_SIZE / 2);
137
        value = value / SnakeWebSocketServlet.GRID_SIZE;
138
        value = value * SnakeWebSocketServlet.GRID_SIZE;
139
        return value;
140
    }
141
142
    @Override
143
    public void destroy() {
144
        super.destroy();
145
        if (gameTimer != null) {
146
            gameTimer.cancel();
147
        }
148
    }
149
150
    @Override
151
    protected StreamInbound createWebSocketInbound(String subProtocol) {
152
        return new SnakeMessageInbound(connectionIds.incrementAndGet());
153
    }
154
155
    private final class SnakeMessageInbound extends MessageInbound {
156
157
        private final int id;
158
        private Snake snake;
159
160
        private SnakeMessageInbound(int id) {
161
            this.id = id;
162
        }
163
164
        @Override
165
        protected void onOpen(WsOutbound outbound) {
166
            this.snake = new Snake(id, outbound);
167
            snakes.put(Integer.valueOf(id), snake);
168
            connections.put(Integer.valueOf(id), this);
169
            StringBuilder sb = new StringBuilder();
170
            for (Iterator<Snake> iterator = getSnakes().iterator();
171
                    iterator.hasNext();) {
172
                Snake snake = iterator.next();
173
                sb.append(String.format("{id: %d, color: '%s'}",
174
                        Integer.valueOf(snake.getId()), snake.getHexColor()));
175
                if (iterator.hasNext()) {
176
                    sb.append(',');
177
                }
178
            }
179
            broadcast(String.format("{'type': 'join','data':[%s]}",
180
                    sb.toString()));
181
        }
182
183
        @Override
184
        protected void onClose(int status) {
185
            connections.remove(Integer.valueOf(id));
186
            snakes.remove(Integer.valueOf(id));
187
            broadcast(String.format("{'type': 'leave', 'id': %d}",
188
                    Integer.valueOf(id)));
189
        }
190
191
        @Override
192
        protected void onBinaryMessage(ByteBuffer message) throws IOException {
193
            throw new UnsupportedOperationException(
194
                    "Binary message not supported.");
195
        }
196
197
        @Override
198
        protected void onTextMessage(CharBuffer charBuffer) throws IOException {
199
            String message = charBuffer.toString();
200
            if ("west".equals(message)) {
201
                snake.setDirection(Direction.WEST);
202
            } else if ("north".equals(message)) {
203
                snake.setDirection(Direction.NORTH);
204
            } else if ("east".equals(message)) {
205
                snake.setDirection(Direction.EAST);
206
            } else if ("south".equals(message)) {
207
                snake.setDirection(Direction.SOUTH);
208
            }
209
        }
210
    }
211
}
(-)webapps/examples/WEB-INF/classes/websocket/EchoMessage.java (+78 lines)
Line 0 Link Here
1
/*
2
 * Licensed to the Apache Software Foundation (ASF) under one or more
3
 * contributor license agreements.  See the NOTICE file distributed with
4
 * this work for additional information regarding copyright ownership.
5
 * The ASF licenses this file to You under the Apache License, Version 2.0
6
 * (the "License"); you may not use this file except in compliance with
7
 * the License.  You may obtain a copy of the License at
8
 *
9
 *      http://www.apache.org/licenses/LICENSE-2.0
10
 *
11
 * Unless required by applicable law or agreed to in writing, software
12
 * distributed under the License is distributed on an "AS IS" BASIS,
13
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
 * See the License for the specific language governing permissions and
15
 * limitations under the License.
16
 */
17
package websocket;
18
19
import java.io.IOException;
20
import java.nio.ByteBuffer;
21
import java.nio.CharBuffer;
22
23
import javax.servlet.ServletException;
24
25
import org.apache.catalina.websocket.MessageInbound;
26
import org.apache.catalina.websocket.StreamInbound;
27
import org.apache.catalina.websocket.WebSocketServlet;
28
29
30
public class EchoMessage extends WebSocketServlet {
31
32
    private static final long serialVersionUID = 1L;
33
    private volatile int byteBufSize;
34
    private volatile int charBufSize;
35
36
    @Override
37
    public void init() throws ServletException {
38
        super.init();
39
        byteBufSize = getInitParameterIntValue("byteBufferMaxSize", 2097152);
40
        charBufSize = getInitParameterIntValue("charBufferMaxSize", 2097152);
41
    }
42
43
    public int getInitParameterIntValue(String name, int defaultValue) {
44
        String val = this.getInitParameter(name);
45
        int result = defaultValue;
46
        try {
47
            result = Integer.parseInt(val);
48
        }catch (Exception x) {
49
        }
50
        return result;
51
    }
52
53
54
55
    @Override
56
    protected StreamInbound createWebSocketInbound(String subProtocol) {
57
        return new EchoMessageInbound(byteBufSize,charBufSize);
58
    }
59
60
    private static final class EchoMessageInbound extends MessageInbound {
61
62
        public EchoMessageInbound(int byteBufferMaxSize, int charBufferMaxSize) {
63
            super();
64
            setByteBufferMaxSize(byteBufferMaxSize);
65
            setCharBufferMaxSize(charBufferMaxSize);
66
        }
67
68
        @Override
69
        protected void onBinaryMessage(ByteBuffer message) throws IOException {
70
            getWsOutbound().writeBinaryMessage(message);
71
        }
72
73
        @Override
74
        protected void onTextMessage(CharBuffer message) throws IOException {
75
            getWsOutbound().writeTextMessage(message);
76
        }
77
    }
78
}
(-)webapps/examples/WEB-INF/classes/websocket/EchoStream.java (+67 lines)
Line 0 Link Here
1
/*
2
 * Licensed to the Apache Software Foundation (ASF) under one or more
3
 * contributor license agreements.  See the NOTICE file distributed with
4
 * this work for additional information regarding copyright ownership.
5
 * The ASF licenses this file to You under the Apache License, Version 2.0
6
 * (the "License"); you may not use this file except in compliance with
7
 * the License.  You may obtain a copy of the License at
8
 *
9
 *      http://www.apache.org/licenses/LICENSE-2.0
10
 *
11
 * Unless required by applicable law or agreed to in writing, software
12
 * distributed under the License is distributed on an "AS IS" BASIS,
13
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
 * See the License for the specific language governing permissions and
15
 * limitations under the License.
16
 */
17
package websocket;
18
19
import java.io.IOException;
20
import java.io.InputStream;
21
import java.io.Reader;
22
23
import org.apache.catalina.websocket.StreamInbound;
24
import org.apache.catalina.websocket.WebSocketServlet;
25
import org.apache.catalina.websocket.WsOutbound;
26
27
28
public class EchoStream extends WebSocketServlet {
29
30
    private static final long serialVersionUID = 1L;
31
32
    @Override
33
    protected StreamInbound createWebSocketInbound(String subProtocol) {
34
        return new EchoStreamInbound();
35
    }
36
37
    private static final class EchoStreamInbound extends StreamInbound {
38
39
        @Override
40
        protected void onBinaryData(InputStream is) throws IOException {
41
            // Simply echo the data to back to the client.
42
            WsOutbound outbound = getWsOutbound();
43
44
            int i = is.read();
45
            while (i != -1) {
46
                outbound.writeBinaryData(i);
47
                i = is.read();
48
            }
49
50
            outbound.flush();
51
        }
52
53
        @Override
54
        protected void onTextData(Reader r) throws IOException {
55
            // Simply echo the data to back to the client.
56
            WsOutbound outbound = getWsOutbound();
57
58
            int c = r.read();
59
            while (c != -1) {
60
                outbound.writeTextData((char) c);
61
                c = r.read();
62
            }
63
64
            outbound.flush();
65
        }
66
    }
67
}

Return to bug 52918