init
This commit is contained in:
151
java/org/apache/tomcat/websocket/AsyncChannelGroupUtil.java
Normal file
151
java/org/apache/tomcat/websocket/AsyncChannelGroupUtil.java
Normal file
@@ -0,0 +1,151 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.apache.tomcat.websocket;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.channels.AsynchronousChannelGroup;
|
||||
import java.security.AccessController;
|
||||
import java.security.PrivilegedAction;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.SynchronousQueue;
|
||||
import java.util.concurrent.ThreadFactory;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
import org.apache.tomcat.util.res.StringManager;
|
||||
import org.apache.tomcat.util.threads.ThreadPoolExecutor;
|
||||
|
||||
/**
|
||||
* This is a utility class that enables multiple {@link WsWebSocketContainer}
|
||||
* instances to share a single {@link AsynchronousChannelGroup} while ensuring
|
||||
* that the group is destroyed when no longer required.
|
||||
*/
|
||||
public class AsyncChannelGroupUtil {
|
||||
|
||||
private static final StringManager sm =
|
||||
StringManager.getManager(AsyncChannelGroupUtil.class);
|
||||
|
||||
private static AsynchronousChannelGroup group = null;
|
||||
private static int usageCount = 0;
|
||||
private static final Object lock = new Object();
|
||||
|
||||
|
||||
private AsyncChannelGroupUtil() {
|
||||
// Hide the default constructor
|
||||
}
|
||||
|
||||
|
||||
public static AsynchronousChannelGroup register() {
|
||||
synchronized (lock) {
|
||||
if (usageCount == 0) {
|
||||
group = createAsynchronousChannelGroup();
|
||||
}
|
||||
usageCount++;
|
||||
return group;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public static void unregister() {
|
||||
synchronized (lock) {
|
||||
usageCount--;
|
||||
if (usageCount == 0) {
|
||||
group.shutdown();
|
||||
group = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private static AsynchronousChannelGroup createAsynchronousChannelGroup() {
|
||||
// Need to do this with the right thread context class loader else the
|
||||
// first web app to call this will trigger a leak
|
||||
ClassLoader original = Thread.currentThread().getContextClassLoader();
|
||||
|
||||
try {
|
||||
Thread.currentThread().setContextClassLoader(
|
||||
AsyncIOThreadFactory.class.getClassLoader());
|
||||
|
||||
// These are the same settings as the default
|
||||
// AsynchronousChannelGroup
|
||||
int initialSize = Runtime.getRuntime().availableProcessors();
|
||||
ExecutorService executorService = new ThreadPoolExecutor(
|
||||
0,
|
||||
Integer.MAX_VALUE,
|
||||
Long.MAX_VALUE, TimeUnit.MILLISECONDS,
|
||||
new SynchronousQueue<Runnable>(),
|
||||
new AsyncIOThreadFactory());
|
||||
|
||||
try {
|
||||
return AsynchronousChannelGroup.withCachedThreadPool(
|
||||
executorService, initialSize);
|
||||
} catch (IOException e) {
|
||||
// No good reason for this to happen.
|
||||
throw new IllegalStateException(sm.getString("asyncChannelGroup.createFail"));
|
||||
}
|
||||
} finally {
|
||||
Thread.currentThread().setContextClassLoader(original);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private static class AsyncIOThreadFactory implements ThreadFactory {
|
||||
|
||||
static {
|
||||
// Load NewThreadPrivilegedAction since newThread() will not be able
|
||||
// to if called from an InnocuousThread.
|
||||
// See https://bz.apache.org/bugzilla/show_bug.cgi?id=57490
|
||||
NewThreadPrivilegedAction.load();
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public Thread newThread(final Runnable r) {
|
||||
// Create the new Thread within a doPrivileged block to ensure that
|
||||
// the thread inherits the current ProtectionDomain which is
|
||||
// essential to be able to use this with a Java Applet. See
|
||||
// https://bz.apache.org/bugzilla/show_bug.cgi?id=57091
|
||||
return AccessController.doPrivileged(new NewThreadPrivilegedAction(r));
|
||||
}
|
||||
|
||||
// Non-anonymous class so that AsyncIOThreadFactory can load it
|
||||
// explicitly
|
||||
private static class NewThreadPrivilegedAction implements PrivilegedAction<Thread> {
|
||||
|
||||
private static AtomicInteger count = new AtomicInteger(0);
|
||||
|
||||
private final Runnable r;
|
||||
|
||||
public NewThreadPrivilegedAction(Runnable r) {
|
||||
this.r = r;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Thread run() {
|
||||
Thread t = new Thread(r);
|
||||
t.setName("WebSocketClient-AsyncIO-" + count.incrementAndGet());
|
||||
t.setContextClassLoader(this.getClass().getClassLoader());
|
||||
t.setDaemon(true);
|
||||
return t;
|
||||
}
|
||||
|
||||
private static void load() {
|
||||
// NO-OP. Just provides a hook to enable the class to be loaded
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
47
java/org/apache/tomcat/websocket/AsyncChannelWrapper.java
Normal file
47
java/org/apache/tomcat/websocket/AsyncChannelWrapper.java
Normal file
@@ -0,0 +1,47 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.apache.tomcat.websocket;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.channels.CompletionHandler;
|
||||
import java.util.concurrent.Future;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import javax.net.ssl.SSLException;
|
||||
|
||||
/**
|
||||
* This is a wrapper for a {@link java.nio.channels.AsynchronousSocketChannel}
|
||||
* that limits the methods available thereby simplifying the process of
|
||||
* implementing SSL/TLS support since there are fewer methods to intercept.
|
||||
*/
|
||||
public interface AsyncChannelWrapper {
|
||||
|
||||
Future<Integer> read(ByteBuffer dst);
|
||||
|
||||
<B,A extends B> void read(ByteBuffer dst, A attachment,
|
||||
CompletionHandler<Integer,B> handler);
|
||||
|
||||
Future<Integer> write(ByteBuffer src);
|
||||
|
||||
<B,A extends B> void write(ByteBuffer[] srcs, int offset, int length,
|
||||
long timeout, TimeUnit unit, A attachment,
|
||||
CompletionHandler<Long,B> handler);
|
||||
|
||||
void close();
|
||||
|
||||
Future<Void> handshake() throws SSLException;
|
||||
}
|
||||
@@ -0,0 +1,112 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.apache.tomcat.websocket;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.channels.AsynchronousSocketChannel;
|
||||
import java.nio.channels.CompletionHandler;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.concurrent.Future;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.TimeoutException;
|
||||
|
||||
/**
|
||||
* Generally, just passes calls straight to the wrapped
|
||||
* {@link AsynchronousSocketChannel}. In some cases exceptions may be swallowed
|
||||
* to save them being swallowed by the calling code.
|
||||
*/
|
||||
public class AsyncChannelWrapperNonSecure implements AsyncChannelWrapper {
|
||||
|
||||
private static final Future<Void> NOOP_FUTURE = new NoOpFuture();
|
||||
|
||||
private final AsynchronousSocketChannel socketChannel;
|
||||
|
||||
public AsyncChannelWrapperNonSecure(
|
||||
AsynchronousSocketChannel socketChannel) {
|
||||
this.socketChannel = socketChannel;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Future<Integer> read(ByteBuffer dst) {
|
||||
return socketChannel.read(dst);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <B,A extends B> void read(ByteBuffer dst, A attachment,
|
||||
CompletionHandler<Integer,B> handler) {
|
||||
socketChannel.read(dst, attachment, handler);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Future<Integer> write(ByteBuffer src) {
|
||||
return socketChannel.write(src);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <B,A extends B> void write(ByteBuffer[] srcs, int offset, int length,
|
||||
long timeout, TimeUnit unit, A attachment,
|
||||
CompletionHandler<Long,B> handler) {
|
||||
socketChannel.write(
|
||||
srcs, offset, length, timeout, unit, attachment, handler);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
try {
|
||||
socketChannel.close();
|
||||
} catch (IOException e) {
|
||||
// Ignore
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Future<Void> handshake() {
|
||||
return NOOP_FUTURE;
|
||||
}
|
||||
|
||||
|
||||
private static final class NoOpFuture implements Future<Void> {
|
||||
|
||||
@Override
|
||||
public boolean cancel(boolean mayInterruptIfRunning) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isCancelled() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isDone() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Void get() throws InterruptedException, ExecutionException {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Void get(long timeout, TimeUnit unit)
|
||||
throws InterruptedException, ExecutionException,
|
||||
TimeoutException {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
578
java/org/apache/tomcat/websocket/AsyncChannelWrapperSecure.java
Normal file
578
java/org/apache/tomcat/websocket/AsyncChannelWrapperSecure.java
Normal file
@@ -0,0 +1,578 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.apache.tomcat.websocket;
|
||||
|
||||
import java.io.EOFException;
|
||||
import java.io.IOException;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.channels.AsynchronousSocketChannel;
|
||||
import java.nio.channels.CompletionHandler;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.Future;
|
||||
import java.util.concurrent.ThreadFactory;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.TimeoutException;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
import javax.net.ssl.SSLEngine;
|
||||
import javax.net.ssl.SSLEngineResult;
|
||||
import javax.net.ssl.SSLEngineResult.HandshakeStatus;
|
||||
import javax.net.ssl.SSLEngineResult.Status;
|
||||
import javax.net.ssl.SSLException;
|
||||
|
||||
import org.apache.juli.logging.Log;
|
||||
import org.apache.juli.logging.LogFactory;
|
||||
import org.apache.tomcat.util.res.StringManager;
|
||||
|
||||
/**
|
||||
* Wraps the {@link AsynchronousSocketChannel} with SSL/TLS. This needs a lot
|
||||
* more testing before it can be considered robust.
|
||||
*/
|
||||
public class AsyncChannelWrapperSecure implements AsyncChannelWrapper {
|
||||
|
||||
private final Log log =
|
||||
LogFactory.getLog(AsyncChannelWrapperSecure.class);
|
||||
private static final StringManager sm =
|
||||
StringManager.getManager(AsyncChannelWrapperSecure.class);
|
||||
|
||||
private static final ByteBuffer DUMMY = ByteBuffer.allocate(16921);
|
||||
private final AsynchronousSocketChannel socketChannel;
|
||||
private final SSLEngine sslEngine;
|
||||
private final ByteBuffer socketReadBuffer;
|
||||
private final ByteBuffer socketWriteBuffer;
|
||||
// One thread for read, one for write
|
||||
private final ExecutorService executor =
|
||||
Executors.newFixedThreadPool(2, new SecureIOThreadFactory());
|
||||
private AtomicBoolean writing = new AtomicBoolean(false);
|
||||
private AtomicBoolean reading = new AtomicBoolean(false);
|
||||
|
||||
public AsyncChannelWrapperSecure(AsynchronousSocketChannel socketChannel,
|
||||
SSLEngine sslEngine) {
|
||||
this.socketChannel = socketChannel;
|
||||
this.sslEngine = sslEngine;
|
||||
|
||||
int socketBufferSize = sslEngine.getSession().getPacketBufferSize();
|
||||
socketReadBuffer = ByteBuffer.allocateDirect(socketBufferSize);
|
||||
socketWriteBuffer = ByteBuffer.allocateDirect(socketBufferSize);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Future<Integer> read(ByteBuffer dst) {
|
||||
WrapperFuture<Integer,Void> future = new WrapperFuture<>();
|
||||
|
||||
if (!reading.compareAndSet(false, true)) {
|
||||
throw new IllegalStateException(sm.getString(
|
||||
"asyncChannelWrapperSecure.concurrentRead"));
|
||||
}
|
||||
|
||||
ReadTask readTask = new ReadTask(dst, future);
|
||||
|
||||
executor.execute(readTask);
|
||||
|
||||
return future;
|
||||
}
|
||||
|
||||
@Override
|
||||
public <B,A extends B> void read(ByteBuffer dst, A attachment,
|
||||
CompletionHandler<Integer,B> handler) {
|
||||
|
||||
WrapperFuture<Integer,B> future =
|
||||
new WrapperFuture<>(handler, attachment);
|
||||
|
||||
if (!reading.compareAndSet(false, true)) {
|
||||
throw new IllegalStateException(sm.getString(
|
||||
"asyncChannelWrapperSecure.concurrentRead"));
|
||||
}
|
||||
|
||||
ReadTask readTask = new ReadTask(dst, future);
|
||||
|
||||
executor.execute(readTask);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Future<Integer> write(ByteBuffer src) {
|
||||
|
||||
WrapperFuture<Long,Void> inner = new WrapperFuture<>();
|
||||
|
||||
if (!writing.compareAndSet(false, true)) {
|
||||
throw new IllegalStateException(sm.getString(
|
||||
"asyncChannelWrapperSecure.concurrentWrite"));
|
||||
}
|
||||
|
||||
WriteTask writeTask =
|
||||
new WriteTask(new ByteBuffer[] {src}, 0, 1, inner);
|
||||
|
||||
executor.execute(writeTask);
|
||||
|
||||
Future<Integer> future = new LongToIntegerFuture(inner);
|
||||
return future;
|
||||
}
|
||||
|
||||
@Override
|
||||
public <B,A extends B> void write(ByteBuffer[] srcs, int offset, int length,
|
||||
long timeout, TimeUnit unit, A attachment,
|
||||
CompletionHandler<Long,B> handler) {
|
||||
|
||||
WrapperFuture<Long,B> future =
|
||||
new WrapperFuture<>(handler, attachment);
|
||||
|
||||
if (!writing.compareAndSet(false, true)) {
|
||||
throw new IllegalStateException(sm.getString(
|
||||
"asyncChannelWrapperSecure.concurrentWrite"));
|
||||
}
|
||||
|
||||
WriteTask writeTask = new WriteTask(srcs, offset, length, future);
|
||||
|
||||
executor.execute(writeTask);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
try {
|
||||
socketChannel.close();
|
||||
} catch (IOException e) {
|
||||
log.info(sm.getString("asyncChannelWrapperSecure.closeFail"));
|
||||
}
|
||||
executor.shutdownNow();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Future<Void> handshake() throws SSLException {
|
||||
|
||||
WrapperFuture<Void,Void> wFuture = new WrapperFuture<>();
|
||||
|
||||
Thread t = new WebSocketSslHandshakeThread(wFuture);
|
||||
t.start();
|
||||
|
||||
return wFuture;
|
||||
}
|
||||
|
||||
|
||||
private class WriteTask implements Runnable {
|
||||
|
||||
private final ByteBuffer[] srcs;
|
||||
private final int offset;
|
||||
private final int length;
|
||||
private final WrapperFuture<Long,?> future;
|
||||
|
||||
public WriteTask(ByteBuffer[] srcs, int offset, int length,
|
||||
WrapperFuture<Long,?> future) {
|
||||
this.srcs = srcs;
|
||||
this.future = future;
|
||||
this.offset = offset;
|
||||
this.length = length;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
long written = 0;
|
||||
|
||||
try {
|
||||
for (int i = offset; i < offset + length; i++) {
|
||||
ByteBuffer src = srcs[i];
|
||||
while (src.hasRemaining()) {
|
||||
socketWriteBuffer.clear();
|
||||
|
||||
// Encrypt the data
|
||||
SSLEngineResult r = sslEngine.wrap(src, socketWriteBuffer);
|
||||
written += r.bytesConsumed();
|
||||
Status s = r.getStatus();
|
||||
|
||||
if (s == Status.OK || s == Status.BUFFER_OVERFLOW) {
|
||||
// Need to write out the bytes and may need to read from
|
||||
// the source again to empty it
|
||||
} else {
|
||||
// Status.BUFFER_UNDERFLOW - only happens on unwrap
|
||||
// Status.CLOSED - unexpected
|
||||
throw new IllegalStateException(sm.getString(
|
||||
"asyncChannelWrapperSecure.statusWrap"));
|
||||
}
|
||||
|
||||
// Check for tasks
|
||||
if (r.getHandshakeStatus() == HandshakeStatus.NEED_TASK) {
|
||||
Runnable runnable = sslEngine.getDelegatedTask();
|
||||
while (runnable != null) {
|
||||
runnable.run();
|
||||
runnable = sslEngine.getDelegatedTask();
|
||||
}
|
||||
}
|
||||
|
||||
socketWriteBuffer.flip();
|
||||
|
||||
// Do the write
|
||||
int toWrite = r.bytesProduced();
|
||||
while (toWrite > 0) {
|
||||
Future<Integer> f =
|
||||
socketChannel.write(socketWriteBuffer);
|
||||
Integer socketWrite = f.get();
|
||||
toWrite -= socketWrite.intValue();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (writing.compareAndSet(true, false)) {
|
||||
future.complete(Long.valueOf(written));
|
||||
} else {
|
||||
future.fail(new IllegalStateException(sm.getString(
|
||||
"asyncChannelWrapperSecure.wrongStateWrite")));
|
||||
}
|
||||
} catch (Exception e) {
|
||||
writing.set(false);
|
||||
future.fail(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private class ReadTask implements Runnable {
|
||||
|
||||
private final ByteBuffer dest;
|
||||
private final WrapperFuture<Integer,?> future;
|
||||
|
||||
public ReadTask(ByteBuffer dest, WrapperFuture<Integer,?> future) {
|
||||
this.dest = dest;
|
||||
this.future = future;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
int read = 0;
|
||||
|
||||
boolean forceRead = false;
|
||||
|
||||
try {
|
||||
while (read == 0) {
|
||||
socketReadBuffer.compact();
|
||||
|
||||
if (forceRead) {
|
||||
forceRead = false;
|
||||
Future<Integer> f = socketChannel.read(socketReadBuffer);
|
||||
Integer socketRead = f.get();
|
||||
if (socketRead.intValue() == -1) {
|
||||
throw new EOFException(sm.getString("asyncChannelWrapperSecure.eof"));
|
||||
}
|
||||
}
|
||||
|
||||
socketReadBuffer.flip();
|
||||
|
||||
if (socketReadBuffer.hasRemaining()) {
|
||||
// Decrypt the data in the buffer
|
||||
SSLEngineResult r = sslEngine.unwrap(socketReadBuffer, dest);
|
||||
read += r.bytesProduced();
|
||||
Status s = r.getStatus();
|
||||
|
||||
if (s == Status.OK) {
|
||||
// Bytes available for reading and there may be
|
||||
// sufficient data in the socketReadBuffer to
|
||||
// support further reads without reading from the
|
||||
// socket
|
||||
} else if (s == Status.BUFFER_UNDERFLOW) {
|
||||
// There is partial data in the socketReadBuffer
|
||||
if (read == 0) {
|
||||
// Need more data before the partial data can be
|
||||
// processed and some output generated
|
||||
forceRead = true;
|
||||
}
|
||||
// else return the data we have and deal with the
|
||||
// partial data on the next read
|
||||
} else if (s == Status.BUFFER_OVERFLOW) {
|
||||
// Not enough space in the destination buffer to
|
||||
// store all of the data. We could use a bytes read
|
||||
// value of -bufferSizeRequired to signal the new
|
||||
// buffer size required but an explicit exception is
|
||||
// clearer.
|
||||
if (reading.compareAndSet(true, false)) {
|
||||
throw new ReadBufferOverflowException(sslEngine.
|
||||
getSession().getApplicationBufferSize());
|
||||
} else {
|
||||
future.fail(new IllegalStateException(sm.getString(
|
||||
"asyncChannelWrapperSecure.wrongStateRead")));
|
||||
}
|
||||
} else {
|
||||
// Status.CLOSED - unexpected
|
||||
throw new IllegalStateException(sm.getString(
|
||||
"asyncChannelWrapperSecure.statusUnwrap"));
|
||||
}
|
||||
|
||||
// Check for tasks
|
||||
if (r.getHandshakeStatus() == HandshakeStatus.NEED_TASK) {
|
||||
Runnable runnable = sslEngine.getDelegatedTask();
|
||||
while (runnable != null) {
|
||||
runnable.run();
|
||||
runnable = sslEngine.getDelegatedTask();
|
||||
}
|
||||
}
|
||||
} else {
|
||||
forceRead = true;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (reading.compareAndSet(true, false)) {
|
||||
future.complete(Integer.valueOf(read));
|
||||
} else {
|
||||
future.fail(new IllegalStateException(sm.getString(
|
||||
"asyncChannelWrapperSecure.wrongStateRead")));
|
||||
}
|
||||
} catch (RuntimeException | ReadBufferOverflowException | SSLException | EOFException |
|
||||
ExecutionException | InterruptedException e) {
|
||||
reading.set(false);
|
||||
future.fail(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private class WebSocketSslHandshakeThread extends Thread {
|
||||
|
||||
private final WrapperFuture<Void,Void> hFuture;
|
||||
|
||||
private HandshakeStatus handshakeStatus;
|
||||
private Status resultStatus;
|
||||
|
||||
public WebSocketSslHandshakeThread(WrapperFuture<Void,Void> hFuture) {
|
||||
this.hFuture = hFuture;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
sslEngine.beginHandshake();
|
||||
// So the first compact does the right thing
|
||||
socketReadBuffer.position(socketReadBuffer.limit());
|
||||
|
||||
handshakeStatus = sslEngine.getHandshakeStatus();
|
||||
resultStatus = Status.OK;
|
||||
|
||||
boolean handshaking = true;
|
||||
|
||||
while(handshaking) {
|
||||
switch (handshakeStatus) {
|
||||
case NEED_WRAP: {
|
||||
socketWriteBuffer.clear();
|
||||
SSLEngineResult r =
|
||||
sslEngine.wrap(DUMMY, socketWriteBuffer);
|
||||
checkResult(r, true);
|
||||
socketWriteBuffer.flip();
|
||||
Future<Integer> fWrite =
|
||||
socketChannel.write(socketWriteBuffer);
|
||||
fWrite.get();
|
||||
break;
|
||||
}
|
||||
case NEED_UNWRAP: {
|
||||
socketReadBuffer.compact();
|
||||
if (socketReadBuffer.position() == 0 ||
|
||||
resultStatus == Status.BUFFER_UNDERFLOW) {
|
||||
Future<Integer> fRead =
|
||||
socketChannel.read(socketReadBuffer);
|
||||
fRead.get();
|
||||
}
|
||||
socketReadBuffer.flip();
|
||||
SSLEngineResult r =
|
||||
sslEngine.unwrap(socketReadBuffer, DUMMY);
|
||||
checkResult(r, false);
|
||||
break;
|
||||
}
|
||||
case NEED_TASK: {
|
||||
Runnable r = null;
|
||||
while ((r = sslEngine.getDelegatedTask()) != null) {
|
||||
r.run();
|
||||
}
|
||||
handshakeStatus = sslEngine.getHandshakeStatus();
|
||||
break;
|
||||
}
|
||||
case FINISHED: {
|
||||
handshaking = false;
|
||||
break;
|
||||
}
|
||||
case NOT_HANDSHAKING: {
|
||||
throw new SSLException(
|
||||
sm.getString("asyncChannelWrapperSecure.notHandshaking"));
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
hFuture.fail(e);
|
||||
return;
|
||||
}
|
||||
|
||||
hFuture.complete(null);
|
||||
}
|
||||
|
||||
private void checkResult(SSLEngineResult result, boolean wrap)
|
||||
throws SSLException {
|
||||
|
||||
handshakeStatus = result.getHandshakeStatus();
|
||||
resultStatus = result.getStatus();
|
||||
|
||||
if (resultStatus != Status.OK &&
|
||||
(wrap || resultStatus != Status.BUFFER_UNDERFLOW)) {
|
||||
throw new SSLException(
|
||||
sm.getString("asyncChannelWrapperSecure.check.notOk", resultStatus));
|
||||
}
|
||||
if (wrap && result.bytesConsumed() != 0) {
|
||||
throw new SSLException(sm.getString("asyncChannelWrapperSecure.check.wrap"));
|
||||
}
|
||||
if (!wrap && result.bytesProduced() != 0) {
|
||||
throw new SSLException(sm.getString("asyncChannelWrapperSecure.check.unwrap"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private static class WrapperFuture<T,A> implements Future<T> {
|
||||
|
||||
private final CompletionHandler<T,A> handler;
|
||||
private final A attachment;
|
||||
|
||||
private volatile T result = null;
|
||||
private volatile Throwable throwable = null;
|
||||
private CountDownLatch completionLatch = new CountDownLatch(1);
|
||||
|
||||
public WrapperFuture() {
|
||||
this(null, null);
|
||||
}
|
||||
|
||||
public WrapperFuture(CompletionHandler<T,A> handler, A attachment) {
|
||||
this.handler = handler;
|
||||
this.attachment = attachment;
|
||||
}
|
||||
|
||||
public void complete(T result) {
|
||||
this.result = result;
|
||||
completionLatch.countDown();
|
||||
if (handler != null) {
|
||||
handler.completed(result, attachment);
|
||||
}
|
||||
}
|
||||
|
||||
public void fail(Throwable t) {
|
||||
throwable = t;
|
||||
completionLatch.countDown();
|
||||
if (handler != null) {
|
||||
handler.failed(throwable, attachment);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public final boolean cancel(boolean mayInterruptIfRunning) {
|
||||
// Could support cancellation by closing the connection
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public final boolean isCancelled() {
|
||||
// Could support cancellation by closing the connection
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public final boolean isDone() {
|
||||
return completionLatch.getCount() > 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public T get() throws InterruptedException, ExecutionException {
|
||||
completionLatch.await();
|
||||
if (throwable != null) {
|
||||
throw new ExecutionException(throwable);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public T get(long timeout, TimeUnit unit)
|
||||
throws InterruptedException, ExecutionException,
|
||||
TimeoutException {
|
||||
boolean latchResult = completionLatch.await(timeout, unit);
|
||||
if (latchResult == false) {
|
||||
throw new TimeoutException();
|
||||
}
|
||||
if (throwable != null) {
|
||||
throw new ExecutionException(throwable);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
private static final class LongToIntegerFuture implements Future<Integer> {
|
||||
|
||||
private final Future<Long> wrapped;
|
||||
|
||||
public LongToIntegerFuture(Future<Long> wrapped) {
|
||||
this.wrapped = wrapped;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean cancel(boolean mayInterruptIfRunning) {
|
||||
return wrapped.cancel(mayInterruptIfRunning);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isCancelled() {
|
||||
return wrapped.isCancelled();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isDone() {
|
||||
return wrapped.isDone();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Integer get() throws InterruptedException, ExecutionException {
|
||||
Long result = wrapped.get();
|
||||
if (result.longValue() > Integer.MAX_VALUE) {
|
||||
throw new ExecutionException(sm.getString(
|
||||
"asyncChannelWrapperSecure.tooBig", result), null);
|
||||
}
|
||||
return Integer.valueOf(result.intValue());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Integer get(long timeout, TimeUnit unit)
|
||||
throws InterruptedException, ExecutionException,
|
||||
TimeoutException {
|
||||
Long result = wrapped.get(timeout, unit);
|
||||
if (result.longValue() > Integer.MAX_VALUE) {
|
||||
throw new ExecutionException(sm.getString(
|
||||
"asyncChannelWrapperSecure.tooBig", result), null);
|
||||
}
|
||||
return Integer.valueOf(result.intValue());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private static class SecureIOThreadFactory implements ThreadFactory {
|
||||
|
||||
private AtomicInteger count = new AtomicInteger(0);
|
||||
|
||||
@Override
|
||||
public Thread newThread(Runnable r) {
|
||||
Thread t = new Thread(r);
|
||||
t.setName("WebSocketClient-SecureIO-" + count.incrementAndGet());
|
||||
// No need to set the context class loader. The threads will be
|
||||
// cleaned up when the connection is closed.
|
||||
t.setDaemon(true);
|
||||
return t;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.apache.tomcat.websocket;
|
||||
|
||||
/**
|
||||
* Exception thrown on authentication error connecting to a remote
|
||||
* websocket endpoint.
|
||||
*/
|
||||
public class AuthenticationException extends Exception {
|
||||
|
||||
private static final long serialVersionUID = 5709887412240096441L;
|
||||
|
||||
/**
|
||||
* Create authentication exception.
|
||||
* @param message the error message
|
||||
*/
|
||||
public AuthenticationException(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
}
|
||||
71
java/org/apache/tomcat/websocket/Authenticator.java
Normal file
71
java/org/apache/tomcat/websocket/Authenticator.java
Normal file
@@ -0,0 +1,71 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.apache.tomcat.websocket;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
/**
|
||||
* Base class for the authentication methods used by the websocket client.
|
||||
*/
|
||||
public abstract class Authenticator {
|
||||
private static final Pattern pattern = Pattern
|
||||
.compile("(\\w+)\\s*=\\s*(\"([^\"]+)\"|([^,=\"]+))\\s*,?");
|
||||
|
||||
/**
|
||||
* Generate the authentication header that will be sent to the server.
|
||||
* @param requestUri The request URI
|
||||
* @param WWWAuthenticate The server auth challenge
|
||||
* @param UserProperties The user information
|
||||
* @return The auth header
|
||||
* @throws AuthenticationException When an error occurs
|
||||
*/
|
||||
public abstract String getAuthorization(String requestUri, String WWWAuthenticate,
|
||||
Map<String, Object> UserProperties) throws AuthenticationException;
|
||||
|
||||
/**
|
||||
* Get the authentication method.
|
||||
* @return the auth scheme
|
||||
*/
|
||||
public abstract String getSchemeName();
|
||||
|
||||
/**
|
||||
* Utility method to parse the authentication header.
|
||||
* @param WWWAuthenticate The server auth challenge
|
||||
* @return the parsed header
|
||||
*/
|
||||
public Map<String, String> parseWWWAuthenticateHeader(String WWWAuthenticate) {
|
||||
|
||||
Matcher m = pattern.matcher(WWWAuthenticate);
|
||||
Map<String, String> challenge = new HashMap<>();
|
||||
|
||||
while (m.find()) {
|
||||
String key = m.group(1);
|
||||
String qtedValue = m.group(3);
|
||||
String value = m.group(4);
|
||||
|
||||
challenge.put(key, qtedValue != null ? qtedValue : value);
|
||||
|
||||
}
|
||||
|
||||
return challenge;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
68
java/org/apache/tomcat/websocket/AuthenticatorFactory.java
Normal file
68
java/org/apache/tomcat/websocket/AuthenticatorFactory.java
Normal file
@@ -0,0 +1,68 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.apache.tomcat.websocket;
|
||||
|
||||
import java.util.Iterator;
|
||||
import java.util.ServiceLoader;
|
||||
|
||||
/**
|
||||
* Utility method to return the appropriate authenticator according to
|
||||
* the scheme that the server uses.
|
||||
*/
|
||||
public class AuthenticatorFactory {
|
||||
|
||||
/**
|
||||
* Return a new authenticator instance.
|
||||
* @param authScheme The scheme used
|
||||
* @return the authenticator
|
||||
*/
|
||||
public static Authenticator getAuthenticator(String authScheme) {
|
||||
|
||||
Authenticator auth = null;
|
||||
switch (authScheme.toLowerCase()) {
|
||||
|
||||
case BasicAuthenticator.schemeName:
|
||||
auth = new BasicAuthenticator();
|
||||
break;
|
||||
|
||||
case DigestAuthenticator.schemeName:
|
||||
auth = new DigestAuthenticator();
|
||||
break;
|
||||
|
||||
default:
|
||||
auth = loadAuthenticators(authScheme);
|
||||
break;
|
||||
}
|
||||
|
||||
return auth;
|
||||
|
||||
}
|
||||
|
||||
private static Authenticator loadAuthenticators(String authScheme) {
|
||||
ServiceLoader<Authenticator> serviceLoader = ServiceLoader.load(Authenticator.class);
|
||||
Iterator<Authenticator> auths = serviceLoader.iterator();
|
||||
|
||||
while (auths.hasNext()) {
|
||||
Authenticator auth = auths.next();
|
||||
if (auth.getSchemeName().equalsIgnoreCase(authScheme))
|
||||
return auth;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
26
java/org/apache/tomcat/websocket/BackgroundProcess.java
Normal file
26
java/org/apache/tomcat/websocket/BackgroundProcess.java
Normal file
@@ -0,0 +1,26 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.apache.tomcat.websocket;
|
||||
|
||||
public interface BackgroundProcess {
|
||||
|
||||
void backgroundProcess();
|
||||
|
||||
void setProcessPeriod(int period);
|
||||
|
||||
int getProcessPeriod();
|
||||
}
|
||||
149
java/org/apache/tomcat/websocket/BackgroundProcessManager.java
Normal file
149
java/org/apache/tomcat/websocket/BackgroundProcessManager.java
Normal file
@@ -0,0 +1,149 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.apache.tomcat.websocket;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
import org.apache.juli.logging.Log;
|
||||
import org.apache.juli.logging.LogFactory;
|
||||
import org.apache.tomcat.util.ExceptionUtils;
|
||||
import org.apache.tomcat.util.res.StringManager;
|
||||
|
||||
/**
|
||||
* Provides a background processing mechanism that triggers roughly once a
|
||||
* second. The class maintains a thread that only runs when there is at least
|
||||
* one instance of {@link BackgroundProcess} registered.
|
||||
*/
|
||||
public class BackgroundProcessManager {
|
||||
|
||||
private final Log log =
|
||||
LogFactory.getLog(BackgroundProcessManager.class);
|
||||
private static final StringManager sm =
|
||||
StringManager.getManager(BackgroundProcessManager.class);
|
||||
private static final BackgroundProcessManager instance;
|
||||
|
||||
|
||||
static {
|
||||
instance = new BackgroundProcessManager();
|
||||
}
|
||||
|
||||
|
||||
public static BackgroundProcessManager getInstance() {
|
||||
return instance;
|
||||
}
|
||||
|
||||
private final Set<BackgroundProcess> processes = new HashSet<>();
|
||||
private final Object processesLock = new Object();
|
||||
private WsBackgroundThread wsBackgroundThread = null;
|
||||
|
||||
private BackgroundProcessManager() {
|
||||
// Hide default constructor
|
||||
}
|
||||
|
||||
|
||||
public void register(BackgroundProcess process) {
|
||||
synchronized (processesLock) {
|
||||
if (processes.size() == 0) {
|
||||
wsBackgroundThread = new WsBackgroundThread(this);
|
||||
wsBackgroundThread.setContextClassLoader(
|
||||
this.getClass().getClassLoader());
|
||||
wsBackgroundThread.setDaemon(true);
|
||||
wsBackgroundThread.start();
|
||||
}
|
||||
processes.add(process);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public void unregister(BackgroundProcess process) {
|
||||
synchronized (processesLock) {
|
||||
processes.remove(process);
|
||||
if (wsBackgroundThread != null && processes.size() == 0) {
|
||||
wsBackgroundThread.halt();
|
||||
wsBackgroundThread = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private void process() {
|
||||
Set<BackgroundProcess> currentProcesses = new HashSet<>();
|
||||
synchronized (processesLock) {
|
||||
currentProcesses.addAll(processes);
|
||||
}
|
||||
for (BackgroundProcess process : currentProcesses) {
|
||||
try {
|
||||
process.backgroundProcess();
|
||||
} catch (Throwable t) {
|
||||
ExceptionUtils.handleThrowable(t);
|
||||
log.error(sm.getString(
|
||||
"backgroundProcessManager.processFailed"), t);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* For unit testing.
|
||||
*/
|
||||
int getProcessCount() {
|
||||
synchronized (processesLock) {
|
||||
return processes.size();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void shutdown() {
|
||||
synchronized (processesLock) {
|
||||
processes.clear();
|
||||
if (wsBackgroundThread != null) {
|
||||
wsBackgroundThread.halt();
|
||||
wsBackgroundThread = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private static class WsBackgroundThread extends Thread {
|
||||
|
||||
private final BackgroundProcessManager manager;
|
||||
private volatile boolean running = true;
|
||||
|
||||
public WsBackgroundThread(BackgroundProcessManager manager) {
|
||||
setName("WebSocket background processing");
|
||||
this.manager = manager;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
while (running) {
|
||||
try {
|
||||
Thread.sleep(1000);
|
||||
} catch (InterruptedException e) {
|
||||
// Ignore
|
||||
}
|
||||
manager.process();
|
||||
}
|
||||
}
|
||||
|
||||
public void halt() {
|
||||
setName("WebSocket background processing - stopping");
|
||||
running = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
67
java/org/apache/tomcat/websocket/BasicAuthenticator.java
Normal file
67
java/org/apache/tomcat/websocket/BasicAuthenticator.java
Normal file
@@ -0,0 +1,67 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.apache.tomcat.websocket;
|
||||
|
||||
import java.nio.charset.Charset;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Map;
|
||||
|
||||
import org.apache.tomcat.util.codec.binary.Base64;
|
||||
|
||||
/**
|
||||
* Authenticator supporting the BASIC auth method.
|
||||
*/
|
||||
public class BasicAuthenticator extends Authenticator {
|
||||
|
||||
public static final String schemeName = "basic";
|
||||
public static final String charsetparam = "charset";
|
||||
|
||||
@Override
|
||||
public String getAuthorization(String requestUri, String WWWAuthenticate,
|
||||
Map<String, Object> userProperties) throws AuthenticationException {
|
||||
|
||||
String userName = (String) userProperties.get(Constants.WS_AUTHENTICATION_USER_NAME);
|
||||
String password = (String) userProperties.get(Constants.WS_AUTHENTICATION_PASSWORD);
|
||||
|
||||
if (userName == null || password == null) {
|
||||
throw new AuthenticationException(
|
||||
"Failed to perform Basic authentication due to missing user/password");
|
||||
}
|
||||
|
||||
Map<String, String> wwwAuthenticate = parseWWWAuthenticateHeader(WWWAuthenticate);
|
||||
|
||||
String userPass = userName + ":" + password;
|
||||
Charset charset;
|
||||
|
||||
if (wwwAuthenticate.get(charsetparam) != null
|
||||
&& wwwAuthenticate.get(charsetparam).equalsIgnoreCase("UTF-8")) {
|
||||
charset = StandardCharsets.UTF_8;
|
||||
} else {
|
||||
charset = StandardCharsets.ISO_8859_1;
|
||||
}
|
||||
|
||||
String base64 = Base64.encodeBase64String(userPass.getBytes(charset));
|
||||
|
||||
return " Basic " + base64;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getSchemeName() {
|
||||
return schemeName;
|
||||
}
|
||||
|
||||
}
|
||||
154
java/org/apache/tomcat/websocket/Constants.java
Normal file
154
java/org/apache/tomcat/websocket/Constants.java
Normal file
@@ -0,0 +1,154 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.apache.tomcat.websocket;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import javax.websocket.Extension;
|
||||
|
||||
/**
|
||||
* Internal implementation constants.
|
||||
*/
|
||||
public class Constants {
|
||||
|
||||
// OP Codes
|
||||
public static final byte OPCODE_CONTINUATION = 0x00;
|
||||
public static final byte OPCODE_TEXT = 0x01;
|
||||
public static final byte OPCODE_BINARY = 0x02;
|
||||
public static final byte OPCODE_CLOSE = 0x08;
|
||||
public static final byte OPCODE_PING = 0x09;
|
||||
public static final byte OPCODE_PONG = 0x0A;
|
||||
|
||||
// Internal OP Codes
|
||||
// RFC 6455 limits OP Codes to 4 bits so these should never clash
|
||||
// Always set bit 4 so these will be treated as control codes
|
||||
static final byte INTERNAL_OPCODE_FLUSH = 0x18;
|
||||
|
||||
// Buffers
|
||||
static final int DEFAULT_BUFFER_SIZE = Integer.getInteger(
|
||||
"org.apache.tomcat.websocket.DEFAULT_BUFFER_SIZE", 8 * 1024)
|
||||
.intValue();
|
||||
|
||||
// Client connection
|
||||
/**
|
||||
* Property name to set to configure the value that is passed to
|
||||
* {@link javax.net.ssl.SSLEngine#setEnabledProtocols(String[])}. The value
|
||||
* should be a comma separated string.
|
||||
*/
|
||||
public static final String SSL_PROTOCOLS_PROPERTY =
|
||||
"org.apache.tomcat.websocket.SSL_PROTOCOLS";
|
||||
public static final String SSL_TRUSTSTORE_PROPERTY =
|
||||
"org.apache.tomcat.websocket.SSL_TRUSTSTORE";
|
||||
public static final String SSL_TRUSTSTORE_PWD_PROPERTY =
|
||||
"org.apache.tomcat.websocket.SSL_TRUSTSTORE_PWD";
|
||||
public static final String SSL_TRUSTSTORE_PWD_DEFAULT = "changeit";
|
||||
/**
|
||||
* Property name to set to configure used SSLContext. The value should be an
|
||||
* instance of SSLContext. If this property is present, the SSL_TRUSTSTORE*
|
||||
* properties are ignored.
|
||||
*/
|
||||
public static final String SSL_CONTEXT_PROPERTY =
|
||||
"org.apache.tomcat.websocket.SSL_CONTEXT";
|
||||
/**
|
||||
* Property name to set to configure the timeout (in milliseconds) when
|
||||
* establishing a WebSocket connection to server. The default is
|
||||
* {@link #IO_TIMEOUT_MS_DEFAULT}.
|
||||
*/
|
||||
public static final String IO_TIMEOUT_MS_PROPERTY =
|
||||
"org.apache.tomcat.websocket.IO_TIMEOUT_MS";
|
||||
public static final long IO_TIMEOUT_MS_DEFAULT = 5000;
|
||||
|
||||
// RFC 2068 recommended a limit of 5
|
||||
// Most browsers have a default limit of 20
|
||||
public static final String MAX_REDIRECTIONS_PROPERTY =
|
||||
"org.apache.tomcat.websocket.MAX_REDIRECTIONS";
|
||||
public static final int MAX_REDIRECTIONS_DEFAULT = 20;
|
||||
|
||||
// HTTP upgrade header names and values
|
||||
public static final String HOST_HEADER_NAME = "Host";
|
||||
public static final String UPGRADE_HEADER_NAME = "Upgrade";
|
||||
public static final String UPGRADE_HEADER_VALUE = "websocket";
|
||||
public static final String ORIGIN_HEADER_NAME = "Origin";
|
||||
public static final String CONNECTION_HEADER_NAME = "Connection";
|
||||
public static final String CONNECTION_HEADER_VALUE = "upgrade";
|
||||
public static final String LOCATION_HEADER_NAME = "Location";
|
||||
public static final String AUTHORIZATION_HEADER_NAME = "Authorization";
|
||||
public static final String WWW_AUTHENTICATE_HEADER_NAME = "WWW-Authenticate";
|
||||
public static final String WS_VERSION_HEADER_NAME = "Sec-WebSocket-Version";
|
||||
public static final String WS_VERSION_HEADER_VALUE = "13";
|
||||
public static final String WS_KEY_HEADER_NAME = "Sec-WebSocket-Key";
|
||||
public static final String WS_PROTOCOL_HEADER_NAME = "Sec-WebSocket-Protocol";
|
||||
public static final String WS_EXTENSIONS_HEADER_NAME = "Sec-WebSocket-Extensions";
|
||||
|
||||
/// HTTP redirection status codes
|
||||
public static final int MULTIPLE_CHOICES = 300;
|
||||
public static final int MOVED_PERMANENTLY = 301;
|
||||
public static final int FOUND = 302;
|
||||
public static final int SEE_OTHER = 303;
|
||||
public static final int USE_PROXY = 305;
|
||||
public static final int TEMPORARY_REDIRECT = 307;
|
||||
|
||||
// Configuration for Origin header in client
|
||||
static final String DEFAULT_ORIGIN_HEADER_VALUE =
|
||||
System.getProperty("org.apache.tomcat.websocket.DEFAULT_ORIGIN_HEADER_VALUE");
|
||||
|
||||
// Configuration for blocking sends
|
||||
public static final String BLOCKING_SEND_TIMEOUT_PROPERTY =
|
||||
"org.apache.tomcat.websocket.BLOCKING_SEND_TIMEOUT";
|
||||
// Milliseconds so this is 20 seconds
|
||||
public static final long DEFAULT_BLOCKING_SEND_TIMEOUT = 20 * 1000;
|
||||
|
||||
// Configuration for background processing checks intervals
|
||||
static final int DEFAULT_PROCESS_PERIOD = Integer.getInteger(
|
||||
"org.apache.tomcat.websocket.DEFAULT_PROCESS_PERIOD", 10)
|
||||
.intValue();
|
||||
|
||||
public static final String WS_AUTHENTICATION_USER_NAME = "org.apache.tomcat.websocket.WS_AUTHENTICATION_USER_NAME";
|
||||
public static final String WS_AUTHENTICATION_PASSWORD = "org.apache.tomcat.websocket.WS_AUTHENTICATION_PASSWORD";
|
||||
|
||||
/* Configuration for extensions
|
||||
* Note: These options are primarily present to enable this implementation
|
||||
* to pass compliance tests. They are expected to be removed once
|
||||
* the WebSocket API includes a mechanism for adding custom extensions
|
||||
* and disabling built-in extensions.
|
||||
*/
|
||||
static final boolean DISABLE_BUILTIN_EXTENSIONS =
|
||||
Boolean.getBoolean("org.apache.tomcat.websocket.DISABLE_BUILTIN_EXTENSIONS");
|
||||
static final boolean ALLOW_UNSUPPORTED_EXTENSIONS =
|
||||
Boolean.getBoolean("org.apache.tomcat.websocket.ALLOW_UNSUPPORTED_EXTENSIONS");
|
||||
|
||||
public static final boolean STRICT_SPEC_COMPLIANCE =
|
||||
Boolean.getBoolean("org.apache.tomcat.websocket.STRICT_SPEC_COMPLIANCE");
|
||||
|
||||
public static final List<Extension> INSTALLED_EXTENSIONS;
|
||||
|
||||
static {
|
||||
if (DISABLE_BUILTIN_EXTENSIONS) {
|
||||
INSTALLED_EXTENSIONS = Collections.unmodifiableList(new ArrayList<Extension>());
|
||||
} else {
|
||||
List<Extension> installed = new ArrayList<>(1);
|
||||
installed.add(new WsExtension("permessage-deflate"));
|
||||
INSTALLED_EXTENSIONS = Collections.unmodifiableList(installed);
|
||||
}
|
||||
}
|
||||
|
||||
private Constants() {
|
||||
// Hide default constructor
|
||||
}
|
||||
}
|
||||
39
java/org/apache/tomcat/websocket/DecoderEntry.java
Normal file
39
java/org/apache/tomcat/websocket/DecoderEntry.java
Normal file
@@ -0,0 +1,39 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.apache.tomcat.websocket;
|
||||
|
||||
import javax.websocket.Decoder;
|
||||
|
||||
public class DecoderEntry {
|
||||
|
||||
private final Class<?> clazz;
|
||||
private final Class<? extends Decoder> decoderClazz;
|
||||
|
||||
public DecoderEntry(Class<?> clazz,
|
||||
Class<? extends Decoder> decoderClazz) {
|
||||
this.clazz = clazz;
|
||||
this.decoderClazz = decoderClazz;
|
||||
}
|
||||
|
||||
public Class<?> getClazz() {
|
||||
return clazz;
|
||||
}
|
||||
|
||||
public Class<? extends Decoder> getDecoderClazz() {
|
||||
return decoderClazz;
|
||||
}
|
||||
}
|
||||
150
java/org/apache/tomcat/websocket/DigestAuthenticator.java
Normal file
150
java/org/apache/tomcat/websocket/DigestAuthenticator.java
Normal file
@@ -0,0 +1,150 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.apache.tomcat.websocket;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.security.MessageDigest;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.SecureRandom;
|
||||
import java.util.Map;
|
||||
|
||||
import org.apache.tomcat.util.security.MD5Encoder;
|
||||
|
||||
/**
|
||||
* Authenticator supporting the DIGEST auth method.
|
||||
*/
|
||||
public class DigestAuthenticator extends Authenticator {
|
||||
|
||||
public static final String schemeName = "digest";
|
||||
private SecureRandom cnonceGenerator;
|
||||
private int nonceCount = 0;
|
||||
private long cNonce;
|
||||
|
||||
@Override
|
||||
public String getAuthorization(String requestUri, String WWWAuthenticate,
|
||||
Map<String, Object> userProperties) throws AuthenticationException {
|
||||
|
||||
String userName = (String) userProperties.get(Constants.WS_AUTHENTICATION_USER_NAME);
|
||||
String password = (String) userProperties.get(Constants.WS_AUTHENTICATION_PASSWORD);
|
||||
|
||||
if (userName == null || password == null) {
|
||||
throw new AuthenticationException(
|
||||
"Failed to perform Digest authentication due to missing user/password");
|
||||
}
|
||||
|
||||
Map<String, String> wwwAuthenticate = parseWWWAuthenticateHeader(WWWAuthenticate);
|
||||
|
||||
String realm = wwwAuthenticate.get("realm");
|
||||
String nonce = wwwAuthenticate.get("nonce");
|
||||
String messageQop = wwwAuthenticate.get("qop");
|
||||
String algorithm = wwwAuthenticate.get("algorithm") == null ? "MD5"
|
||||
: wwwAuthenticate.get("algorithm");
|
||||
String opaque = wwwAuthenticate.get("opaque");
|
||||
|
||||
StringBuilder challenge = new StringBuilder();
|
||||
|
||||
if (!messageQop.isEmpty()) {
|
||||
if (cnonceGenerator == null) {
|
||||
cnonceGenerator = new SecureRandom();
|
||||
}
|
||||
|
||||
cNonce = cnonceGenerator.nextLong();
|
||||
nonceCount++;
|
||||
}
|
||||
|
||||
challenge.append("Digest ");
|
||||
challenge.append("username =\"" + userName + "\",");
|
||||
challenge.append("realm=\"" + realm + "\",");
|
||||
challenge.append("nonce=\"" + nonce + "\",");
|
||||
challenge.append("uri=\"" + requestUri + "\",");
|
||||
|
||||
try {
|
||||
challenge.append("response=\"" + calculateRequestDigest(requestUri, userName, password,
|
||||
realm, nonce, messageQop, algorithm) + "\",");
|
||||
}
|
||||
|
||||
catch (NoSuchAlgorithmException e) {
|
||||
throw new AuthenticationException(
|
||||
"Unable to generate request digest " + e.getMessage());
|
||||
}
|
||||
|
||||
challenge.append("algorithm=" + algorithm + ",");
|
||||
challenge.append("opaque=\"" + opaque + "\",");
|
||||
|
||||
if (!messageQop.isEmpty()) {
|
||||
challenge.append("qop=\"" + messageQop + "\"");
|
||||
challenge.append(",cnonce=\"" + cNonce + "\",");
|
||||
challenge.append("nc=" + String.format("%08X", Integer.valueOf(nonceCount)));
|
||||
}
|
||||
|
||||
return challenge.toString();
|
||||
|
||||
}
|
||||
|
||||
private String calculateRequestDigest(String requestUri, String userName, String password,
|
||||
String realm, String nonce, String qop, String algorithm)
|
||||
throws NoSuchAlgorithmException {
|
||||
|
||||
StringBuilder preDigest = new StringBuilder();
|
||||
String A1;
|
||||
|
||||
if (algorithm.equalsIgnoreCase("MD5"))
|
||||
A1 = userName + ":" + realm + ":" + password;
|
||||
|
||||
else
|
||||
A1 = encodeMD5(userName + ":" + realm + ":" + password) + ":" + nonce + ":" + cNonce;
|
||||
|
||||
/*
|
||||
* If the "qop" value is "auth-int", then A2 is: A2 = Method ":"
|
||||
* digest-uri-value ":" H(entity-body) since we do not have an entity-body, A2 =
|
||||
* Method ":" digest-uri-value for auth and auth_int
|
||||
*/
|
||||
String A2 = "GET:" + requestUri;
|
||||
|
||||
preDigest.append(encodeMD5(A1));
|
||||
preDigest.append(":");
|
||||
preDigest.append(nonce);
|
||||
|
||||
if (qop.toLowerCase().contains("auth")) {
|
||||
preDigest.append(":");
|
||||
preDigest.append(String.format("%08X", Integer.valueOf(nonceCount)));
|
||||
preDigest.append(":");
|
||||
preDigest.append(String.valueOf(cNonce));
|
||||
preDigest.append(":");
|
||||
preDigest.append(qop);
|
||||
}
|
||||
|
||||
preDigest.append(":");
|
||||
preDigest.append(encodeMD5(A2));
|
||||
|
||||
return encodeMD5(preDigest.toString());
|
||||
|
||||
}
|
||||
|
||||
private String encodeMD5(String value) throws NoSuchAlgorithmException {
|
||||
byte[] bytesOfMessage = value.getBytes(StandardCharsets.ISO_8859_1);
|
||||
MessageDigest md = MessageDigest.getInstance("MD5");
|
||||
byte[] thedigest = md.digest(bytesOfMessage);
|
||||
|
||||
return MD5Encoder.encode(thedigest);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getSchemeName() {
|
||||
return schemeName;
|
||||
}
|
||||
}
|
||||
112
java/org/apache/tomcat/websocket/FutureToSendHandler.java
Normal file
112
java/org/apache/tomcat/websocket/FutureToSendHandler.java
Normal file
@@ -0,0 +1,112 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.apache.tomcat.websocket;
|
||||
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.concurrent.Future;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.TimeoutException;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
|
||||
import javax.websocket.SendHandler;
|
||||
import javax.websocket.SendResult;
|
||||
|
||||
import org.apache.tomcat.util.res.StringManager;
|
||||
|
||||
|
||||
/**
|
||||
* Converts a Future to a SendHandler.
|
||||
*/
|
||||
class FutureToSendHandler implements Future<Void>, SendHandler {
|
||||
|
||||
private static final StringManager sm = StringManager.getManager(FutureToSendHandler.class);
|
||||
|
||||
private final CountDownLatch latch = new CountDownLatch(1);
|
||||
private final WsSession wsSession;
|
||||
private volatile AtomicReference<SendResult> result = new AtomicReference<>(null);
|
||||
|
||||
public FutureToSendHandler(WsSession wsSession) {
|
||||
this.wsSession = wsSession;
|
||||
}
|
||||
|
||||
|
||||
// --------------------------------------------------------- SendHandler
|
||||
|
||||
@Override
|
||||
public void onResult(SendResult result) {
|
||||
this.result.compareAndSet(null, result);
|
||||
latch.countDown();
|
||||
}
|
||||
|
||||
|
||||
// -------------------------------------------------------------- Future
|
||||
|
||||
@Override
|
||||
public boolean cancel(boolean mayInterruptIfRunning) {
|
||||
// Cancelling the task is not supported
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isCancelled() {
|
||||
// Cancelling the task is not supported
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isDone() {
|
||||
return latch.getCount() == 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Void get() throws InterruptedException,
|
||||
ExecutionException {
|
||||
try {
|
||||
wsSession.registerFuture(this);
|
||||
latch.await();
|
||||
} finally {
|
||||
wsSession.unregisterFuture(this);
|
||||
}
|
||||
if (result.get().getException() != null) {
|
||||
throw new ExecutionException(result.get().getException());
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Void get(long timeout, TimeUnit unit)
|
||||
throws InterruptedException, ExecutionException,
|
||||
TimeoutException {
|
||||
boolean retval = false;
|
||||
try {
|
||||
wsSession.registerFuture(this);
|
||||
retval = latch.await(timeout, unit);
|
||||
} finally {
|
||||
wsSession.unregisterFuture(this);
|
||||
|
||||
}
|
||||
if (retval == false) {
|
||||
throw new TimeoutException(sm.getString("futureToSendHandler.timeout",
|
||||
Long.valueOf(timeout), unit.toString().toLowerCase()));
|
||||
}
|
||||
if (result.get().getException() != null) {
|
||||
throw new ExecutionException(result.get().getException());
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
146
java/org/apache/tomcat/websocket/LocalStrings.properties
Normal file
146
java/org/apache/tomcat/websocket/LocalStrings.properties
Normal file
@@ -0,0 +1,146 @@
|
||||
# Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
# contributor license agreements. See the NOTICE file distributed with
|
||||
# this work for additional information regarding copyright ownership.
|
||||
# The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
# (the "License"); you may not use this file except in compliance with
|
||||
# the License. You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
asyncChannelGroup.createFail=Unable to create dedicated AsynchronousChannelGroup for WebSocket clients which is required to prevent memory leaks in complex class loader environments like JavaEE containers
|
||||
|
||||
asyncChannelWrapperSecure.check.notOk=TLS handshake returned an unexpected status [{0}]
|
||||
asyncChannelWrapperSecure.check.unwrap=Bytes were written to the output during a read
|
||||
asyncChannelWrapperSecure.check.wrap=Bytes were consumed from the input during a write
|
||||
asyncChannelWrapperSecure.closeFail=Failed to close channel cleanly
|
||||
asyncChannelWrapperSecure.concurrentRead=Concurrent read operations are not permitted
|
||||
asyncChannelWrapperSecure.concurrentWrite=Concurrent write operations are not permitted
|
||||
asyncChannelWrapperSecure.eof=Unexpected end of stream
|
||||
asyncChannelWrapperSecure.notHandshaking=Unexpected state [NOT_HANDSHAKING] during TLS handshake
|
||||
asyncChannelWrapperSecure.statusUnwrap=Unexpected Status of SSLEngineResult after an unwrap() operation
|
||||
asyncChannelWrapperSecure.statusWrap=Unexpected Status of SSLEngineResult after a wrap() operation
|
||||
asyncChannelWrapperSecure.tooBig=The result [{0}] is too big to be expressed as an Integer
|
||||
asyncChannelWrapperSecure.wrongStateRead=Flag that indicates a read is in progress was found to be false (it should have been true) when trying to complete a read operation
|
||||
asyncChannelWrapperSecure.wrongStateWrite=Flag that indicates a write is in progress was found to be false (it should have been true) when trying to complete a write operation
|
||||
|
||||
backgroundProcessManager.processFailed=A background process failed
|
||||
|
||||
caseInsensitiveKeyMap.nullKey=Null keys are not permitted
|
||||
|
||||
futureToSendHandler.timeout=Operation timed out after waiting [{0}] [{1}] to complete
|
||||
|
||||
perMessageDeflate.alreadyClosed=The transformer has been closed and may no longer be used
|
||||
perMessageDeflate.deflateFailed=Failed to decompress a compressed WebSocket frame
|
||||
perMessageDeflate.duplicateParameter=Duplicate definition of the [{0}] extension parameter
|
||||
perMessageDeflate.invalidState=Invalid state
|
||||
perMessageDeflate.invalidWindowSize=An invalid windows of [{1}] size was specified for [{0}]. Valid values are whole numbers from 8 to 15 inclusive.
|
||||
perMessageDeflate.unknownParameter=An unknown extension parameter [{0}] was defined
|
||||
|
||||
transformerFactory.unsupportedExtension=The extension [{0}] is not supported
|
||||
|
||||
util.invalidMessageHandler=The message handler provided does not have an onMessage(Object) method
|
||||
util.invalidType=Unable to coerce value [{0}] to type [{1}]. That type is not supported.
|
||||
util.notToken=An illegal extension parameter was specified with name [{0}] and value [{1}]
|
||||
util.unknownDecoderType=The Decoder type [{0}] is not recognized
|
||||
|
||||
# Note the wsFrame.* messages are used as close reasons in WebSocket control
|
||||
# frames and therefore must be 123 bytes (not characters) or less in length.
|
||||
# Messages are encoded using UTF-8 where a single character may be encoded in
|
||||
# as many as 4 bytes.
|
||||
wsFrame.alreadyResumed=Message receiving has already been resumed.
|
||||
wsFrame.alreadySuspended=Message receiving has already been suspended.
|
||||
wsFrame.bufferTooSmall=No async message support and buffer too small. Buffer size: [{0}], Message size: [{1}]
|
||||
wsFrame.byteToLongFail=Too many bytes ([{0}]) were provided to be converted into a long
|
||||
wsFrame.closed=New frame received after a close control frame
|
||||
wsFrame.controlFragmented=A fragmented control frame was received but control frames may not be fragmented
|
||||
wsFrame.controlNoFin=A control frame was sent that did not have the fin bit set. Control frames are not permitted to use continuation frames.
|
||||
wsFrame.controlPayloadTooBig=A control frame was sent with a payload of size [{0}] which is larger than the maximum permitted of 125 bytes
|
||||
wsFrame.illegalReadState=Unexpected read state [{0}]
|
||||
wsFrame.invalidOpCode=A WebSocket frame was sent with an unrecognised opCode of [{0}]
|
||||
wsFrame.invalidUtf8=A WebSocket text frame was received that could not be decoded to UTF-8 because it contained invalid byte sequences
|
||||
wsFrame.invalidUtf8Close=A WebSocket close frame was received with a close reason that contained invalid UTF-8 byte sequences
|
||||
wsFrame.ioeTriggeredClose=An unrecoverable IOException occurred so the connection was closed
|
||||
wsFrame.messageTooBig=The message was [{0}] bytes long but the MessageHandler has a limit of [{1}] bytes
|
||||
wsFrame.noContinuation=A new message was started when a continuation frame was expected
|
||||
wsFrame.notMasked=The client frame was not masked but all client frames must be masked
|
||||
wsFrame.oneByteCloseCode=The client sent a close frame with a single byte payload which is not valid
|
||||
wsFrame.partialHeaderComplete=WebSocket frame received. fin [{0}], rsv [{1}], OpCode [{2}], payload length [{3}]
|
||||
wsFrame.sessionClosed=The client data cannot be processed because the session has already been closed
|
||||
wsFrame.suspendRequested=Suspend of the message receiving has already been requested.
|
||||
wsFrame.textMessageTooBig=The decoded text message was too big for the output buffer and the endpoint does not support partial messages
|
||||
wsFrame.wrongRsv=The client frame set the reserved bits to [{0}] for a message with opCode [{1}] which was not supported by this endpoint
|
||||
|
||||
wsFrameClient.ioe=Failure while reading data sent by server
|
||||
|
||||
wsHandshakeRequest.invalidUri=The string [{0}] cannot be used to construct a valid URI
|
||||
wsHandshakeRequest.unknownScheme=The scheme [{0}] in the request is not recognised
|
||||
|
||||
wsRemoteEndpoint.acquireTimeout=The current message was not fully sent within the specified timeout
|
||||
wsRemoteEndpoint.changeType=When sending a fragmented message, all fragments must be of the same type
|
||||
wsRemoteEndpoint.closed=Message will not be sent because the WebSocket session has been closed
|
||||
wsRemoteEndpoint.closedDuringMessage=The remainder of the message will not be sent because the WebSocket session has been closed
|
||||
wsRemoteEndpoint.closedOutputStream=This method may not be called as the OutputStream has been closed
|
||||
wsRemoteEndpoint.closedWriter=This method may not be called as the Writer has been closed
|
||||
wsRemoteEndpoint.flushOnCloseFailed=Batched messages still enabled after session has been closed. Unable to flush remaining batched message.
|
||||
wsRemoteEndpoint.invalidEncoder=The specified encoder of type [{0}] could not be instantiated
|
||||
wsRemoteEndpoint.noEncoder=No encoder specified for object of class [{0}]
|
||||
wsRemoteEndpoint.nullData=Invalid null data argument
|
||||
wsRemoteEndpoint.nullHandler=Invalid null handler argument
|
||||
wsRemoteEndpoint.sendInterrupt=The current thread was interrupted while waiting for a blocking send to complete
|
||||
wsRemoteEndpoint.tooMuchData=Ping or pong may not send more than 125 bytes
|
||||
wsRemoteEndpoint.writeTimeout=Blocking write timeout
|
||||
wsRemoteEndpoint.wrongState=The remote endpoint was in state [{0}] which is an invalid state for called method
|
||||
|
||||
# Note the following message is used as a close reason in a WebSocket control
|
||||
# frame and therefore must be 123 bytes (not characters) or less in length.
|
||||
# Messages are encoded using UTF-8 where a single character may be encoded in
|
||||
# as many as 4 bytes.
|
||||
wsSession.timeout=The WebSocket session [{0}] timeout expired
|
||||
|
||||
wsSession.closed=The WebSocket session [{0}] has been closed and no method (apart from close()) may be called on a closed session
|
||||
wsSession.created=Created WebSocket session [{0}]
|
||||
wsSession.doClose=Closing WebSocket session [{0}]
|
||||
wsSession.duplicateHandlerBinary=A binary message handler has already been configured
|
||||
wsSession.duplicateHandlerPong=A pong message handler has already been configured
|
||||
wsSession.duplicateHandlerText=A text message handler has already been configured
|
||||
wsSession.flushFailOnClose=Failed to flush batched messages on session close
|
||||
wsSession.instanceNew=Endpoint instance registration failed
|
||||
wsSession.invalidHandlerTypePong=A pong message handler must implement MessageHandler.Whole
|
||||
wsSession.messageFailed=Unable to write the complete message as the WebSocket connection has been closed
|
||||
wsSession.removeHandlerFailed=Unable to remove the handler [{0}] as it was not registered with this session
|
||||
wsSession.sendCloseFail=Failed to send close message for session [{0}] to remote endpoint
|
||||
wsSession.unknownHandler=Unable to add the message handler [{0}] as it was for the unrecognised type [{1}]
|
||||
wsSession.unknownHandlerType=Unable to add the message handler [{0}] as it was wrapped as the unrecognised type [{1}]
|
||||
|
||||
# Note the following message is used as a close reason in a WebSocket control
|
||||
# frame and therefore must be 123 bytes (not characters) or less in length.
|
||||
# Messages are encoded using UTF-8 where a single character may be encoded in
|
||||
# as many as 4 bytes.
|
||||
wsWebSocketContainer.shutdown=The web application is stopping
|
||||
|
||||
wsWebSocketContainer.asynchronousSocketChannelFail=Unable to open a connection to the server
|
||||
wsWebSocketContainer.defaultConfiguratorFail=Failed to create the default configurator
|
||||
wsWebSocketContainer.endpointCreateFail=Failed to create a local endpoint of type [{0}]
|
||||
wsWebSocketContainer.failedAuthentication=Failed to handle HTTP response code [{0}]. Authentication header was not accepted by server.
|
||||
wsWebSocketContainer.httpRequestFailed=The HTTP request to initiate the WebSocket connection failed
|
||||
wsWebSocketContainer.invalidExtensionParameters=The server responded with extension parameters the client is unable to support
|
||||
wsWebSocketContainer.invalidHeader=Unable to parse HTTP header as no colon is present to delimit header name and header value in [{0}]. The header has been skipped.
|
||||
wsWebSocketContainer.invalidStatus=The HTTP response from the server [{0}] did not permit the HTTP upgrade to WebSocket
|
||||
wsWebSocketContainer.invalidSubProtocol=The WebSocket server returned multiple values for the Sec-WebSocket-Protocol header
|
||||
wsWebSocketContainer.maxBuffer=This implementation limits the maximum size of a buffer to Integer.MAX_VALUE
|
||||
wsWebSocketContainer.missingAnnotation=Cannot use POJO class [{0}] as it is not annotated with @ClientEndpoint
|
||||
wsWebSocketContainer.missingLocationHeader=Failed to handle HTTP response code [{0}]. Missing Location header in response
|
||||
wsWebSocketContainer.missingWWWAuthenticateHeader=Failed to handle HTTP response code [{0}]. Missing WWW-Authenticate header in response
|
||||
wsWebSocketContainer.pathNoHost=No host was specified in URI
|
||||
wsWebSocketContainer.pathWrongScheme=The scheme [{0}] is not supported. The supported schemes are ws and wss
|
||||
wsWebSocketContainer.proxyConnectFail=Failed to connect to the configured Proxy [{0}]. The HTTP response code was [{1}]
|
||||
wsWebSocketContainer.redirectThreshold=Cyclic Location header [{0}] detected / reached max number of redirects [{1}] of max [{2}]
|
||||
wsWebSocketContainer.sessionCloseFail=Session with ID [{0}] did not close cleanly
|
||||
wsWebSocketContainer.sslEngineFail=Unable to create SSLEngine to support SSL/TLS connections
|
||||
wsWebSocketContainer.unsupportedAuthScheme=Failed to handle HTTP response code [{0}]. Unsupported Authentication scheme [{1}] returned in response
|
||||
36
java/org/apache/tomcat/websocket/LocalStrings_de.properties
Normal file
36
java/org/apache/tomcat/websocket/LocalStrings_de.properties
Normal file
@@ -0,0 +1,36 @@
|
||||
# Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
# contributor license agreements. See the NOTICE file distributed with
|
||||
# this work for additional information regarding copyright ownership.
|
||||
# The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
# (the "License"); you may not use this file except in compliance with
|
||||
# the License. You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
caseInsensitiveKeyMap.nullKey=Null Schlüssel sind nicht erlaubt
|
||||
|
||||
wsFrame.closed=Weiterer Frame empfangen nachdem bereits ein Kontroll Frame vom Typ Close empfangen wurde
|
||||
wsFrame.illegalReadState=Unerwarteter Lesestatus [{0}]
|
||||
wsFrame.wrongRsv=Der Client Frame setzt die reservierten Bits auf [{0}] für eine Nachricht mit dem opCode [{1}], der von diesem Endpunkt nicht unterstüzt wird
|
||||
|
||||
wsHandshakeRequest.invalidUri=Aus dem String [{0}] kann keine valide URI konstruiert werden
|
||||
|
||||
wsRemoteEndpoint.closedDuringMessage=Der Rest der Message wird nicht gesendet werden, da die WebSocket Session bereits beendet wurde
|
||||
wsRemoteEndpoint.tooMuchData=Ping oder Pong darf nicht mehr als 125 Bytes senden
|
||||
wsRemoteEndpoint.wrongState=Der entfernte Endpunkt war im Zustand [{0}] welcher für die aufgerufene Methode ungültig ist
|
||||
|
||||
wsSession.created=Websocket Sitzung [{0}] erzeugt
|
||||
wsSession.doClose=Schließe WebSocket-Sitzung [{1}]
|
||||
wsSession.duplicateHandlerText=Ein Text Message Handler ist bereits konfiguriert
|
||||
wsSession.instanceNew=Registrierung der Endpunkt-Instanz ist fehlgeschlagen
|
||||
|
||||
wsWebSocketContainer.asynchronousSocketChannelFail=Es kann keine Verbindung zum Server hergestellt werden
|
||||
wsWebSocketContainer.missingAnnotation=Die POJO Klasse [{0}] kann nicht verwendet werden da sie nicht mit @ClientEndpoint annotiert ist.
|
||||
wsWebSocketContainer.pathNoHost=In der URI wurde kein Host angegeben
|
||||
wsWebSocketContainer.sessionCloseFail=Die Sitzung mit der ID [{0}] wurde nicht sauber geschlossen.
|
||||
45
java/org/apache/tomcat/websocket/LocalStrings_es.properties
Normal file
45
java/org/apache/tomcat/websocket/LocalStrings_es.properties
Normal file
@@ -0,0 +1,45 @@
|
||||
# Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
# contributor license agreements. See the NOTICE file distributed with
|
||||
# this work for additional information regarding copyright ownership.
|
||||
# The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
# (the "License"); you may not use this file except in compliance with
|
||||
# the License. You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
caseInsensitiveKeyMap.nullKey=No se permiten llaves nulas (Null)
|
||||
|
||||
perMessageDeflate.duplicateParameter=La definición del parámetro de extención [{0}] esta duplicada
|
||||
perMessageDeflate.invalidWindowSize=Una ventana inválida de tamaño [{1}] fue especificada para [{0}]. Los valores válidos son números entre 8 y 15 incluyendo los extremos.
|
||||
|
||||
util.notToken=Un parámetro con extención ilegal fue especificado con nombre [{0}] y valor [{1}]
|
||||
util.unknownDecoderType=No se reconoce el decodificador tipo [{0}]
|
||||
|
||||
wsFrame.closed=Nuevo cuadro recibido luego de cerrar el cuadro de control
|
||||
wsFrame.notMasked=El cuadro del cliente no fue enmascarado, pero todos los cuadros de clientes deben ser enmascarados
|
||||
wsFrame.wrongRsv=El rango del cliente fija los bits reservados a [{0}] para un mensaje con opCode [{1}] el cual no fue soportado por este endpoint\n
|
||||
|
||||
wsHandshakeRequest.invalidUri=La cadena [{0}] no puede ser usada para construir una URI válida
|
||||
|
||||
wsRemoteEndpoint.closed=El mensaje no será enviado porque la sesión WebSocket ha sido cerrada
|
||||
wsRemoteEndpoint.closedDuringMessage=No se enviara el resto del mensaje debido a que la sesión WebSocket esta cerrada
|
||||
wsRemoteEndpoint.flushOnCloseFailed=Los mensages de lote estan habilitados aún después de haberse cerrado la sesión. Imposible descartar los messages de lote restantes.
|
||||
wsRemoteEndpoint.sendInterrupt=El hilo actual fue interrumpido mientras esperaba que se completara un envio de bloqueo
|
||||
wsRemoteEndpoint.tooMuchData=Ping o pong no pueden enviar más de 125 bytes
|
||||
wsRemoteEndpoint.wrongState=El endpoint remoto estaba en estado [{0}] el cual es un estado no válido para el método llamado
|
||||
|
||||
wsSession.closed=La sesión WebSocket [{0}] ha sido cerrada y ningún otro método (aparte de close()) puede ser llamado en una sesión cerrada\n
|
||||
wsSession.created=La sesion WebSocket [{0}] fue creada\n
|
||||
wsSession.doClose=Cerrando WebSocket sesión [{1}]
|
||||
wsSession.duplicateHandlerText=Un manejador de mensaje de texto ya ha sido configurado
|
||||
wsSession.instanceNew=Falló la registración de la instancia del dispoitivo final
|
||||
|
||||
wsWebSocketContainer.missingWWWAuthenticateHeader=Fallo al manejar el código de respuesta HTTP [{0}]. No existe la cabecera WWW-Authenticate en la respuesta
|
||||
wsWebSocketContainer.pathNoHost=No se especificó ningún host en URI
|
||||
wsWebSocketContainer.sessionCloseFail=La sesión con ID [{0}] no se cerró correctamente
|
||||
131
java/org/apache/tomcat/websocket/LocalStrings_fr.properties
Normal file
131
java/org/apache/tomcat/websocket/LocalStrings_fr.properties
Normal file
@@ -0,0 +1,131 @@
|
||||
# Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
# contributor license agreements. See the NOTICE file distributed with
|
||||
# this work for additional information regarding copyright ownership.
|
||||
# The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
# (the "License"); you may not use this file except in compliance with
|
||||
# the License. You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
asyncChannelGroup.createFail=Impossible de créer un AsynchronousChannelGroup dédié poour les clients Websockets ce qui est nécessaire pour éviter des fuites de mémoire dans un conteneur EE
|
||||
|
||||
asyncChannelWrapperSecure.check.notOk=La négociation TLS a renvoyé un état inattendu [{0}]
|
||||
asyncChannelWrapperSecure.check.unwrap=Des octets ont été écrits sur la sortie pendant la lecture
|
||||
asyncChannelWrapperSecure.check.wrap=Des octets ont été consommés depuis l'entrée lors d'une écriture
|
||||
asyncChannelWrapperSecure.closeFail=Impossible de fermer proprement le canal
|
||||
asyncChannelWrapperSecure.concurrentRead=Les opérations de lecture concurrentes ne sont pas permises
|
||||
asyncChannelWrapperSecure.concurrentWrite=Les opérations d'écriture concurrentes ne sont pas permises
|
||||
asyncChannelWrapperSecure.eof=Fin de flux inattendue
|
||||
asyncChannelWrapperSecure.notHandshaking=Etat NOT_HANDSHAKING inattendu pendant la négociation TLS
|
||||
asyncChannelWrapperSecure.statusUnwrap=Etat inattendu de SSLEngineResult après une opération unwrap()
|
||||
asyncChannelWrapperSecure.statusWrap=Etat inattendu de SSLEngineResult après une opération wrap()
|
||||
asyncChannelWrapperSecure.tooBig=Le résultat [{0}] est trop grand pour pouvoir être converti en Integer
|
||||
asyncChannelWrapperSecure.wrongStateRead=L'indicateur de lecture en cours était faux alors qu'il aurait dû vrai lors d'une tentative pour terminer une opération de lecture
|
||||
asyncChannelWrapperSecure.wrongStateWrite=L'indicateur d'écriture en cours était faux alors qu'il aurait dû vrai lors d'une tentative pour terminer une opération d'écriture
|
||||
|
||||
backgroundProcessManager.processFailed=Un processus d'arrière-plan a échoué
|
||||
|
||||
caseInsensitiveKeyMap.nullKey=Les clés nulles ne sont pas admises
|
||||
|
||||
futureToSendHandler.timeout=Le délai d''attente de l''opération est dépassé après avoir attendu [{0}] [{1}] pour qu''elle se termine
|
||||
|
||||
perMessageDeflate.deflateFailed=Impossible de décompresser une trame WebSocket compressée
|
||||
perMessageDeflate.duplicateParameter=Double définition pour le paramètre d''extension [{0}]
|
||||
perMessageDeflate.invalidState=Etat invalide
|
||||
perMessageDeflate.invalidWindowSize=Une taille [{1}] de fenêtre invalide a été spécifiée pour [{0}], les valeurs valides sont les entiers de 8 à 15 inclus
|
||||
perMessageDeflate.unknownParameter=Un paramètre d''extension inconnu [{0}] a été défini
|
||||
|
||||
transformerFactory.unsupportedExtension=L''extension [{0}] n''est pas supportée
|
||||
|
||||
util.invalidMessageHandler=Le gestionnaire de messages fourni n'a pas de méthode onMessage(Object)
|
||||
util.invalidType=Incapable de convertir la valeur [{0}] en le type [{1}]. Ce type n''est pas supporté.
|
||||
util.notToken=Un paramètre d''extension illégal a été spécifié avec le nom [{0}] et la valeur [{1}]
|
||||
util.unknownDecoderType=Le Decoder de type [{0}] n''est pas reconnu
|
||||
|
||||
wsFrame.alreadyResumed=Le message reçu a déjà été suspendu puis recommencé
|
||||
wsFrame.alreadySuspended=La réception des messages a déjà été suspendue
|
||||
wsFrame.bufferTooSmall=Le tampon de taille [{0}] est trop petit pour le message de taille [{1}] et les messages asynchrones ne sont pas supportés
|
||||
wsFrame.byteToLongFail=trop d''octets fournis ([{0}]) pour une conversion vers "long"
|
||||
wsFrame.closed=Une nouvelle trame (frame) a été reçue après une trame de contrôle de fermeture
|
||||
wsFrame.controlFragmented=Une trame de contrôle fragmentée a été reçue mais les trames de contrôle ne peuvent pas être fragmentées
|
||||
wsFrame.controlNoFin=Une trame de contrôle qui a été envoyée n'avait pas le bit fin mis, alors qu'elles ne peuvent pas utiliser de trame de continuation
|
||||
wsFrame.controlPayloadTooBig=Une trame de contrôle a été envoyée avec des données de taille [{0}] ce qui est supérieur aux 125 octets autorisés au maximum
|
||||
wsFrame.illegalReadState=Etat en lecture inattendu [{0}]
|
||||
wsFrame.invalidOpCode=Une trame a été envoyée avec un opCode non reconnu [{0}]
|
||||
wsFrame.invalidUtf8=Une trame texte Websocket a été reçue et n'a pu 6etre traitée car elle contenait des séquence d'octets UTF-8 invalides
|
||||
wsFrame.invalidUtf8Close=Une trame de fermeture Websocket a été reçue avec une cause qui contenait des séquences UTF-8 invalides
|
||||
wsFrame.ioeTriggeredClose=Une IOException non récupérable est survenue donc la connection a été fermée
|
||||
wsFrame.messageTooBig=Le message fait [{0}] octets mais le MessageHandler a une limite de [{1}] octets
|
||||
wsFrame.noContinuation=Un nouveau message a été démarré quand une trame de continuation était attendue
|
||||
wsFrame.notMasked=La trame du client n'a pas de masque alors que toutes les trames des clients doivent en avoir un
|
||||
wsFrame.oneByteCloseCode=Le client a envoyé une trame de fermeture avec un octet de données ce qui est invalide
|
||||
wsFrame.partialHeaderComplete=Une trame Websocket a été recue, fin [{0}], rsv [{1}], opCode [{2}], taille de données [{3}]
|
||||
wsFrame.sessionClosed=Les données du client ne peuvent pas être traitées car la session a déjà été fermée
|
||||
wsFrame.suspendRequested=La suspension de la réception des messages a déjà été demandée
|
||||
wsFrame.textMessageTooBig=Le message texte décodé était trop grand pour le tampon de sortie et la terminaison ne supporte pas les messages partiels
|
||||
wsFrame.wrongRsv=La trame cliente (client frame) a les bits réservés d''un message dont l''opCode est [{1}] définis à [{0}], et ce n''est pas supporté par cette terminaison
|
||||
|
||||
wsFrameClient.ioe=Echec lors de la lecture des données envoyées par le serveur
|
||||
|
||||
wsHandshakeRequest.invalidUri=La chaîne de caractères [{0}] ne peut être utilisée pour construire un URL valide
|
||||
wsHandshakeRequest.unknownScheme=Le schéma [{0}] de la requête n''est pas reconnu
|
||||
|
||||
wsRemoteEndpoint.acquireTimeout=Le message en cours n'a pas été complètement envoyé dans le délai imparti
|
||||
wsRemoteEndpoint.changeType=Quand un message fragmenté est envoyé, tous les fragments doivent être de même type
|
||||
wsRemoteEndpoint.closed=Le message ne sera pas envoyé parce que la session WebSocket a été fermée
|
||||
wsRemoteEndpoint.closedDuringMessage=Le reste du message ne sera pas envoyé parce que la session WebSocket est déjà fermée.
|
||||
wsRemoteEndpoint.closedOutputStream=La méthode ne peut pas être appelée alors que l'OutputStream a été fermée
|
||||
wsRemoteEndpoint.closedWriter=Cette méthode ne doit pas être appelée car le Writer a été fermé
|
||||
wsRemoteEndpoint.flushOnCloseFailed=Le groupement de messages est toujours actif après fermeture de la session, impossible d'envoyer les messages restants
|
||||
wsRemoteEndpoint.invalidEncoder=L''encodeur spécifié de type [{0}] n''a pu être instancié
|
||||
wsRemoteEndpoint.noEncoder=Pas d''encodeur spécifié pour un objet de classe [{0}]
|
||||
wsRemoteEndpoint.nullData=Argument nul invalide.
|
||||
wsRemoteEndpoint.nullHandler=Argument null invalide pour le gestionnaire
|
||||
wsRemoteEndpoint.sendInterrupt=Le thread actuel a été interrompu alors qu'il attendait qu'un envoi bloquant ne se termine
|
||||
wsRemoteEndpoint.tooMuchData=Un ping ou pong ne peut pas envoyer plus de 125 octets
|
||||
wsRemoteEndpoint.writeTimeout=Délai d'attente dépassé pour l'écriture bloquante
|
||||
wsRemoteEndpoint.wrongState=La terminaison distante est dans l''état [{0}] ce qui est invalide pour la méthode appelée
|
||||
|
||||
wsSession.closed=La session WebSocket [{0}] a été fermée et aucune méthode (à part close()) ne peut être appelée sur une session fermée
|
||||
wsSession.created=Création de la session WebSocket [{0}]
|
||||
wsSession.doClose=Fermeture de la session WebSocket [{1}]
|
||||
wsSession.duplicateHandlerBinary=Un gestionnaire de message binaire a déjà été configuré
|
||||
wsSession.duplicateHandlerPong=Un gestionnaire de messages pong a déjà été configuré
|
||||
wsSession.duplicateHandlerText=Un gestionnaire de message texte a déjà été configuré
|
||||
wsSession.flushFailOnClose=Impossible d'envoyer la file de messages lors de la fermeture de la session
|
||||
wsSession.instanceNew=L'enregistrement de l'instance de la terminaison a échoué
|
||||
wsSession.invalidHandlerTypePong=Un gestionnaire de message pong doit implémenter MessageHandler.Whole
|
||||
wsSession.messageFailed=Impossible d'écrire le message WebSocket complet car la connection a été fermée
|
||||
wsSession.removeHandlerFailed=Impossible d''enlever le gestionnaire [{0}] car il n''était pas enregistré dans la session
|
||||
wsSession.sendCloseFail=Impossible d''envoyer le message de fermeture pour la session [{0}] à la terminaison distante
|
||||
wsSession.timeout=Le délai d''attente maximum de la session WebSocket [{0}] a été dépassé
|
||||
wsSession.unknownHandler=Impossible d''ajouter le gestionnaire de messages [{0}] pour le type non reconnu [{1}]
|
||||
wsSession.unknownHandlerType=Incapable d''ajouter le gestionnaire de messages [{0}] puisqu''il est enveloppé (wrapped) comme le type non reconnu [{1}]
|
||||
|
||||
wsWebSocketContainer.asynchronousSocketChannelFail=Impossible d'ouvrir une connection vers le serveur
|
||||
wsWebSocketContainer.defaultConfiguratorFail=Impossible de créer le configurateur par défaut
|
||||
wsWebSocketContainer.endpointCreateFail=Echec de création d''un point d''entrée local de type [{0}]
|
||||
wsWebSocketContainer.failedAuthentication=Echec du traitement du code de réponse HTTP [{0}], l''en-tête d''authentification n''a pas été accepté par le serveur
|
||||
wsWebSocketContainer.httpRequestFailed=La requête HTTP pour initier la connection WebSocket a échoué
|
||||
wsWebSocketContainer.invalidExtensionParameters=Le serveur a répondu avec des paramètres d'extension que le client n'est pas capable de traiter
|
||||
wsWebSocketContainer.invalidHeader=Impossible de traiter l''en-tête HTTP car deux-points n''est pas présents pour délimiter le nom et la valeur dans [{0}], l''en-tête a été sauté
|
||||
wsWebSocketContainer.invalidStatus=La réponse HTTP du serveur [{0}] n''a pas permis une mise à niveau de HTTP vers WebSocket
|
||||
wsWebSocketContainer.invalidSubProtocol=Le serveur WebSocket a renvoyé plusieurs valeurs pour l'en-tête Sec-WebSocket-Protocol
|
||||
wsWebSocketContainer.maxBuffer=L'implémentation limites la valeur maximale d'un tampon à Integer.MAX_VALUE
|
||||
wsWebSocketContainer.missingAnnotation=Impossible d''utiliser la classe POJO [{0}] car elle n''est pas annotée avec @ClientEndpoint
|
||||
wsWebSocketContainer.missingLocationHeader=Echec du traitement du code de réponse HTTP [{0}], l''en-tête location n''est pas présent dans la réponse
|
||||
wsWebSocketContainer.missingWWWAuthenticateHeader=Echec de traitement du code HTTP de réponse [{0}] : la réponse ne contient pas de header WWW-Authenticate.
|
||||
wsWebSocketContainer.pathNoHost=Aucun hôte n'est spécifié dans l'URI
|
||||
wsWebSocketContainer.pathWrongScheme=Le schéma [{0}] n''est pas supporté, seuls sont supportés ws et wss
|
||||
wsWebSocketContainer.proxyConnectFail=Impossible de se connecter au Proxy [{0}] configuré, le code HTTP de la réponse est [{0}]
|
||||
wsWebSocketContainer.redirectThreshold=L''en-tête Location [{0}] est cyclique, le nombre de redirections [{1}] a été atteint sur le maximum [{2}]
|
||||
wsWebSocketContainer.sessionCloseFail=La session avec ID [{0}] n''a pas été fermée proprement.
|
||||
wsWebSocketContainer.shutdown=L'application web s'arrête
|
||||
wsWebSocketContainer.sslEngineFail=Impossible de créer un SSLEngine pour supporter les connections TLS
|
||||
wsWebSocketContainer.unsupportedAuthScheme=Impossible de gérer le code de réponse HTTP [{0}], un schéma authentification [{1}] non supporté a été retourné
|
||||
131
java/org/apache/tomcat/websocket/LocalStrings_ja.properties
Normal file
131
java/org/apache/tomcat/websocket/LocalStrings_ja.properties
Normal file
@@ -0,0 +1,131 @@
|
||||
# Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
# contributor license agreements. See the NOTICE file distributed with
|
||||
# this work for additional information regarding copyright ownership.
|
||||
# The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
# (the "License"); you may not use this file except in compliance with
|
||||
# the License. You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
asyncChannelGroup.createFail=JavaEEコンテナなどの複雑なクラスローダ環境でメモリリークを防ぐために必要なWebSocketクライアント専用のAsynchronousChannelGroupを作成できません
|
||||
|
||||
asyncChannelWrapperSecure.check.notOk=TLSハンドシェイクが予期しないステータスを返しました[{0}]
|
||||
asyncChannelWrapperSecure.check.unwrap=読み込み中にバイトが出力に書き込まれました。
|
||||
asyncChannelWrapperSecure.check.wrap=書き込み中に入力からバイトが消費されました。
|
||||
asyncChannelWrapperSecure.closeFail=チャンネルをきれいに閉じることができませんでした
|
||||
asyncChannelWrapperSecure.concurrentRead=コンカレントな読み取り操作を行うことはできません。
|
||||
asyncChannelWrapperSecure.concurrentWrite=コンカレントな書き込み操作を行うことはできません。
|
||||
asyncChannelWrapperSecure.eof=予期せぬ位置にストリームの終端を検出しました。
|
||||
asyncChannelWrapperSecure.notHandshaking=TLSハンドシェイク中に予期しない状態[NOT_HANDSHAKING]
|
||||
asyncChannelWrapperSecure.statusUnwrap=unwrap()操作後のSSLEngineResultの予期しないステータス
|
||||
asyncChannelWrapperSecure.statusWrap=wrap()操作後のSSLEngineResultの予期しないステータス。
|
||||
asyncChannelWrapperSecure.tooBig=Integer として解釈するには大きすぎる結果 [{0}] です。
|
||||
asyncChannelWrapperSecure.wrongStateRead=読み取り操作の完了中に読み取り中を意味するフラグが false になっていることを検出しました (true になっているべきです)。
|
||||
asyncChannelWrapperSecure.wrongStateWrite=書き込み操作を完了しようとすると、書き込みが進行中であることを示すフラグがfalse(trueであったはずです)であることが判明しました。
|
||||
|
||||
backgroundProcessManager.processFailed=バックグラウンド処理が失敗しました。
|
||||
|
||||
caseInsensitiveKeyMap.nullKey=null 値はキーに使用できません。
|
||||
|
||||
futureToSendHandler.timeout=[{0}] [{1}]を完了するのを待ってから操作がタイムアウトしました
|
||||
|
||||
perMessageDeflate.deflateFailed=圧縮された WebSocket フレームを展開できません。
|
||||
perMessageDeflate.duplicateParameter=[{0}]拡張パラメータの重複した定義
|
||||
perMessageDeflate.invalidState=不正な状態です。
|
||||
perMessageDeflate.invalidWindowSize=[{0}] のウインドウサイズに不正な値 [{1}] が指定されました。ウインドウサイズは 8 より大きく 15 未満でなければなりません。
|
||||
perMessageDeflate.unknownParameter=未知の拡張パラメーター [{0}] が指定されました。
|
||||
|
||||
transformerFactory.unsupportedExtension=未対応の拡張 [{0}] です。
|
||||
|
||||
util.invalidMessageHandler=提供されたメッセージハンドラにonMessage(Object)メソッドがありません
|
||||
util.invalidType=値[{0}]をタイプ[{1}]に強制できません。 このタイプはサポートされていません。
|
||||
util.notToken=パラメーター名 [{0}]、値 [{1}] の不正な拡張パラメーターが指定されました。
|
||||
util.unknownDecoderType=デコーダタイプ[{0}]は認識されません
|
||||
|
||||
wsFrame.alreadyResumed=メッセージの受信は既に再開されています。
|
||||
wsFrame.alreadySuspended=メッセージの受信は既に中断されています。
|
||||
wsFrame.bufferTooSmall=非同期メッセージの非サポートとバッファーが小さすぎます。 バッファサイズ:[{0}]、メッセージサイズ:[{1}]
|
||||
wsFrame.byteToLongFail=long へ変換するために巨大なバイト列 ([{0}]) が指定されました。
|
||||
wsFrame.closed=閉じたコントローフレームで新しいデータフレームを受信しました。
|
||||
wsFrame.controlFragmented=断片化されたコントロールフレームが受信されましたが、コントロールフレームは断片化されません。
|
||||
wsFrame.controlNoFin=fin ビットがオフになっているコントロールフレームを受信しました。コントロールフレームを継続フレームとして使用することはできません。
|
||||
wsFrame.controlPayloadTooBig=コントロールフレームは [{0}] バイトのペイロードで送信されましたが、送信可能な最大値の 125 バイトを越えています。
|
||||
wsFrame.illegalReadState=予期しない読み取り状態[{0}]
|
||||
wsFrame.invalidOpCode=未知の opCode [{0}] の WebSocket フレームを受信しました。
|
||||
wsFrame.invalidUtf8=無効なバイトシーケンスが含まれていたため、UTF-8にデコードできなかったWebSocketテキストフレームが受信されました。
|
||||
wsFrame.invalidUtf8Close=WebSocket は不正な UTF-8 バイト列を含むことを原因とするクローズフレームを受信しました。
|
||||
wsFrame.ioeTriggeredClose=回復不能なIOException が発生したためコネクションを切断します。
|
||||
wsFrame.messageTooBig=メッセージは[{0}]バイトの長さでしたが、MessageHandlerには[{1}]バイトの制限があります。
|
||||
wsFrame.noContinuation=continuation フレームが予想されたときに新しいメッセージが開始されました。
|
||||
wsFrame.notMasked=クライアントのフレームはマスクされていませんが、全てのクライアントのフレームはマスクしなければなりません。
|
||||
wsFrame.oneByteCloseCode=クライアントは有効ではない単一バイトのペイロードを含むクローズフレームを送信しました。
|
||||
wsFrame.partialHeaderComplete=WebSocket フレームを受信しました。fin [{0}]、rsv [{1}]、OpCode [{2}]、ペイロード長 [{3}]
|
||||
wsFrame.sessionClosed=セッションが既に閉じられているため、クライアントデータを処理できません。
|
||||
wsFrame.suspendRequested=すでにメッセージの受信中断を要求しています。
|
||||
wsFrame.textMessageTooBig=デコードされたテキストメッセージが出力バッファにとって大きすぎます。さらにエンドポイントが部分メッセージをサポートしていません。
|
||||
wsFrame.wrongRsv=クライアントフレームは opCode [{1}] でメッセージを送信するため [{0}] の予約ビットを設定しましたが、エンドポイントは対応していません。
|
||||
|
||||
wsFrameClient.ioe=サーバーから送信されたデータを読み取る際にエラーが発生しました。
|
||||
|
||||
wsHandshakeRequest.invalidUri=文字列 [{0}] は正常な URI に含めることができません。
|
||||
wsHandshakeRequest.unknownScheme=リクエストのスキーム[{0}]が認識されません
|
||||
|
||||
wsRemoteEndpoint.acquireTimeout=指定した時間内にメッセージを送信できませんでした。
|
||||
wsRemoteEndpoint.changeType=フラグメント化されたメッセージを送信する場合、すべてのフラグメントは同じタイプでなければなりません。
|
||||
wsRemoteEndpoint.closed=WebSocket セッションは切断済みのためメッセージを送信しません。
|
||||
wsRemoteEndpoint.closedDuringMessage=WebSocket セッションが切断されているため残りのメッセージは送信できません。
|
||||
wsRemoteEndpoint.closedOutputStream=このメソッドは、OutputStreamが閉じられたときに呼び出されない場合があります。
|
||||
wsRemoteEndpoint.closedWriter=Writerがクローズされているため、このメソッドを呼び出すことはできません。
|
||||
wsRemoteEndpoint.flushOnCloseFailed=セッションが閉じられた後にまだ可能であったバッチメッセージ。 残りのバッチメッセージをフラッシュできません。
|
||||
wsRemoteEndpoint.invalidEncoder=指定されたタイプ[{0}]のエンコーダをインスタンス化できませんでした。
|
||||
wsRemoteEndpoint.noEncoder=クラス [{0}] のオブジェクトのエンコーダーが未指定です。
|
||||
wsRemoteEndpoint.nullData=無効なNullデータ引数
|
||||
wsRemoteEndpoint.nullHandler=無効なnullハンドラ引数
|
||||
wsRemoteEndpoint.sendInterrupt=同期送信の完了待ちスレッドに割り込みが発生しました。
|
||||
wsRemoteEndpoint.tooMuchData=Ping および Pong は 125 バイト以上送信できません。
|
||||
wsRemoteEndpoint.writeTimeout=ブロッキング書き込みのタイムアウト
|
||||
wsRemoteEndpoint.wrongState=リモートエンドポイントの状態 [{0}] は呼び出したメソッドに対して不正な状態です。
|
||||
|
||||
wsSession.closed=WebSocket セッション [{0}] を切断しました。切断済みのセッションに close() 以外のメソッド呼び出しをすることはありません。
|
||||
wsSession.created=WebSocket セッション [{0}] を作成しました。
|
||||
wsSession.doClose=WebSocket セッション [{1}] を切断します。
|
||||
wsSession.duplicateHandlerBinary=バイナリメッセージハンドラは既に設定されています。
|
||||
wsSession.duplicateHandlerPong=pongメッセージハンドラは既に設定されています。
|
||||
wsSession.duplicateHandlerText=テキストメッセージハンドラはすでに構成されています。
|
||||
wsSession.flushFailOnClose=セッション切断時にバッチメッセージをフラッシュできませんでした。
|
||||
wsSession.instanceNew=エンドポイントインスタンスの登録に失敗しました。
|
||||
wsSession.invalidHandlerTypePong=pongメッセージハンドラはMessageHandler.Wholeを実装する必要があります。
|
||||
wsSession.messageFailed=WebSocket コネクションが切断されているため、完了メッセージを送信できません。
|
||||
wsSession.removeHandlerFailed=セッションに登録されていないためハンドラー [{0}] を解除できません。
|
||||
wsSession.sendCloseFail=セッション[{0}]のクローズメッセージをリモートエンドポイントに送信できませんでした。
|
||||
wsSession.timeout=WebSocketセッション[{0}]タイムアウトが切れました。
|
||||
wsSession.unknownHandler=認識できないタイプ[{1}]のメッセージハンドラ[{0}]を追加できません。
|
||||
wsSession.unknownHandlerType=認識できない型[{1}]としてラップされたメッセージハンドラ[{0}]を追加できません。
|
||||
|
||||
wsWebSocketContainer.asynchronousSocketChannelFail=サーバーへの接続を開始できません。
|
||||
wsWebSocketContainer.defaultConfiguratorFail=デフォルトコンフィグレータの作成に失敗しました。
|
||||
wsWebSocketContainer.endpointCreateFail=クラス [{0}] のローカルエンドポイントを作成できません。
|
||||
wsWebSocketContainer.failedAuthentication=HTTP応答コード[{0}]を処理できませんでした。 認証ヘッダーがサーバーによって受け入れられませんでした。
|
||||
wsWebSocketContainer.httpRequestFailed=WebSocket接続を開始するHTTPリクエストが失敗しました。
|
||||
wsWebSocketContainer.invalidExtensionParameters=サーバーはクライアントの解釈できない拡張パラメーターで応答しました。
|
||||
wsWebSocketContainer.invalidHeader=ヘッダー名と値の区切り文字(コロン)がない HTTP ヘッダー [{0}] は解釈できないため無視します。
|
||||
wsWebSocketContainer.invalidStatus=サーバー[{0}]からのHTTPレスポンスがWebSocketへのHTTPアップグレードを許可しませんでした。
|
||||
wsWebSocketContainer.invalidSubProtocol=WebSocketサーバーは、Sec-WebSocket-Protocolヘッダーに複数の値を返しました。
|
||||
wsWebSocketContainer.maxBuffer=この実装はバッファの最大サイズをInteger.MAX_VALUEに制限します。
|
||||
wsWebSocketContainer.missingAnnotation=POJOクラス[{0}]は@ClientEndpointでアノテーションされていないため使用できません。
|
||||
wsWebSocketContainer.missingLocationHeader=HTTP レスポンスコード [{0}] を処理できません。レスポンスに Location ヘッダーがありませんでした。
|
||||
wsWebSocketContainer.missingWWWAuthenticateHeader=HTTP 応答コード [{0}] を処理できません。応答ヘッダーに WWW-Authenticate ヘッダーがありません。
|
||||
wsWebSocketContainer.pathNoHost=URIにホストが指定されていません
|
||||
wsWebSocketContainer.pathWrongScheme=スキーマ[{0}]はサポートされていません。 サポートされているスキーマはwsとwssです
|
||||
wsWebSocketContainer.proxyConnectFail=設定されたプロキシ[{0}]に接続できませんでした。 HTTPレスポンスコードは[{1}]でした。
|
||||
wsWebSocketContainer.redirectThreshold=Locationヘッダー [{0}] の循環を検出、リダイレクト回数 [{1}] が上限値 [{2}] を超過しました。
|
||||
wsWebSocketContainer.sessionCloseFail=ID [{0}] のセッションは正常に切断しませんでした。
|
||||
wsWebSocketContainer.shutdown=Webアプリケーションは停止中です
|
||||
wsWebSocketContainer.sslEngineFail=SSL/TLS 接続のための SSL エンジンを作成できません。
|
||||
wsWebSocketContainer.unsupportedAuthScheme=HTTPレスポンスコード[{0}]を処理できませんでした。 サポートされていない認証スキーム[{1}]がレスポンスで返されました。
|
||||
131
java/org/apache/tomcat/websocket/LocalStrings_ko.properties
Normal file
131
java/org/apache/tomcat/websocket/LocalStrings_ko.properties
Normal file
@@ -0,0 +1,131 @@
|
||||
# Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
# contributor license agreements. See the NOTICE file distributed with
|
||||
# this work for additional information regarding copyright ownership.
|
||||
# The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
# (the "License"); you may not use this file except in compliance with
|
||||
# the License. You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
asyncChannelGroup.createFail=JavaEE 컨테이너들과 같은 복잡한 클래스로더 환경에서 메모리 누수를 방지하기 위해 필수적이며 웹소켓 클라이언트들을 위한, 전용 AsynchronousChannelGroup를 생성할 수 없습니다.
|
||||
|
||||
asyncChannelWrapperSecure.check.notOk=TLS handshake가 예기치 않은 상태값 [{0}]을(를) 반환했습니다.
|
||||
asyncChannelWrapperSecure.check.unwrap=데이터를 읽는 동안, 바이트들이 출력으로 쓰여졌습니다.
|
||||
asyncChannelWrapperSecure.check.wrap=쓰기 작업을 하는 동안, 입력으로부터 바이트들이 소비되었습니다.
|
||||
asyncChannelWrapperSecure.closeFail=채널을 깨끗하게 닫지 못했습니다.
|
||||
asyncChannelWrapperSecure.concurrentRead=동시 발생적인 읽기 오퍼레이션들은 허용되지 않습니다.
|
||||
asyncChannelWrapperSecure.concurrentWrite=동시적인 쓰기 오퍼레이션들은 허용되지 않습니다.
|
||||
asyncChannelWrapperSecure.eof=예기치 않은 스트림의 끝
|
||||
asyncChannelWrapperSecure.notHandshaking=TLS handshake 과정 중 예기치 않은 상태 [NOT_HANDSHAKING]입니다.
|
||||
asyncChannelWrapperSecure.statusUnwrap=unwrap() 오퍼레이션 후에, SSLEngineResult의 예기치 않은 상태입니다.
|
||||
asyncChannelWrapperSecure.statusWrap=wrap() 오퍼레이션 수행 이후, SSLEngineResult의 예기치 않은 상태입니다.
|
||||
asyncChannelWrapperSecure.tooBig=결과 [{0}]이(가) 너무 커서, 정수로서 표현될 수 없습니다.
|
||||
asyncChannelWrapperSecure.wrongStateRead=읽기 오퍼레이션을 완료하려 시도할 때에, 읽기 진행 중임을 표시하는 플래그가 false인 것으로 (true였어만 했음에도) 밝혀졌습니다.
|
||||
asyncChannelWrapperSecure.wrongStateWrite=쓰기 오퍼레이션을 완료하려 시도할 때, 쓰기 진행 중이라는 플래그가 false로 (true였어야 함에도) 밝혀졌습니다.
|
||||
|
||||
backgroundProcessManager.processFailed=백그라운드 프로세스가 실패했습니다.
|
||||
|
||||
caseInsensitiveKeyMap.nullKey=널인 키들은 허용되지 않습니다.
|
||||
|
||||
futureToSendHandler.timeout=[{0}] [{1}]이(가) 완료되기를 기다린 후, 작업 제한 시간을 초과했습니다.
|
||||
|
||||
perMessageDeflate.deflateFailed=압축된 웹소켓 프레임의 압축을 풀지 못했습니다.
|
||||
perMessageDeflate.duplicateParameter=[{0}] 확장 파라미터가 중복 정의되어 있습니다.
|
||||
perMessageDeflate.invalidState=유효하지 않은 상태
|
||||
perMessageDeflate.invalidWindowSize=크기가 [{1}]인 유효하지 않은 윈도우들이 [{0}]을 위해 지정되었습니다. 유효한 값들의 범위는 8에서 15까지의 모든 숫자들입니다.
|
||||
perMessageDeflate.unknownParameter=알 수 없는 확장 파라미터 [{0}]은(는), 정의되지 않았습니다.
|
||||
|
||||
transformerFactory.unsupportedExtension=Extension [{0}]은(는) 지원되지 않습니다.
|
||||
|
||||
util.invalidMessageHandler=제공된 메시지 핸들러에 onMessage(Object) 메소드가 없습니다.
|
||||
util.invalidType=[{0}] 값을 타입 [{1}](으)로 강제 변환시킬 수 없습니다. 해당 타입은 지원되지 않습니다.
|
||||
util.notToken=허용되지 않는 확장 파라미터가 지정되었습니다. 이름: [{0}], 값: [{1}].
|
||||
util.unknownDecoderType=해당 디코더 타입 [{0}]은(는) 인식되지 않습니다.
|
||||
|
||||
wsFrame.alreadyResumed=메시지 수신이 이미 재개되었습니다.
|
||||
wsFrame.alreadySuspended=메시지 수신이 이미 일시 정지되었습니다.
|
||||
wsFrame.bufferTooSmall=비동기 메시지를 지원할 수 없습니다. 버퍼가 너무 작습니다. 버퍼 크기: [{0}], 메시지 크기: [{1}]
|
||||
wsFrame.byteToLongFail=너무 많은 바이트들([{0}])이 제공되어, long으로 변환될 수 없었습니다.
|
||||
wsFrame.closed=Control 프레임을 닫은 이후에 새로운 프레임을 받았습니다.
|
||||
wsFrame.controlFragmented=단편화된(fragmented) Control 프레임을 받았지만, Control 프레임은 단편화될 수 없습니다.
|
||||
wsFrame.controlNoFin=fin 비트셋을 포함하지 않은 control 프레임이 전송되었습니다. Control 프레임들에 continuation 프레임들이 사용되는 것이 허용되지 않습니다.
|
||||
wsFrame.controlPayloadTooBig=Control 프레임이, 크기가 [{0}]인 payload와 함께 전송되었는데, 이는 최대 허용치인 125바이트를 초과합니다.
|
||||
wsFrame.illegalReadState=예기치 않은 읽기 상태: [{0}]
|
||||
wsFrame.invalidOpCode=인식되지 않는 opCode [{0}]와(과) 함께, 웹소켓 프레임이 전송되었습니다.
|
||||
wsFrame.invalidUtf8=웹소켓 텍스트 프레임을 받았는데, 유효하지 않은 바이트 시퀀스를 포함하고 있기 때문에, UTF-8로 디코딩될 수 없었습니다.
|
||||
wsFrame.invalidUtf8Close=웹소켓 닫기 프레임을 접수하였는데, 닫기 사유는 유효하지 않은 UTF-8 바이트 시퀀스들을 포함했다는 것입니다.
|
||||
wsFrame.ioeTriggeredClose=복구될 수 없는 IOException이 발생하여 연결이 닫혔습니다.
|
||||
wsFrame.messageTooBig=메시지가 [{0}] 바이트의 길이로 되어 있으나, MessageHandler는 [{1}] 바이트의 제한값을 가지고 있습니다.
|
||||
wsFrame.noContinuation=Continuation 프레임이 요구될 때에, 새로운 메시지가 시작되었습니다.
|
||||
wsFrame.notMasked=클라이언트 프레임이 마스크 되어 있지 않습니다. 모든 클라이언트 프레임들은 반드시 마스크 되어야 합니다.
|
||||
wsFrame.oneByteCloseCode=클라이언트가 단일 바이트의 payload를 가진 닫기 프레임을 보냈는데, 이는 유효하지 않습니다.
|
||||
wsFrame.partialHeaderComplete=웹소켓 프레임을 받았습니다. fin [{0}], rsv [{1}], OpCode [{2}], payload 길이 [{3}]
|
||||
wsFrame.sessionClosed=해당 세션이 이미 닫혔기 때문에, 클라이언트 데이터가 처리될 수 없습니다.
|
||||
wsFrame.suspendRequested=메시지 수신의 일시 정지가 이미 요청되었습니다.
|
||||
wsFrame.textMessageTooBig=디코드된 텍스트 메시지가 출력 버퍼에 비해 너무 크며, 해당 엔드포인트는 partial 메시지들을 지원하지 않습니다.
|
||||
wsFrame.wrongRsv=클라이언트 프레임이, opCode [{1}]을(를) 포함한 메시지를 위해, reserved 비트들을 [{0}](으)로 설정했는데, 이는 이 엔드포인트에 의해 지원되지 않습니다.
|
||||
|
||||
wsFrameClient.ioe=서버가 전송한 데이터를 읽는 중 실패
|
||||
|
||||
wsHandshakeRequest.invalidUri=문자열 [{0}]은(는) 유효한 URI를 구성하는 데 사용될 수 없습니다.
|
||||
wsHandshakeRequest.unknownScheme=요청의 스킴 [{0}]이(가) 인식되지 않는 스킴입니다.
|
||||
|
||||
wsRemoteEndpoint.acquireTimeout=지정된 제한 시간 내에, 현재 메시지가 완전히 전송되지 않았습니다.
|
||||
wsRemoteEndpoint.changeType=단편화된(fragmented) 메시지를 전송할 때, 모든 fragment들은 반드시 동일한 타입이어야 합니다.
|
||||
wsRemoteEndpoint.closed=웹소켓 세션이 이미 닫혔기 때문에, 메시지가 전달되지 않을 것입니다.
|
||||
wsRemoteEndpoint.closedDuringMessage=웹소켓 세션이 이미 닫혔기 때문에, 메시지의 나머지 부분은 전달되지 않을 것입니다.
|
||||
wsRemoteEndpoint.closedOutputStream=OutputStream이 이미 닫혀 있으므로, 이 메소드는 호출될 수 없습니다.
|
||||
wsRemoteEndpoint.closedWriter=Writer가 이미 닫혔기 때문에, 이 메소드는 호출될 수 없습니다.
|
||||
wsRemoteEndpoint.flushOnCloseFailed=세션이 이미 종료된 이후에도, 메시지들이 배치(batch)에 포함되어 있습니다. 배치에 남아있는 메시지들을 배출할 수 없습니다.
|
||||
wsRemoteEndpoint.invalidEncoder=지정된 타입 [{0}]의 Encoder의 인스턴스를 생성할 수 없었습니다.
|
||||
wsRemoteEndpoint.noEncoder=클래스 [{0}]의 객체를 위한 인코더가 지정되지 않았습니다.
|
||||
wsRemoteEndpoint.nullData=유효하지 않은 널 데이터 아규먼트
|
||||
wsRemoteEndpoint.nullHandler=유효하지 않은 널 핸들러 아규먼트
|
||||
wsRemoteEndpoint.sendInterrupt=현재 쓰레드가, blocking 전송이 완료되기를 기다리던 중 중단되었습니다.
|
||||
wsRemoteEndpoint.tooMuchData=Ping 또는 pong은 125 바이트를 초과한 데이터를 보낼 수 없습니다.
|
||||
wsRemoteEndpoint.writeTimeout=Blocking 쓰기가 제한 시간 초과되었습니다.
|
||||
wsRemoteEndpoint.wrongState=호출된 메소드에 대해, 원격 엔드포인트가 유효하지 않은 상태 [{0}]에 있습니다.
|
||||
|
||||
wsSession.closed=웹소켓 세션 [{0}]은(는) 이미 닫혔으며, (close()를 제외한) 어떤 메소드도 닫힌 세션에 호출되어서는 안됩니다.
|
||||
wsSession.created=웹소켓 세션 [{0}]을(를) 생성했습니다.
|
||||
wsSession.doClose=웹소켓 세션 [{0}]을(를) 닫습니다.
|
||||
wsSession.duplicateHandlerBinary=바이너리 메시지 핸들러가 이미 설정되었습니다.
|
||||
wsSession.duplicateHandlerPong=Pong 메시지 핸들러가 이미 설정되었습니다.
|
||||
wsSession.duplicateHandlerText=텍스트 메시지 핸들러가 이미 설정되어 있습니다.
|
||||
wsSession.flushFailOnClose=세션이 닫힐 때, 배치에 쌓인 메시지들을 배출하지 못했습니다.
|
||||
wsSession.instanceNew=엔드포인트 인스턴스 등록 실패
|
||||
wsSession.invalidHandlerTypePong=Pong 메시지 핸들러는 반드시 MessageHandler.Whole을 구현해야 합니다.
|
||||
wsSession.messageFailed=웹소켓 연결이 이미 닫혔기 때문에, 완전한 메시지를 쓸 수 없습니다.
|
||||
wsSession.removeHandlerFailed=핸들러 [{0}]이(가), 이 세션과 함께 등록되지 않았었기 때문에, 제거될 수 없습니다.
|
||||
wsSession.sendCloseFail=세션 [{0}]을(를) 위해, 원격 엔드포인트로 세션 닫기 메시지를 보내지 못했습니다.
|
||||
wsSession.timeout=웹소켓 세션 [{0}]이(가) 제한 시간 초과로 만료되었습니다.
|
||||
wsSession.unknownHandler=인식되지 않는 타입 [{1}]을(를) 위한 것이었기에, 해당 메시지 핸들러 [{0}]을(를) 추가할 수 없습니다.
|
||||
wsSession.unknownHandlerType=메시지 핸들러 [{0}]이(가) 인식되지 않는 타입 [{1}](으)로 wrap 되어 있어, 추가할 수 없습니다.
|
||||
|
||||
wsWebSocketContainer.asynchronousSocketChannelFail=서버에 대한 연결을 열 수 없습니다.
|
||||
wsWebSocketContainer.defaultConfiguratorFail=기본 Configurator를 생성하지 못했습니다.
|
||||
wsWebSocketContainer.endpointCreateFail=타입이 [{0}]인 로컬 엔드포인트를 생성하지 못했습니다.
|
||||
wsWebSocketContainer.failedAuthentication=HTTP 응답 코드 [{0}]을(를) 처리하지 못했습니다. 인증 헤더가 서버에 의해 받아들여지지 않았습니다.
|
||||
wsWebSocketContainer.httpRequestFailed=웹소켓 연결을 초기화하기 위한 HTTP 요청이 실패했습니다.
|
||||
wsWebSocketContainer.invalidExtensionParameters=서버가, 클라이언트가 지원할 수 없는 확장 파라미터들과 함께 응답했습니다.
|
||||
wsWebSocketContainer.invalidHeader=[{0}] 내에서, 헤더 이름과 헤더 값을 구분하기 위한 콜론('':'')이 존재하지 않기에, HTTP 헤더를 파싱할 수 없습니다. 해당 헤더를 건너뛰었습니다.
|
||||
wsWebSocketContainer.invalidStatus=서버 [{0}](으)로부터의 HTTP 응답은, 웹소켓으로 HTTP 업그레이드를 허용하지 않았습니다.
|
||||
wsWebSocketContainer.invalidSubProtocol=웹소켓 서버가, 해당 Sec-WebSocket-Protocol 헤더를 위해 여러 값들을 반환했습니다.
|
||||
wsWebSocketContainer.maxBuffer=이 구현은 버퍼의 최대 크기를 Integer.MAX_VALUE로 제한합니다.
|
||||
wsWebSocketContainer.missingAnnotation=@ClientEndpoint에 의해 annotate되지 않았기에, POJO 클래스 [{0}]을(를) 사용할 수 없습니다.
|
||||
wsWebSocketContainer.missingLocationHeader=HTTP 응답 코드 [{0}]을(를) 처리하지 못했습니다. 응답에 Location 헤더가 없습니다.
|
||||
wsWebSocketContainer.missingWWWAuthenticateHeader=HTTP 응답 코드 [{0}]을(를) 처리하지 못했습니다. 응답 헤더 WWW-Authenticate가 없습니다.
|
||||
wsWebSocketContainer.pathNoHost=URI 내에 호스트가 지정되지 않았습니다.
|
||||
wsWebSocketContainer.pathWrongScheme=스킴 [{0}]은(는) 지원되지 않습니다. 지원되는 스킴들은 ws와 wss입니다.
|
||||
wsWebSocketContainer.proxyConnectFail=설정된 프록시 [{0}](으)로 연결하지 못했습니다. HTTP 응답 코드는 [{1}]이었습니다.
|
||||
wsWebSocketContainer.redirectThreshold=순환 Location 헤더 [{0}]이(가) 탐지되었고, 최대 redirect 회수에 도달했습니다. 최대 [{2}]회 중 [{1}]회.
|
||||
wsWebSocketContainer.sessionCloseFail=ID가 [{0}]인 세션이 깨끗하게 닫히지 않았습니다.
|
||||
wsWebSocketContainer.shutdown=웹 애플리케이션이 중지되고 있습니다.
|
||||
wsWebSocketContainer.sslEngineFail=SSL/TLS 연결들을 지원하는 SSLEngine을 생성할 수 없습니다.
|
||||
wsWebSocketContainer.unsupportedAuthScheme=HTTP 응답 코드 [{0}]을(를) 처리하지 못했습니다. 지원되지 않는 인증 스킴 [{1}]이(가) 응답에서 반환되었습니다.
|
||||
20
java/org/apache/tomcat/websocket/LocalStrings_ru.properties
Normal file
20
java/org/apache/tomcat/websocket/LocalStrings_ru.properties
Normal file
@@ -0,0 +1,20 @@
|
||||
# Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
# contributor license agreements. See the NOTICE file distributed with
|
||||
# this work for additional information regarding copyright ownership.
|
||||
# The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
# (the "License"); you may not use this file except in compliance with
|
||||
# the License. You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
caseInsensitiveKeyMap.nullKey=Пустое значение ключей запрещено
|
||||
|
||||
wsHandshakeRequest.invalidUri=Строка [{0}] не может быть использована для создания корректного URI
|
||||
|
||||
wsSession.doClose=Закрытие WebSocket сессии [{1}]
|
||||
@@ -0,0 +1,78 @@
|
||||
# Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
# contributor license agreements. See the NOTICE file distributed with
|
||||
# this work for additional information regarding copyright ownership.
|
||||
# The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
# (the "License"); you may not use this file except in compliance with
|
||||
# the License. You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
asyncChannelGroup.createFail=无法为WebSocket客户端创建专用的异步通道组,这是防止复杂类加载程序环境(如JavaEE容器)中内存泄漏所必需的
|
||||
|
||||
asyncChannelWrapperSecure.check.notOk=TLS握手返回意外状态[{0}]
|
||||
asyncChannelWrapperSecure.check.unwrap=在读取期间将字节写入输出
|
||||
asyncChannelWrapperSecure.closeFail=干净的关闭通道失败
|
||||
asyncChannelWrapperSecure.eof=意外的流结尾
|
||||
asyncChannelWrapperSecure.statusUnwrap=unwrap()操作后SSLEngineResult 的意外状态
|
||||
|
||||
backgroundProcessManager.processFailed=后台进程失败
|
||||
|
||||
caseInsensitiveKeyMap.nullKey=不允许 Key 是 Null
|
||||
|
||||
perMessageDeflate.deflateFailed=无法压缩这个WebSocket压缩结构
|
||||
perMessageDeflate.duplicateParameter=重复定义的扩展参数[{0}]
|
||||
perMessageDeflate.invalidWindowSize=为[{0}]指定了[{1}]大小的无效窗口。 有效值是从8到15(包括8和15)的整数。
|
||||
|
||||
util.notToken=一个非法的扩展参数被指定为名称[{0}]和值[{0}]
|
||||
util.unknownDecoderType=无法识别该解码器类型[{0}]
|
||||
|
||||
wsFrame.alreadySuspended=消息接收已挂起。
|
||||
wsFrame.byteToLongFail=提供了太多字节([{0}]),转换成一个长的字节。
|
||||
wsFrame.closed=在一个关闭的控制帧后受到了一个新的帧.
|
||||
wsFrame.controlFragmented=接收到分段的控制帧,但控制帧可能不被分割。
|
||||
wsFrame.controlNoFin=发送一个没有设置的控制帧。控制帧不允许使用连续帧。
|
||||
wsFrame.controlPayloadTooBig=以大于125字节的最大允许值的大小[{0}]的有效载荷发送控制帧。
|
||||
wsFrame.illegalReadState=意外的读状态[{0}]
|
||||
wsFrame.invalidUtf8Close=接收到一个WebSocket关闭帧,其关闭原因包含无效的UTF-8字节序列
|
||||
wsFrame.notMasked=客户端帧未被屏蔽,但必须屏蔽所有客户端帧
|
||||
wsFrame.partialHeaderComplete=接收到WebSocket帧. fin [{0}], rsv [{1}], OpCode [{2}], payload 长度 [{3}]
|
||||
wsFrame.sessionClosed=无法处理客户端数据,因为会话已被关闭
|
||||
wsFrame.textMessageTooBig=解码的文本消息对于输出缓冲区太大,终结点不支持部分消息
|
||||
wsFrame.wrongRsv=对于具有opCode [{1}]的消息,客户端帧将保留位设置为[{0}],此端点不支持
|
||||
|
||||
wsHandshakeRequest.invalidUri=字符串 [{0}] 不能用来组成一个有效的URI
|
||||
|
||||
wsRemoteEndpoint.acquireTimeout=当前消息没有在指定的超时内完全发送
|
||||
wsRemoteEndpoint.changeType=发送分段消息时,所有片段必须是相同类型的。
|
||||
wsRemoteEndpoint.closed=由于 WebSocket session 已关闭,消息将不会被发送
|
||||
wsRemoteEndpoint.closedDuringMessage=因为 WebSocket session 被关闭,消息的剩余部分将不会被送达
|
||||
wsRemoteEndpoint.flushOnCloseFailed=会话关闭后仍然启用批处理消息。无法刷新剩余的批量消息
|
||||
wsRemoteEndpoint.noEncoder=没有为类 [{0}] 的对象指定编码器
|
||||
wsRemoteEndpoint.nullData=无效空的data 参数
|
||||
wsRemoteEndpoint.sendInterrupt=当前线程在等待阻塞发送完成时被中断
|
||||
wsRemoteEndpoint.tooMuchData=ping或pong不应该发送超过125字节
|
||||
wsRemoteEndpoint.wrongState=远程 endpoint 处于 [{0}] 状态,是被调用方法的无效状态
|
||||
|
||||
wsSession.closed=WebSocket会话[{0}]已关闭,并且在关闭的会话上不能调用任何方法(除了close())
|
||||
wsSession.created=创建WebSocket session [{0}]。
|
||||
wsSession.doClose=关闭 WebSocket session [{1}]
|
||||
wsSession.duplicateHandlerText=已配置文本消息处理器
|
||||
wsSession.instanceNew=endpoint 实例注册失败
|
||||
wsSession.unknownHandler=无法添加消息处理程序[{0}],因为它是针对无法识别的类型[{1}]
|
||||
|
||||
wsWebSocketContainer.asynchronousSocketChannelFail=无法打开与服务器的连接
|
||||
wsWebSocketContainer.failedAuthentication=无法处理http响应代码[{0}]。服务器不接受身份验证头。
|
||||
wsWebSocketContainer.invalidExtensionParameters=服务器用客户端无法支持的扩展参数响应
|
||||
wsWebSocketContainer.missingAnnotation=无法使用POJO类[{0}],因为它未添加注解@ClientEndpoint
|
||||
wsWebSocketContainer.missingLocationHeader=处理HTTP响应码 [{0}] 失败。响应头缺少Location
|
||||
wsWebSocketContainer.missingWWWAuthenticateHeader=无法处理HTTP响应代码[{0}]。 缺少WWW-Authenticate标头作为响应
|
||||
wsWebSocketContainer.pathNoHost=URI中未指定主机
|
||||
wsWebSocketContainer.sessionCloseFail=ID 为 [{0}] 的session 没有彻底关闭
|
||||
wsWebSocketContainer.shutdown=web应用程序正在停止
|
||||
wsWebSocketContainer.sslEngineFail=无法创建SSLEngine以支持SSL/TLS连接
|
||||
42
java/org/apache/tomcat/websocket/MessageHandlerResult.java
Normal file
42
java/org/apache/tomcat/websocket/MessageHandlerResult.java
Normal file
@@ -0,0 +1,42 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.apache.tomcat.websocket;
|
||||
|
||||
import javax.websocket.MessageHandler;
|
||||
|
||||
public class MessageHandlerResult {
|
||||
|
||||
private final MessageHandler handler;
|
||||
private final MessageHandlerResultType type;
|
||||
|
||||
|
||||
public MessageHandlerResult(MessageHandler handler,
|
||||
MessageHandlerResultType type) {
|
||||
this.handler = handler;
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
|
||||
public MessageHandler getHandler() {
|
||||
return handler;
|
||||
}
|
||||
|
||||
|
||||
public MessageHandlerResultType getType() {
|
||||
return type;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.apache.tomcat.websocket;
|
||||
|
||||
public enum MessageHandlerResultType {
|
||||
BINARY,
|
||||
TEXT,
|
||||
PONG
|
||||
}
|
||||
83
java/org/apache/tomcat/websocket/MessagePart.java
Normal file
83
java/org/apache/tomcat/websocket/MessagePart.java
Normal file
@@ -0,0 +1,83 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.apache.tomcat.websocket;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
import javax.websocket.SendHandler;
|
||||
|
||||
class MessagePart {
|
||||
private final boolean fin;
|
||||
private final int rsv;
|
||||
private final byte opCode;
|
||||
private final ByteBuffer payload;
|
||||
private final SendHandler intermediateHandler;
|
||||
private volatile SendHandler endHandler;
|
||||
private final long blockingWriteTimeoutExpiry;
|
||||
|
||||
public MessagePart( boolean fin, int rsv, byte opCode, ByteBuffer payload,
|
||||
SendHandler intermediateHandler, SendHandler endHandler,
|
||||
long blockingWriteTimeoutExpiry) {
|
||||
this.fin = fin;
|
||||
this.rsv = rsv;
|
||||
this.opCode = opCode;
|
||||
this.payload = payload;
|
||||
this.intermediateHandler = intermediateHandler;
|
||||
this.endHandler = endHandler;
|
||||
this.blockingWriteTimeoutExpiry = blockingWriteTimeoutExpiry;
|
||||
}
|
||||
|
||||
|
||||
public boolean isFin() {
|
||||
return fin;
|
||||
}
|
||||
|
||||
|
||||
public int getRsv() {
|
||||
return rsv;
|
||||
}
|
||||
|
||||
|
||||
public byte getOpCode() {
|
||||
return opCode;
|
||||
}
|
||||
|
||||
|
||||
public ByteBuffer getPayload() {
|
||||
return payload;
|
||||
}
|
||||
|
||||
|
||||
public SendHandler getIntermediateHandler() {
|
||||
return intermediateHandler;
|
||||
}
|
||||
|
||||
|
||||
public SendHandler getEndHandler() {
|
||||
return endHandler;
|
||||
}
|
||||
|
||||
public void setEndHandler(SendHandler endHandler) {
|
||||
this.endHandler = endHandler;
|
||||
}
|
||||
|
||||
public long getBlockingWriteTimeoutExpiry() {
|
||||
return blockingWriteTimeoutExpiry;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
495
java/org/apache/tomcat/websocket/PerMessageDeflate.java
Normal file
495
java/org/apache/tomcat/websocket/PerMessageDeflate.java
Normal file
@@ -0,0 +1,495 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.apache.tomcat.websocket;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.zip.DataFormatException;
|
||||
import java.util.zip.Deflater;
|
||||
import java.util.zip.Inflater;
|
||||
|
||||
import javax.websocket.Extension;
|
||||
import javax.websocket.Extension.Parameter;
|
||||
import javax.websocket.SendHandler;
|
||||
|
||||
import org.apache.tomcat.util.res.StringManager;
|
||||
|
||||
public class PerMessageDeflate implements Transformation {
|
||||
|
||||
private static final StringManager sm = StringManager.getManager(PerMessageDeflate.class);
|
||||
|
||||
private static final String SERVER_NO_CONTEXT_TAKEOVER = "server_no_context_takeover";
|
||||
private static final String CLIENT_NO_CONTEXT_TAKEOVER = "client_no_context_takeover";
|
||||
private static final String SERVER_MAX_WINDOW_BITS = "server_max_window_bits";
|
||||
private static final String CLIENT_MAX_WINDOW_BITS = "client_max_window_bits";
|
||||
|
||||
private static final int RSV_BITMASK = 0b100;
|
||||
private static final byte[] EOM_BYTES = new byte[] {0, 0, -1, -1};
|
||||
|
||||
public static final String NAME = "permessage-deflate";
|
||||
|
||||
private final boolean serverContextTakeover;
|
||||
private final int serverMaxWindowBits;
|
||||
private final boolean clientContextTakeover;
|
||||
private final int clientMaxWindowBits;
|
||||
private final boolean isServer;
|
||||
private final Inflater inflater = new Inflater(true);
|
||||
private final ByteBuffer readBuffer = ByteBuffer.allocate(Constants.DEFAULT_BUFFER_SIZE);
|
||||
private final Deflater deflater = new Deflater(Deflater.DEFAULT_COMPRESSION, true);
|
||||
private final byte[] EOM_BUFFER = new byte[EOM_BYTES.length + 1];
|
||||
|
||||
private volatile Transformation next;
|
||||
private volatile boolean skipDecompression = false;
|
||||
private volatile ByteBuffer writeBuffer = ByteBuffer.allocate(Constants.DEFAULT_BUFFER_SIZE);
|
||||
private volatile boolean firstCompressedFrameWritten = false;
|
||||
// Flag to track if a message is completely empty
|
||||
private volatile boolean emptyMessage = true;
|
||||
|
||||
static PerMessageDeflate negotiate(List<List<Parameter>> preferences, boolean isServer) {
|
||||
// Accept the first preference that the endpoint is able to support
|
||||
for (List<Parameter> preference : preferences) {
|
||||
boolean ok = true;
|
||||
boolean serverContextTakeover = true;
|
||||
int serverMaxWindowBits = -1;
|
||||
boolean clientContextTakeover = true;
|
||||
int clientMaxWindowBits = -1;
|
||||
|
||||
for (Parameter param : preference) {
|
||||
if (SERVER_NO_CONTEXT_TAKEOVER.equals(param.getName())) {
|
||||
if (serverContextTakeover) {
|
||||
serverContextTakeover = false;
|
||||
} else {
|
||||
// Duplicate definition
|
||||
throw new IllegalArgumentException(sm.getString(
|
||||
"perMessageDeflate.duplicateParameter",
|
||||
SERVER_NO_CONTEXT_TAKEOVER ));
|
||||
}
|
||||
} else if (CLIENT_NO_CONTEXT_TAKEOVER.equals(param.getName())) {
|
||||
if (clientContextTakeover) {
|
||||
clientContextTakeover = false;
|
||||
} else {
|
||||
// Duplicate definition
|
||||
throw new IllegalArgumentException(sm.getString(
|
||||
"perMessageDeflate.duplicateParameter",
|
||||
CLIENT_NO_CONTEXT_TAKEOVER ));
|
||||
}
|
||||
} else if (SERVER_MAX_WINDOW_BITS.equals(param.getName())) {
|
||||
if (serverMaxWindowBits == -1) {
|
||||
serverMaxWindowBits = Integer.parseInt(param.getValue());
|
||||
if (serverMaxWindowBits < 8 || serverMaxWindowBits > 15) {
|
||||
throw new IllegalArgumentException(sm.getString(
|
||||
"perMessageDeflate.invalidWindowSize",
|
||||
SERVER_MAX_WINDOW_BITS,
|
||||
Integer.valueOf(serverMaxWindowBits)));
|
||||
}
|
||||
// Java SE API (as of Java 8) does not expose the API to
|
||||
// control the Window size. It is effectively hard-coded
|
||||
// to 15
|
||||
if (isServer && serverMaxWindowBits != 15) {
|
||||
ok = false;
|
||||
break;
|
||||
// Note server window size is not an issue for the
|
||||
// client since the client will assume 15 and if the
|
||||
// server uses a smaller window everything will
|
||||
// still work
|
||||
}
|
||||
} else {
|
||||
// Duplicate definition
|
||||
throw new IllegalArgumentException(sm.getString(
|
||||
"perMessageDeflate.duplicateParameter",
|
||||
SERVER_MAX_WINDOW_BITS ));
|
||||
}
|
||||
} else if (CLIENT_MAX_WINDOW_BITS.equals(param.getName())) {
|
||||
if (clientMaxWindowBits == -1) {
|
||||
if (param.getValue() == null) {
|
||||
// Hint to server that the client supports this
|
||||
// option. Java SE API (as of Java 8) does not
|
||||
// expose the API to control the Window size. It is
|
||||
// effectively hard-coded to 15
|
||||
clientMaxWindowBits = 15;
|
||||
} else {
|
||||
clientMaxWindowBits = Integer.parseInt(param.getValue());
|
||||
if (clientMaxWindowBits < 8 || clientMaxWindowBits > 15) {
|
||||
throw new IllegalArgumentException(sm.getString(
|
||||
"perMessageDeflate.invalidWindowSize",
|
||||
CLIENT_MAX_WINDOW_BITS,
|
||||
Integer.valueOf(clientMaxWindowBits)));
|
||||
}
|
||||
}
|
||||
// Java SE API (as of Java 8) does not expose the API to
|
||||
// control the Window size. It is effectively hard-coded
|
||||
// to 15
|
||||
if (!isServer && clientMaxWindowBits != 15) {
|
||||
ok = false;
|
||||
break;
|
||||
// Note client window size is not an issue for the
|
||||
// server since the server will assume 15 and if the
|
||||
// client uses a smaller window everything will
|
||||
// still work
|
||||
}
|
||||
} else {
|
||||
// Duplicate definition
|
||||
throw new IllegalArgumentException(sm.getString(
|
||||
"perMessageDeflate.duplicateParameter",
|
||||
CLIENT_MAX_WINDOW_BITS ));
|
||||
}
|
||||
} else {
|
||||
// Unknown parameter
|
||||
throw new IllegalArgumentException(sm.getString(
|
||||
"perMessageDeflate.unknownParameter", param.getName()));
|
||||
}
|
||||
}
|
||||
if (ok) {
|
||||
return new PerMessageDeflate(serverContextTakeover, serverMaxWindowBits,
|
||||
clientContextTakeover, clientMaxWindowBits, isServer);
|
||||
}
|
||||
}
|
||||
// Failed to negotiate agreeable terms
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
private PerMessageDeflate(boolean serverContextTakeover, int serverMaxWindowBits,
|
||||
boolean clientContextTakeover, int clientMaxWindowBits, boolean isServer) {
|
||||
this.serverContextTakeover = serverContextTakeover;
|
||||
this.serverMaxWindowBits = serverMaxWindowBits;
|
||||
this.clientContextTakeover = clientContextTakeover;
|
||||
this.clientMaxWindowBits = clientMaxWindowBits;
|
||||
this.isServer = isServer;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public TransformationResult getMoreData(byte opCode, boolean fin, int rsv, ByteBuffer dest)
|
||||
throws IOException {
|
||||
// Control frames are never compressed and may appear in the middle of
|
||||
// a WebSocket method. Pass them straight through.
|
||||
if (Util.isControl(opCode)) {
|
||||
return next.getMoreData(opCode, fin, rsv, dest);
|
||||
}
|
||||
|
||||
if (!Util.isContinuation(opCode)) {
|
||||
// First frame in new message
|
||||
skipDecompression = (rsv & RSV_BITMASK) == 0;
|
||||
}
|
||||
|
||||
// Pass uncompressed frames straight through.
|
||||
if (skipDecompression) {
|
||||
return next.getMoreData(opCode, fin, rsv, dest);
|
||||
}
|
||||
|
||||
int written;
|
||||
boolean usedEomBytes = false;
|
||||
|
||||
while (dest.remaining() > 0) {
|
||||
// Space available in destination. Try and fill it.
|
||||
try {
|
||||
written = inflater.inflate(
|
||||
dest.array(), dest.arrayOffset() + dest.position(), dest.remaining());
|
||||
} catch (DataFormatException e) {
|
||||
throw new IOException(sm.getString("perMessageDeflate.deflateFailed"), e);
|
||||
} catch (NullPointerException e) {
|
||||
throw new IOException(sm.getString("perMessageDeflate.alreadyClosed"), e);
|
||||
}
|
||||
dest.position(dest.position() + written);
|
||||
|
||||
if (inflater.needsInput() && !usedEomBytes ) {
|
||||
if (dest.hasRemaining()) {
|
||||
readBuffer.clear();
|
||||
TransformationResult nextResult =
|
||||
next.getMoreData(opCode, fin, (rsv ^ RSV_BITMASK), readBuffer);
|
||||
inflater.setInput(
|
||||
readBuffer.array(), readBuffer.arrayOffset(), readBuffer.position());
|
||||
if (TransformationResult.UNDERFLOW.equals(nextResult)) {
|
||||
return nextResult;
|
||||
} else if (TransformationResult.END_OF_FRAME.equals(nextResult) &&
|
||||
readBuffer.position() == 0) {
|
||||
if (fin) {
|
||||
inflater.setInput(EOM_BYTES);
|
||||
usedEomBytes = true;
|
||||
} else {
|
||||
return TransformationResult.END_OF_FRAME;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (written == 0) {
|
||||
if (fin && (isServer && !clientContextTakeover ||
|
||||
!isServer && !serverContextTakeover)) {
|
||||
try {
|
||||
inflater.reset();
|
||||
} catch (NullPointerException e) {
|
||||
throw new IOException(sm.getString("perMessageDeflate.alreadyClosed"), e);
|
||||
}
|
||||
}
|
||||
return TransformationResult.END_OF_FRAME;
|
||||
}
|
||||
}
|
||||
|
||||
return TransformationResult.OVERFLOW;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public boolean validateRsv(int rsv, byte opCode) {
|
||||
if (Util.isControl(opCode)) {
|
||||
if ((rsv & RSV_BITMASK) != 0) {
|
||||
return false;
|
||||
} else {
|
||||
if (next == null) {
|
||||
return true;
|
||||
} else {
|
||||
return next.validateRsv(rsv, opCode);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
int rsvNext = rsv;
|
||||
if ((rsv & RSV_BITMASK) != 0) {
|
||||
rsvNext = rsv ^ RSV_BITMASK;
|
||||
}
|
||||
if (next == null) {
|
||||
return true;
|
||||
} else {
|
||||
return next.validateRsv(rsvNext, opCode);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public Extension getExtensionResponse() {
|
||||
Extension result = new WsExtension(NAME);
|
||||
|
||||
List<Extension.Parameter> params = result.getParameters();
|
||||
|
||||
if (!serverContextTakeover) {
|
||||
params.add(new WsExtensionParameter(SERVER_NO_CONTEXT_TAKEOVER, null));
|
||||
}
|
||||
if (serverMaxWindowBits != -1) {
|
||||
params.add(new WsExtensionParameter(SERVER_MAX_WINDOW_BITS,
|
||||
Integer.toString(serverMaxWindowBits)));
|
||||
}
|
||||
if (!clientContextTakeover) {
|
||||
params.add(new WsExtensionParameter(CLIENT_NO_CONTEXT_TAKEOVER, null));
|
||||
}
|
||||
if (clientMaxWindowBits != -1) {
|
||||
params.add(new WsExtensionParameter(CLIENT_MAX_WINDOW_BITS,
|
||||
Integer.toString(clientMaxWindowBits)));
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void setNext(Transformation t) {
|
||||
if (next == null) {
|
||||
this.next = t;
|
||||
} else {
|
||||
next.setNext(t);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public boolean validateRsvBits(int i) {
|
||||
if ((i & RSV_BITMASK) != 0) {
|
||||
return false;
|
||||
}
|
||||
if (next == null) {
|
||||
return true;
|
||||
} else {
|
||||
return next.validateRsvBits(i | RSV_BITMASK);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public List<MessagePart> sendMessagePart(List<MessagePart> uncompressedParts) throws IOException {
|
||||
List<MessagePart> allCompressedParts = new ArrayList<>();
|
||||
|
||||
for (MessagePart uncompressedPart : uncompressedParts) {
|
||||
byte opCode = uncompressedPart.getOpCode();
|
||||
boolean emptyPart = uncompressedPart.getPayload().limit() == 0;
|
||||
emptyMessage = emptyMessage && emptyPart;
|
||||
if (Util.isControl(opCode)) {
|
||||
// Control messages can appear in the middle of other messages
|
||||
// and must not be compressed. Pass it straight through
|
||||
allCompressedParts.add(uncompressedPart);
|
||||
} else if (emptyMessage && uncompressedPart.isFin()) {
|
||||
// Zero length messages can't be compressed so pass the
|
||||
// final (empty) part straight through.
|
||||
allCompressedParts.add(uncompressedPart);
|
||||
} else {
|
||||
List<MessagePart> compressedParts = new ArrayList<>();
|
||||
ByteBuffer uncompressedPayload = uncompressedPart.getPayload();
|
||||
SendHandler uncompressedIntermediateHandler =
|
||||
uncompressedPart.getIntermediateHandler();
|
||||
|
||||
deflater.setInput(uncompressedPayload.array(),
|
||||
uncompressedPayload.arrayOffset() + uncompressedPayload.position(),
|
||||
uncompressedPayload.remaining());
|
||||
|
||||
int flush = (uncompressedPart.isFin() ? Deflater.SYNC_FLUSH : Deflater.NO_FLUSH);
|
||||
boolean deflateRequired = true;
|
||||
|
||||
while (deflateRequired) {
|
||||
ByteBuffer compressedPayload = writeBuffer;
|
||||
|
||||
try {
|
||||
int written = deflater.deflate(compressedPayload.array(),
|
||||
compressedPayload.arrayOffset() + compressedPayload.position(),
|
||||
compressedPayload.remaining(), flush);
|
||||
compressedPayload.position(compressedPayload.position() + written);
|
||||
} catch (NullPointerException e) {
|
||||
throw new IOException(sm.getString("perMessageDeflate.alreadyClosed"), e);
|
||||
}
|
||||
|
||||
if (!uncompressedPart.isFin() && compressedPayload.hasRemaining() && deflater.needsInput()) {
|
||||
// This message part has been fully processed by the
|
||||
// deflater. Fire the send handler for this message part
|
||||
// and move on to the next message part.
|
||||
break;
|
||||
}
|
||||
|
||||
// If this point is reached, a new compressed message part
|
||||
// will be created...
|
||||
MessagePart compressedPart;
|
||||
|
||||
// .. and a new writeBuffer will be required.
|
||||
writeBuffer = ByteBuffer.allocate(Constants.DEFAULT_BUFFER_SIZE);
|
||||
|
||||
// Flip the compressed payload ready for writing
|
||||
compressedPayload.flip();
|
||||
|
||||
boolean fin = uncompressedPart.isFin();
|
||||
boolean full = compressedPayload.limit() == compressedPayload.capacity();
|
||||
boolean needsInput = deflater.needsInput();
|
||||
long blockingWriteTimeoutExpiry = uncompressedPart.getBlockingWriteTimeoutExpiry();
|
||||
|
||||
if (fin && !full && needsInput) {
|
||||
// End of compressed message. Drop EOM bytes and output.
|
||||
compressedPayload.limit(compressedPayload.limit() - EOM_BYTES.length);
|
||||
compressedPart = new MessagePart(true, getRsv(uncompressedPart),
|
||||
opCode, compressedPayload, uncompressedIntermediateHandler,
|
||||
uncompressedIntermediateHandler, blockingWriteTimeoutExpiry);
|
||||
deflateRequired = false;
|
||||
startNewMessage();
|
||||
} else if (full && !needsInput) {
|
||||
// Write buffer full and input message not fully read.
|
||||
// Output and start new compressed part.
|
||||
compressedPart = new MessagePart(false, getRsv(uncompressedPart),
|
||||
opCode, compressedPayload, uncompressedIntermediateHandler,
|
||||
uncompressedIntermediateHandler, blockingWriteTimeoutExpiry);
|
||||
} else if (!fin && full && needsInput) {
|
||||
// Write buffer full and input message not fully read.
|
||||
// Output and get more data.
|
||||
compressedPart = new MessagePart(false, getRsv(uncompressedPart),
|
||||
opCode, compressedPayload, uncompressedIntermediateHandler,
|
||||
uncompressedIntermediateHandler, blockingWriteTimeoutExpiry);
|
||||
deflateRequired = false;
|
||||
} else if (fin && full && needsInput) {
|
||||
// Write buffer full. Input fully read. Deflater may be
|
||||
// in one of four states:
|
||||
// - output complete (just happened to align with end of
|
||||
// buffer
|
||||
// - in middle of EOM bytes
|
||||
// - about to write EOM bytes
|
||||
// - more data to write
|
||||
int eomBufferWritten;
|
||||
try {
|
||||
eomBufferWritten = deflater.deflate(EOM_BUFFER, 0, EOM_BUFFER.length, Deflater.SYNC_FLUSH);
|
||||
} catch (NullPointerException e) {
|
||||
throw new IOException(sm.getString("perMessageDeflate.alreadyClosed"), e);
|
||||
}
|
||||
if (eomBufferWritten < EOM_BUFFER.length) {
|
||||
// EOM has just been completed
|
||||
compressedPayload.limit(compressedPayload.limit() - EOM_BYTES.length + eomBufferWritten);
|
||||
compressedPart = new MessagePart(true,
|
||||
getRsv(uncompressedPart), opCode, compressedPayload,
|
||||
uncompressedIntermediateHandler, uncompressedIntermediateHandler,
|
||||
blockingWriteTimeoutExpiry);
|
||||
deflateRequired = false;
|
||||
startNewMessage();
|
||||
} else {
|
||||
// More data to write
|
||||
// Copy bytes to new write buffer
|
||||
writeBuffer.put(EOM_BUFFER, 0, eomBufferWritten);
|
||||
compressedPart = new MessagePart(false,
|
||||
getRsv(uncompressedPart), opCode, compressedPayload,
|
||||
uncompressedIntermediateHandler, uncompressedIntermediateHandler,
|
||||
blockingWriteTimeoutExpiry);
|
||||
}
|
||||
} else {
|
||||
throw new IllegalStateException(sm.getString("perMessageDeflate.invalidState"));
|
||||
}
|
||||
|
||||
// Add the newly created compressed part to the set of parts
|
||||
// to pass on to the next transformation.
|
||||
compressedParts.add(compressedPart);
|
||||
}
|
||||
|
||||
SendHandler uncompressedEndHandler = uncompressedPart.getEndHandler();
|
||||
int size = compressedParts.size();
|
||||
if (size > 0) {
|
||||
compressedParts.get(size - 1).setEndHandler(uncompressedEndHandler);
|
||||
}
|
||||
|
||||
allCompressedParts.addAll(compressedParts);
|
||||
}
|
||||
}
|
||||
|
||||
if (next == null) {
|
||||
return allCompressedParts;
|
||||
} else {
|
||||
return next.sendMessagePart(allCompressedParts);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private void startNewMessage() throws IOException {
|
||||
firstCompressedFrameWritten = false;
|
||||
emptyMessage = true;
|
||||
if (isServer && !serverContextTakeover || !isServer && !clientContextTakeover) {
|
||||
try {
|
||||
deflater.reset();
|
||||
} catch (NullPointerException e) {
|
||||
throw new IOException(sm.getString("perMessageDeflate.alreadyClosed"), e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private int getRsv(MessagePart uncompressedMessagePart) {
|
||||
int result = uncompressedMessagePart.getRsv();
|
||||
if (!firstCompressedFrameWritten) {
|
||||
result += RSV_BITMASK;
|
||||
firstCompressedFrameWritten = true;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
// There will always be a next transformation
|
||||
next.close();
|
||||
inflater.end();
|
||||
deflater.end();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.apache.tomcat.websocket;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
public class ReadBufferOverflowException extends IOException {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
private final int minBufferSize;
|
||||
|
||||
public ReadBufferOverflowException(int minBufferSize) {
|
||||
this.minBufferSize = minBufferSize;
|
||||
}
|
||||
|
||||
public int getMinBufferSize() {
|
||||
return minBufferSize;
|
||||
}
|
||||
}
|
||||
114
java/org/apache/tomcat/websocket/Transformation.java
Normal file
114
java/org/apache/tomcat/websocket/Transformation.java
Normal file
@@ -0,0 +1,114 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.apache.tomcat.websocket;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.List;
|
||||
|
||||
import javax.websocket.Extension;
|
||||
|
||||
/**
|
||||
* The internal representation of the transformation that a WebSocket extension
|
||||
* performs on a message.
|
||||
*/
|
||||
public interface Transformation {
|
||||
|
||||
/**
|
||||
* Sets the next transformation in the pipeline.
|
||||
* @param t The next transformation
|
||||
*/
|
||||
void setNext(Transformation t);
|
||||
|
||||
/**
|
||||
* Validate that the RSV bit(s) required by this transformation are not
|
||||
* being used by another extension. The implementation is expected to set
|
||||
* any bits it requires before passing the set of in-use bits to the next
|
||||
* transformation.
|
||||
*
|
||||
* @param i The RSV bits marked as in use so far as an int in the
|
||||
* range zero to seven with RSV1 as the MSB and RSV3 as the
|
||||
* LSB
|
||||
*
|
||||
* @return <code>true</code> if the combination of RSV bits used by the
|
||||
* transformations in the pipeline do not conflict otherwise
|
||||
* <code>false</code>
|
||||
*/
|
||||
boolean validateRsvBits(int i);
|
||||
|
||||
/**
|
||||
* Obtain the extension that describes the information to be returned to the
|
||||
* client.
|
||||
*
|
||||
* @return The extension information that describes the parameters that have
|
||||
* been agreed for this transformation
|
||||
*/
|
||||
Extension getExtensionResponse();
|
||||
|
||||
/**
|
||||
* Obtain more input data.
|
||||
*
|
||||
* @param opCode The opcode for the frame currently being processed
|
||||
* @param fin Is this the final frame in this WebSocket message?
|
||||
* @param rsv The reserved bits for the frame currently being
|
||||
* processed
|
||||
* @param dest The buffer in which the data is to be written
|
||||
*
|
||||
* @return The result of trying to read more data from the transform
|
||||
*
|
||||
* @throws IOException If an I/O error occurs while reading data from the
|
||||
* transform
|
||||
*/
|
||||
TransformationResult getMoreData(byte opCode, boolean fin, int rsv, ByteBuffer dest) throws IOException;
|
||||
|
||||
/**
|
||||
* Validates the RSV and opcode combination (assumed to have been extracted
|
||||
* from a WebSocket Frame) for this extension. The implementation is
|
||||
* expected to unset any RSV bits it has validated before passing the
|
||||
* remaining RSV bits to the next transformation in the pipeline.
|
||||
*
|
||||
* @param rsv The RSV bits received as an int in the range zero to
|
||||
* seven with RSV1 as the MSB and RSV3 as the LSB
|
||||
* @param opCode The opCode received
|
||||
*
|
||||
* @return <code>true</code> if the RSV is valid otherwise
|
||||
* <code>false</code>
|
||||
*/
|
||||
boolean validateRsv(int rsv, byte opCode);
|
||||
|
||||
/**
|
||||
* Takes the provided list of messages, transforms them, passes the
|
||||
* transformed list on to the next transformation (if any) and then returns
|
||||
* the resulting list of message parts after all of the transformations have
|
||||
* been applied.
|
||||
*
|
||||
* @param messageParts The list of messages to be transformed
|
||||
*
|
||||
* @return The list of messages after this any any subsequent
|
||||
* transformations have been applied. The size of the returned list
|
||||
* may be bigger or smaller than the size of the input list
|
||||
*
|
||||
* @throws IOException If an error occurs during the transformation of the
|
||||
* message parts
|
||||
*/
|
||||
List<MessagePart> sendMessagePart(List<MessagePart> messageParts) throws IOException;
|
||||
|
||||
/**
|
||||
* Clean-up any resources that were used by the transformation.
|
||||
*/
|
||||
void close();
|
||||
}
|
||||
51
java/org/apache/tomcat/websocket/TransformationFactory.java
Normal file
51
java/org/apache/tomcat/websocket/TransformationFactory.java
Normal file
@@ -0,0 +1,51 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.apache.tomcat.websocket;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import javax.websocket.Extension;
|
||||
|
||||
import org.apache.tomcat.util.res.StringManager;
|
||||
|
||||
public class TransformationFactory {
|
||||
|
||||
private static final StringManager sm = StringManager.getManager(TransformationFactory.class);
|
||||
|
||||
private static final TransformationFactory factory = new TransformationFactory();
|
||||
|
||||
private TransformationFactory() {
|
||||
// Hide default constructor
|
||||
}
|
||||
|
||||
public static TransformationFactory getInstance() {
|
||||
return factory;
|
||||
}
|
||||
|
||||
public Transformation create(String name, List<List<Extension.Parameter>> preferences,
|
||||
boolean isServer) {
|
||||
if (PerMessageDeflate.NAME.equals(name)) {
|
||||
return PerMessageDeflate.negotiate(preferences, isServer);
|
||||
}
|
||||
if (Constants.ALLOW_UNSUPPORTED_EXTENSIONS) {
|
||||
return null;
|
||||
} else {
|
||||
throw new IllegalArgumentException(
|
||||
sm.getString("transformerFactory.unsupportedExtension", name));
|
||||
}
|
||||
}
|
||||
}
|
||||
37
java/org/apache/tomcat/websocket/TransformationResult.java
Normal file
37
java/org/apache/tomcat/websocket/TransformationResult.java
Normal file
@@ -0,0 +1,37 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.apache.tomcat.websocket;
|
||||
|
||||
public enum TransformationResult {
|
||||
/**
|
||||
* The end of the available data was reached before the WebSocket frame was
|
||||
* completely read.
|
||||
*/
|
||||
UNDERFLOW,
|
||||
|
||||
/**
|
||||
* The provided destination buffer was filled before all of the available
|
||||
* data from the WebSocket frame could be processed.
|
||||
*/
|
||||
OVERFLOW,
|
||||
|
||||
/**
|
||||
* The end of the WebSocket frame was reached and all the data from that
|
||||
* frame processed into the provided destination buffer.
|
||||
*/
|
||||
END_OF_FRAME
|
||||
}
|
||||
666
java/org/apache/tomcat/websocket/Util.java
Normal file
666
java/org/apache/tomcat/websocket/Util.java
Normal file
@@ -0,0 +1,666 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.apache.tomcat.websocket;
|
||||
|
||||
import java.io.InputStream;
|
||||
import java.io.Reader;
|
||||
import java.lang.reflect.GenericArrayType;
|
||||
import java.lang.reflect.Method;
|
||||
import java.lang.reflect.ParameterizedType;
|
||||
import java.lang.reflect.Type;
|
||||
import java.lang.reflect.TypeVariable;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.SecureRandom;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Queue;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ConcurrentLinkedQueue;
|
||||
|
||||
import javax.websocket.CloseReason.CloseCode;
|
||||
import javax.websocket.CloseReason.CloseCodes;
|
||||
import javax.websocket.Decoder;
|
||||
import javax.websocket.Decoder.Binary;
|
||||
import javax.websocket.Decoder.BinaryStream;
|
||||
import javax.websocket.Decoder.Text;
|
||||
import javax.websocket.Decoder.TextStream;
|
||||
import javax.websocket.DeploymentException;
|
||||
import javax.websocket.Encoder;
|
||||
import javax.websocket.EndpointConfig;
|
||||
import javax.websocket.Extension;
|
||||
import javax.websocket.MessageHandler;
|
||||
import javax.websocket.PongMessage;
|
||||
import javax.websocket.Session;
|
||||
|
||||
import org.apache.tomcat.util.res.StringManager;
|
||||
import org.apache.tomcat.websocket.pojo.PojoMessageHandlerPartialBinary;
|
||||
import org.apache.tomcat.websocket.pojo.PojoMessageHandlerWholeBinary;
|
||||
import org.apache.tomcat.websocket.pojo.PojoMessageHandlerWholeText;
|
||||
|
||||
/**
|
||||
* Utility class for internal use only within the
|
||||
* {@link org.apache.tomcat.websocket} package.
|
||||
*/
|
||||
public class Util {
|
||||
|
||||
private static final StringManager sm = StringManager.getManager(Util.class);
|
||||
private static final Queue<SecureRandom> randoms =
|
||||
new ConcurrentLinkedQueue<>();
|
||||
|
||||
private Util() {
|
||||
// Hide default constructor
|
||||
}
|
||||
|
||||
|
||||
static boolean isControl(byte opCode) {
|
||||
return (opCode & 0x08) != 0;
|
||||
}
|
||||
|
||||
|
||||
static boolean isText(byte opCode) {
|
||||
return opCode == Constants.OPCODE_TEXT;
|
||||
}
|
||||
|
||||
|
||||
static boolean isContinuation(byte opCode) {
|
||||
return opCode == Constants.OPCODE_CONTINUATION;
|
||||
}
|
||||
|
||||
|
||||
static CloseCode getCloseCode(int code) {
|
||||
if (code > 2999 && code < 5000) {
|
||||
return CloseCodes.getCloseCode(code);
|
||||
}
|
||||
switch (code) {
|
||||
case 1000:
|
||||
return CloseCodes.NORMAL_CLOSURE;
|
||||
case 1001:
|
||||
return CloseCodes.GOING_AWAY;
|
||||
case 1002:
|
||||
return CloseCodes.PROTOCOL_ERROR;
|
||||
case 1003:
|
||||
return CloseCodes.CANNOT_ACCEPT;
|
||||
case 1004:
|
||||
// Should not be used in a close frame
|
||||
// return CloseCodes.RESERVED;
|
||||
return CloseCodes.PROTOCOL_ERROR;
|
||||
case 1005:
|
||||
// Should not be used in a close frame
|
||||
// return CloseCodes.NO_STATUS_CODE;
|
||||
return CloseCodes.PROTOCOL_ERROR;
|
||||
case 1006:
|
||||
// Should not be used in a close frame
|
||||
// return CloseCodes.CLOSED_ABNORMALLY;
|
||||
return CloseCodes.PROTOCOL_ERROR;
|
||||
case 1007:
|
||||
return CloseCodes.NOT_CONSISTENT;
|
||||
case 1008:
|
||||
return CloseCodes.VIOLATED_POLICY;
|
||||
case 1009:
|
||||
return CloseCodes.TOO_BIG;
|
||||
case 1010:
|
||||
return CloseCodes.NO_EXTENSION;
|
||||
case 1011:
|
||||
return CloseCodes.UNEXPECTED_CONDITION;
|
||||
case 1012:
|
||||
// Not in RFC6455
|
||||
// return CloseCodes.SERVICE_RESTART;
|
||||
return CloseCodes.PROTOCOL_ERROR;
|
||||
case 1013:
|
||||
// Not in RFC6455
|
||||
// return CloseCodes.TRY_AGAIN_LATER;
|
||||
return CloseCodes.PROTOCOL_ERROR;
|
||||
case 1015:
|
||||
// Should not be used in a close frame
|
||||
// return CloseCodes.TLS_HANDSHAKE_FAILURE;
|
||||
return CloseCodes.PROTOCOL_ERROR;
|
||||
default:
|
||||
return CloseCodes.PROTOCOL_ERROR;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static byte[] generateMask() {
|
||||
// SecureRandom is not thread-safe so need to make sure only one thread
|
||||
// uses it at a time. In theory, the pool could grow to the same size
|
||||
// as the number of request processing threads. In reality it will be
|
||||
// a lot smaller.
|
||||
|
||||
// Get a SecureRandom from the pool
|
||||
SecureRandom sr = randoms.poll();
|
||||
|
||||
// If one isn't available, generate a new one
|
||||
if (sr == null) {
|
||||
try {
|
||||
sr = SecureRandom.getInstance("SHA1PRNG");
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
// Fall back to platform default
|
||||
sr = new SecureRandom();
|
||||
}
|
||||
}
|
||||
|
||||
// Generate the mask
|
||||
byte[] result = new byte[4];
|
||||
sr.nextBytes(result);
|
||||
|
||||
// Put the SecureRandom back in the poll
|
||||
randoms.add(sr);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
static Class<?> getMessageType(MessageHandler listener) {
|
||||
return Util.getGenericType(MessageHandler.class,
|
||||
listener.getClass()).getClazz();
|
||||
}
|
||||
|
||||
|
||||
private static Class<?> getDecoderType(Class<? extends Decoder> decoder) {
|
||||
return Util.getGenericType(Decoder.class, decoder).getClazz();
|
||||
}
|
||||
|
||||
|
||||
static Class<?> getEncoderType(Class<? extends Encoder> encoder) {
|
||||
return Util.getGenericType(Encoder.class, encoder).getClazz();
|
||||
}
|
||||
|
||||
|
||||
private static <T> TypeResult getGenericType(Class<T> type,
|
||||
Class<? extends T> clazz) {
|
||||
|
||||
// Look to see if this class implements the interface of interest
|
||||
|
||||
// Get all the interfaces
|
||||
Type[] interfaces = clazz.getGenericInterfaces();
|
||||
for (Type iface : interfaces) {
|
||||
// Only need to check interfaces that use generics
|
||||
if (iface instanceof ParameterizedType) {
|
||||
ParameterizedType pi = (ParameterizedType) iface;
|
||||
// Look for the interface of interest
|
||||
if (pi.getRawType() instanceof Class) {
|
||||
if (type.isAssignableFrom((Class<?>) pi.getRawType())) {
|
||||
return getTypeParameter(
|
||||
clazz, pi.getActualTypeArguments()[0]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Interface not found on this class. Look at the superclass.
|
||||
@SuppressWarnings("unchecked")
|
||||
Class<? extends T> superClazz =
|
||||
(Class<? extends T>) clazz.getSuperclass();
|
||||
if (superClazz == null) {
|
||||
// Finished looking up the class hierarchy without finding anything
|
||||
return null;
|
||||
}
|
||||
|
||||
TypeResult superClassTypeResult = getGenericType(type, superClazz);
|
||||
int dimension = superClassTypeResult.getDimension();
|
||||
if (superClassTypeResult.getIndex() == -1 && dimension == 0) {
|
||||
// Superclass implements interface and defines explicit type for
|
||||
// the interface of interest
|
||||
return superClassTypeResult;
|
||||
}
|
||||
|
||||
if (superClassTypeResult.getIndex() > -1) {
|
||||
// Superclass implements interface and defines unknown type for
|
||||
// the interface of interest
|
||||
// Map that unknown type to the generic types defined in this class
|
||||
ParameterizedType superClassType =
|
||||
(ParameterizedType) clazz.getGenericSuperclass();
|
||||
TypeResult result = getTypeParameter(clazz,
|
||||
superClassType.getActualTypeArguments()[
|
||||
superClassTypeResult.getIndex()]);
|
||||
result.incrementDimension(superClassTypeResult.getDimension());
|
||||
if (result.getClazz() != null && result.getDimension() > 0) {
|
||||
superClassTypeResult = result;
|
||||
} else {
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
if (superClassTypeResult.getDimension() > 0) {
|
||||
StringBuilder className = new StringBuilder();
|
||||
for (int i = 0; i < dimension; i++) {
|
||||
className.append('[');
|
||||
}
|
||||
className.append('L');
|
||||
className.append(superClassTypeResult.getClazz().getCanonicalName());
|
||||
className.append(';');
|
||||
|
||||
Class<?> arrayClazz;
|
||||
try {
|
||||
arrayClazz = Class.forName(className.toString());
|
||||
} catch (ClassNotFoundException e) {
|
||||
throw new IllegalArgumentException(e);
|
||||
}
|
||||
|
||||
return new TypeResult(arrayClazz, -1, 0);
|
||||
}
|
||||
|
||||
// Error will be logged further up the call stack
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* For a generic parameter, return either the Class used or if the type
|
||||
* is unknown, the index for the type in definition of the class
|
||||
*/
|
||||
private static TypeResult getTypeParameter(Class<?> clazz, Type argType) {
|
||||
if (argType instanceof Class<?>) {
|
||||
return new TypeResult((Class<?>) argType, -1, 0);
|
||||
} else if (argType instanceof ParameterizedType) {
|
||||
return new TypeResult((Class<?>)((ParameterizedType) argType).getRawType(), -1, 0);
|
||||
} else if (argType instanceof GenericArrayType) {
|
||||
Type arrayElementType = ((GenericArrayType) argType).getGenericComponentType();
|
||||
TypeResult result = getTypeParameter(clazz, arrayElementType);
|
||||
result.incrementDimension(1);
|
||||
return result;
|
||||
} else {
|
||||
TypeVariable<?>[] tvs = clazz.getTypeParameters();
|
||||
for (int i = 0; i < tvs.length; i++) {
|
||||
if (tvs[i].equals(argType)) {
|
||||
return new TypeResult(null, i, 0);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public static boolean isPrimitive(Class<?> clazz) {
|
||||
if (clazz.isPrimitive()) {
|
||||
return true;
|
||||
} else if(clazz.equals(Boolean.class) ||
|
||||
clazz.equals(Byte.class) ||
|
||||
clazz.equals(Character.class) ||
|
||||
clazz.equals(Double.class) ||
|
||||
clazz.equals(Float.class) ||
|
||||
clazz.equals(Integer.class) ||
|
||||
clazz.equals(Long.class) ||
|
||||
clazz.equals(Short.class)) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
public static Object coerceToType(Class<?> type, String value) {
|
||||
if (type.equals(String.class)) {
|
||||
return value;
|
||||
} else if (type.equals(boolean.class) || type.equals(Boolean.class)) {
|
||||
return Boolean.valueOf(value);
|
||||
} else if (type.equals(byte.class) || type.equals(Byte.class)) {
|
||||
return Byte.valueOf(value);
|
||||
} else if (type.equals(char.class) || type.equals(Character.class)) {
|
||||
return Character.valueOf(value.charAt(0));
|
||||
} else if (type.equals(double.class) || type.equals(Double.class)) {
|
||||
return Double.valueOf(value);
|
||||
} else if (type.equals(float.class) || type.equals(Float.class)) {
|
||||
return Float.valueOf(value);
|
||||
} else if (type.equals(int.class) || type.equals(Integer.class)) {
|
||||
return Integer.valueOf(value);
|
||||
} else if (type.equals(long.class) || type.equals(Long.class)) {
|
||||
return Long.valueOf(value);
|
||||
} else if (type.equals(short.class) || type.equals(Short.class)) {
|
||||
return Short.valueOf(value);
|
||||
} else {
|
||||
throw new IllegalArgumentException(sm.getString(
|
||||
"util.invalidType", value, type.getName()));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public static List<DecoderEntry> getDecoders(
|
||||
List<Class<? extends Decoder>> decoderClazzes)
|
||||
throws DeploymentException{
|
||||
|
||||
List<DecoderEntry> result = new ArrayList<>();
|
||||
if (decoderClazzes != null) {
|
||||
for (Class<? extends Decoder> decoderClazz : decoderClazzes) {
|
||||
// Need to instantiate decoder to ensure it is valid and that
|
||||
// deployment can be failed if it is not
|
||||
@SuppressWarnings("unused")
|
||||
Decoder instance;
|
||||
try {
|
||||
instance = decoderClazz.getConstructor().newInstance();
|
||||
} catch (ReflectiveOperationException e) {
|
||||
throw new DeploymentException(
|
||||
sm.getString("pojoMethodMapping.invalidDecoder",
|
||||
decoderClazz.getName()), e);
|
||||
}
|
||||
DecoderEntry entry = new DecoderEntry(
|
||||
Util.getDecoderType(decoderClazz), decoderClazz);
|
||||
result.add(entry);
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
static Set<MessageHandlerResult> getMessageHandlers(Class<?> target,
|
||||
MessageHandler listener, EndpointConfig endpointConfig,
|
||||
Session session) {
|
||||
|
||||
// Will never be more than 2 types
|
||||
Set<MessageHandlerResult> results = new HashSet<>(2);
|
||||
|
||||
// Simple cases - handlers already accepts one of the types expected by
|
||||
// the frame handling code
|
||||
if (String.class.isAssignableFrom(target)) {
|
||||
MessageHandlerResult result =
|
||||
new MessageHandlerResult(listener,
|
||||
MessageHandlerResultType.TEXT);
|
||||
results.add(result);
|
||||
} else if (ByteBuffer.class.isAssignableFrom(target)) {
|
||||
MessageHandlerResult result =
|
||||
new MessageHandlerResult(listener,
|
||||
MessageHandlerResultType.BINARY);
|
||||
results.add(result);
|
||||
} else if (PongMessage.class.isAssignableFrom(target)) {
|
||||
MessageHandlerResult result =
|
||||
new MessageHandlerResult(listener,
|
||||
MessageHandlerResultType.PONG);
|
||||
results.add(result);
|
||||
// Handler needs wrapping and optional decoder to convert it to one of
|
||||
// the types expected by the frame handling code
|
||||
} else if (byte[].class.isAssignableFrom(target)) {
|
||||
boolean whole = MessageHandler.Whole.class.isAssignableFrom(listener.getClass());
|
||||
MessageHandlerResult result = new MessageHandlerResult(
|
||||
whole ? new PojoMessageHandlerWholeBinary(listener,
|
||||
getOnMessageMethod(listener), session,
|
||||
endpointConfig, matchDecoders(target, endpointConfig, true),
|
||||
new Object[1], 0, true, -1, false, -1) :
|
||||
new PojoMessageHandlerPartialBinary(listener,
|
||||
getOnMessagePartialMethod(listener), session,
|
||||
new Object[2], 0, true, 1, -1, -1),
|
||||
MessageHandlerResultType.BINARY);
|
||||
results.add(result);
|
||||
} else if (InputStream.class.isAssignableFrom(target)) {
|
||||
MessageHandlerResult result = new MessageHandlerResult(
|
||||
new PojoMessageHandlerWholeBinary(listener,
|
||||
getOnMessageMethod(listener), session,
|
||||
endpointConfig, matchDecoders(target, endpointConfig, true),
|
||||
new Object[1], 0, true, -1, true, -1),
|
||||
MessageHandlerResultType.BINARY);
|
||||
results.add(result);
|
||||
} else if (Reader.class.isAssignableFrom(target)) {
|
||||
MessageHandlerResult result = new MessageHandlerResult(
|
||||
new PojoMessageHandlerWholeText(listener,
|
||||
getOnMessageMethod(listener), session,
|
||||
endpointConfig, matchDecoders(target, endpointConfig, false),
|
||||
new Object[1], 0, true, -1, -1),
|
||||
MessageHandlerResultType.TEXT);
|
||||
results.add(result);
|
||||
} else {
|
||||
// Handler needs wrapping and requires decoder to convert it to one
|
||||
// of the types expected by the frame handling code
|
||||
DecoderMatch decoderMatch = matchDecoders(target, endpointConfig);
|
||||
Method m = getOnMessageMethod(listener);
|
||||
if (decoderMatch.getBinaryDecoders().size() > 0) {
|
||||
MessageHandlerResult result = new MessageHandlerResult(
|
||||
new PojoMessageHandlerWholeBinary(listener, m, session,
|
||||
endpointConfig,
|
||||
decoderMatch.getBinaryDecoders(), new Object[1],
|
||||
0, false, -1, false, -1),
|
||||
MessageHandlerResultType.BINARY);
|
||||
results.add(result);
|
||||
}
|
||||
if (decoderMatch.getTextDecoders().size() > 0) {
|
||||
MessageHandlerResult result = new MessageHandlerResult(
|
||||
new PojoMessageHandlerWholeText(listener, m, session,
|
||||
endpointConfig,
|
||||
decoderMatch.getTextDecoders(), new Object[1],
|
||||
0, false, -1, -1),
|
||||
MessageHandlerResultType.TEXT);
|
||||
results.add(result);
|
||||
}
|
||||
}
|
||||
|
||||
if (results.size() == 0) {
|
||||
throw new IllegalArgumentException(
|
||||
sm.getString("wsSession.unknownHandler", listener, target));
|
||||
}
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
private static List<Class<? extends Decoder>> matchDecoders(Class<?> target,
|
||||
EndpointConfig endpointConfig, boolean binary) {
|
||||
DecoderMatch decoderMatch = matchDecoders(target, endpointConfig);
|
||||
if (binary) {
|
||||
if (decoderMatch.getBinaryDecoders().size() > 0) {
|
||||
return decoderMatch.getBinaryDecoders();
|
||||
}
|
||||
} else if (decoderMatch.getTextDecoders().size() > 0) {
|
||||
return decoderMatch.getTextDecoders();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private static DecoderMatch matchDecoders(Class<?> target,
|
||||
EndpointConfig endpointConfig) {
|
||||
DecoderMatch decoderMatch;
|
||||
try {
|
||||
List<Class<? extends Decoder>> decoders =
|
||||
endpointConfig.getDecoders();
|
||||
List<DecoderEntry> decoderEntries = getDecoders(decoders);
|
||||
decoderMatch = new DecoderMatch(target, decoderEntries);
|
||||
} catch (DeploymentException e) {
|
||||
throw new IllegalArgumentException(e);
|
||||
}
|
||||
return decoderMatch;
|
||||
}
|
||||
|
||||
public static void parseExtensionHeader(List<Extension> extensions,
|
||||
String header) {
|
||||
// The relevant ABNF for the Sec-WebSocket-Extensions is as follows:
|
||||
// extension-list = 1#extension
|
||||
// extension = extension-token *( ";" extension-param )
|
||||
// extension-token = registered-token
|
||||
// registered-token = token
|
||||
// extension-param = token [ "=" (token | quoted-string) ]
|
||||
// ; When using the quoted-string syntax variant, the value
|
||||
// ; after quoted-string unescaping MUST conform to the
|
||||
// ; 'token' ABNF.
|
||||
//
|
||||
// The limiting of parameter values to tokens or "quoted tokens" makes
|
||||
// the parsing of the header significantly simpler and allows a number
|
||||
// of short-cuts to be taken.
|
||||
|
||||
// Step one, split the header into individual extensions using ',' as a
|
||||
// separator
|
||||
String unparsedExtensions[] = header.split(",");
|
||||
for (String unparsedExtension : unparsedExtensions) {
|
||||
// Step two, split the extension into the registered name and
|
||||
// parameter/value pairs using ';' as a separator
|
||||
String unparsedParameters[] = unparsedExtension.split(";");
|
||||
WsExtension extension = new WsExtension(unparsedParameters[0].trim());
|
||||
|
||||
for (int i = 1; i < unparsedParameters.length; i++) {
|
||||
int equalsPos = unparsedParameters[i].indexOf('=');
|
||||
String name;
|
||||
String value;
|
||||
if (equalsPos == -1) {
|
||||
name = unparsedParameters[i].trim();
|
||||
value = null;
|
||||
} else {
|
||||
name = unparsedParameters[i].substring(0, equalsPos).trim();
|
||||
value = unparsedParameters[i].substring(equalsPos + 1).trim();
|
||||
int len = value.length();
|
||||
if (len > 1) {
|
||||
if (value.charAt(0) == '\"' && value.charAt(len - 1) == '\"') {
|
||||
value = value.substring(1, value.length() - 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
// Make sure value doesn't contain any of the delimiters since
|
||||
// that would indicate something went wrong
|
||||
if (containsDelims(name) || containsDelims(value)) {
|
||||
throw new IllegalArgumentException(sm.getString(
|
||||
"util.notToken", name, value));
|
||||
}
|
||||
if (value != null &&
|
||||
(value.indexOf(',') > -1 || value.indexOf(';') > -1 ||
|
||||
value.indexOf('\"') > -1 || value.indexOf('=') > -1)) {
|
||||
throw new IllegalArgumentException(sm.getString("", value));
|
||||
}
|
||||
extension.addParameter(new WsExtensionParameter(name, value));
|
||||
}
|
||||
extensions.add(extension);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private static boolean containsDelims(String input) {
|
||||
if (input == null || input.length() == 0) {
|
||||
return false;
|
||||
}
|
||||
for (char c : input.toCharArray()) {
|
||||
switch (c) {
|
||||
case ',':
|
||||
case ';':
|
||||
case '\"':
|
||||
case '=':
|
||||
return true;
|
||||
default:
|
||||
// NO_OP
|
||||
}
|
||||
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private static Method getOnMessageMethod(MessageHandler listener) {
|
||||
try {
|
||||
return listener.getClass().getMethod("onMessage", Object.class);
|
||||
} catch (NoSuchMethodException | SecurityException e) {
|
||||
throw new IllegalArgumentException(
|
||||
sm.getString("util.invalidMessageHandler"), e);
|
||||
}
|
||||
}
|
||||
|
||||
private static Method getOnMessagePartialMethod(MessageHandler listener) {
|
||||
try {
|
||||
return listener.getClass().getMethod("onMessage", Object.class, Boolean.TYPE);
|
||||
} catch (NoSuchMethodException | SecurityException e) {
|
||||
throw new IllegalArgumentException(
|
||||
sm.getString("util.invalidMessageHandler"), e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public static class DecoderMatch {
|
||||
|
||||
private final List<Class<? extends Decoder>> textDecoders =
|
||||
new ArrayList<>();
|
||||
private final List<Class<? extends Decoder>> binaryDecoders =
|
||||
new ArrayList<>();
|
||||
private final Class<?> target;
|
||||
|
||||
public DecoderMatch(Class<?> target, List<DecoderEntry> decoderEntries) {
|
||||
this.target = target;
|
||||
for (DecoderEntry decoderEntry : decoderEntries) {
|
||||
if (decoderEntry.getClazz().isAssignableFrom(target)) {
|
||||
if (Binary.class.isAssignableFrom(
|
||||
decoderEntry.getDecoderClazz())) {
|
||||
binaryDecoders.add(decoderEntry.getDecoderClazz());
|
||||
// willDecode() method means this decoder may or may not
|
||||
// decode a message so need to carry on checking for
|
||||
// other matches
|
||||
} else if (BinaryStream.class.isAssignableFrom(
|
||||
decoderEntry.getDecoderClazz())) {
|
||||
binaryDecoders.add(decoderEntry.getDecoderClazz());
|
||||
// Stream decoders have to process the message so no
|
||||
// more decoders can be matched
|
||||
break;
|
||||
} else if (Text.class.isAssignableFrom(
|
||||
decoderEntry.getDecoderClazz())) {
|
||||
textDecoders.add(decoderEntry.getDecoderClazz());
|
||||
// willDecode() method means this decoder may or may not
|
||||
// decode a message so need to carry on checking for
|
||||
// other matches
|
||||
} else if (TextStream.class.isAssignableFrom(
|
||||
decoderEntry.getDecoderClazz())) {
|
||||
textDecoders.add(decoderEntry.getDecoderClazz());
|
||||
// Stream decoders have to process the message so no
|
||||
// more decoders can be matched
|
||||
break;
|
||||
} else {
|
||||
throw new IllegalArgumentException(
|
||||
sm.getString("util.unknownDecoderType"));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public List<Class<? extends Decoder>> getTextDecoders() {
|
||||
return textDecoders;
|
||||
}
|
||||
|
||||
|
||||
public List<Class<? extends Decoder>> getBinaryDecoders() {
|
||||
return binaryDecoders;
|
||||
}
|
||||
|
||||
|
||||
public Class<?> getTarget() {
|
||||
return target;
|
||||
}
|
||||
|
||||
|
||||
public boolean hasMatches() {
|
||||
return (textDecoders.size() > 0) || (binaryDecoders.size() > 0);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private static class TypeResult {
|
||||
private final Class<?> clazz;
|
||||
private final int index;
|
||||
private int dimension;
|
||||
|
||||
public TypeResult(Class<?> clazz, int index, int dimension) {
|
||||
this.clazz= clazz;
|
||||
this.index = index;
|
||||
this.dimension = dimension;
|
||||
}
|
||||
|
||||
public Class<?> getClazz() {
|
||||
return clazz;
|
||||
}
|
||||
|
||||
public int getIndex() {
|
||||
return index;
|
||||
}
|
||||
|
||||
public int getDimension() {
|
||||
return dimension;
|
||||
}
|
||||
|
||||
public void incrementDimension(int inc) {
|
||||
dimension += inc;
|
||||
}
|
||||
}
|
||||
}
|
||||
25
java/org/apache/tomcat/websocket/WrappedMessageHandler.java
Normal file
25
java/org/apache/tomcat/websocket/WrappedMessageHandler.java
Normal file
@@ -0,0 +1,25 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.apache.tomcat.websocket;
|
||||
|
||||
import javax.websocket.MessageHandler;
|
||||
|
||||
public interface WrappedMessageHandler {
|
||||
long getMaxMessageSize();
|
||||
|
||||
MessageHandler getWrappedHandler();
|
||||
}
|
||||
28
java/org/apache/tomcat/websocket/WsContainerProvider.java
Normal file
28
java/org/apache/tomcat/websocket/WsContainerProvider.java
Normal file
@@ -0,0 +1,28 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.apache.tomcat.websocket;
|
||||
|
||||
import javax.websocket.ContainerProvider;
|
||||
import javax.websocket.WebSocketContainer;
|
||||
|
||||
public class WsContainerProvider extends ContainerProvider {
|
||||
|
||||
@Override
|
||||
protected WebSocketContainer getContainer() {
|
||||
return new WsWebSocketContainer();
|
||||
}
|
||||
}
|
||||
46
java/org/apache/tomcat/websocket/WsExtension.java
Normal file
46
java/org/apache/tomcat/websocket/WsExtension.java
Normal file
@@ -0,0 +1,46 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.apache.tomcat.websocket;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import javax.websocket.Extension;
|
||||
|
||||
public class WsExtension implements Extension {
|
||||
|
||||
private final String name;
|
||||
private final List<Parameter> parameters = new ArrayList<>();
|
||||
|
||||
WsExtension(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
void addParameter(Parameter parameter) {
|
||||
parameters.add(parameter);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Parameter> getParameters() {
|
||||
return parameters;
|
||||
}
|
||||
}
|
||||
40
java/org/apache/tomcat/websocket/WsExtensionParameter.java
Normal file
40
java/org/apache/tomcat/websocket/WsExtensionParameter.java
Normal file
@@ -0,0 +1,40 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.apache.tomcat.websocket;
|
||||
|
||||
import javax.websocket.Extension.Parameter;
|
||||
|
||||
public class WsExtensionParameter implements Parameter {
|
||||
|
||||
private final String name;
|
||||
private final String value;
|
||||
|
||||
WsExtensionParameter(String name, String value) {
|
||||
this.name = name;
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getValue() {
|
||||
return value;
|
||||
}
|
||||
}
|
||||
1011
java/org/apache/tomcat/websocket/WsFrameBase.java
Normal file
1011
java/org/apache/tomcat/websocket/WsFrameBase.java
Normal file
File diff suppressed because it is too large
Load Diff
228
java/org/apache/tomcat/websocket/WsFrameClient.java
Normal file
228
java/org/apache/tomcat/websocket/WsFrameClient.java
Normal file
@@ -0,0 +1,228 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.apache.tomcat.websocket;
|
||||
|
||||
import java.io.EOFException;
|
||||
import java.io.IOException;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.channels.CompletionHandler;
|
||||
|
||||
import javax.websocket.CloseReason;
|
||||
import javax.websocket.CloseReason.CloseCodes;
|
||||
|
||||
import org.apache.juli.logging.Log;
|
||||
import org.apache.juli.logging.LogFactory;
|
||||
import org.apache.tomcat.util.res.StringManager;
|
||||
|
||||
public class WsFrameClient extends WsFrameBase {
|
||||
|
||||
private final Log log = LogFactory.getLog(WsFrameClient.class); // must not be static
|
||||
private static final StringManager sm = StringManager.getManager(WsFrameClient.class);
|
||||
|
||||
private final AsyncChannelWrapper channel;
|
||||
private final CompletionHandler<Integer, Void> handler;
|
||||
// Not final as it may need to be re-sized
|
||||
private volatile ByteBuffer response;
|
||||
|
||||
public WsFrameClient(ByteBuffer response, AsyncChannelWrapper channel, WsSession wsSession,
|
||||
Transformation transformation) {
|
||||
super(wsSession, transformation);
|
||||
this.response = response;
|
||||
this.channel = channel;
|
||||
this.handler = new WsFrameClientCompletionHandler();
|
||||
}
|
||||
|
||||
|
||||
void startInputProcessing() {
|
||||
try {
|
||||
processSocketRead();
|
||||
} catch (IOException e) {
|
||||
close(e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private void processSocketRead() throws IOException {
|
||||
while (true) {
|
||||
switch (getReadState()) {
|
||||
case WAITING:
|
||||
if (!changeReadState(ReadState.WAITING, ReadState.PROCESSING)) {
|
||||
continue;
|
||||
}
|
||||
while (response.hasRemaining()) {
|
||||
if (isSuspended()) {
|
||||
if (!changeReadState(ReadState.SUSPENDING_PROCESS, ReadState.SUSPENDED)) {
|
||||
continue;
|
||||
}
|
||||
// There is still data available in the response buffer
|
||||
// Return here so that the response buffer will not be
|
||||
// cleared and there will be no data read from the
|
||||
// socket. Thus when the read operation is resumed first
|
||||
// the data left in the response buffer will be consumed
|
||||
// and then a new socket read will be performed
|
||||
return;
|
||||
}
|
||||
inputBuffer.mark();
|
||||
inputBuffer.position(inputBuffer.limit()).limit(inputBuffer.capacity());
|
||||
|
||||
int toCopy = Math.min(response.remaining(), inputBuffer.remaining());
|
||||
|
||||
// Copy remaining bytes read in HTTP phase to input buffer used by
|
||||
// frame processing
|
||||
|
||||
int orgLimit = response.limit();
|
||||
response.limit(response.position() + toCopy);
|
||||
inputBuffer.put(response);
|
||||
response.limit(orgLimit);
|
||||
|
||||
inputBuffer.limit(inputBuffer.position()).reset();
|
||||
|
||||
// Process the data we have
|
||||
processInputBuffer();
|
||||
}
|
||||
response.clear();
|
||||
|
||||
// Get some more data
|
||||
if (isOpen()) {
|
||||
channel.read(response, null, handler);
|
||||
} else {
|
||||
changeReadState(ReadState.CLOSING);
|
||||
}
|
||||
return;
|
||||
case SUSPENDING_WAIT:
|
||||
if (!changeReadState(ReadState.SUSPENDING_WAIT, ReadState.SUSPENDED)) {
|
||||
continue;
|
||||
}
|
||||
return;
|
||||
default:
|
||||
throw new IllegalStateException(
|
||||
sm.getString("wsFrameServer.illegalReadState", getReadState()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Fatal error. Usually an I/O error. Try and send notifications. Make sure
|
||||
* socket is closed.
|
||||
*/
|
||||
private final void close(Throwable t) {
|
||||
changeReadState(ReadState.CLOSING);
|
||||
CloseReason cr;
|
||||
if (t instanceof WsIOException) {
|
||||
cr = ((WsIOException) t).getCloseReason();
|
||||
} else {
|
||||
cr = new CloseReason(CloseCodes.CLOSED_ABNORMALLY, t.getMessage());
|
||||
}
|
||||
|
||||
wsSession.doClose(cr, cr, true);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
protected boolean isMasked() {
|
||||
// Data is from the server so it is not masked
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
protected Log getLog() {
|
||||
return log;
|
||||
}
|
||||
|
||||
private class WsFrameClientCompletionHandler implements CompletionHandler<Integer, Void> {
|
||||
|
||||
@Override
|
||||
public void completed(Integer result, Void attachment) {
|
||||
if (result.intValue() == -1) {
|
||||
// BZ 57762. A dropped connection will get reported as EOF
|
||||
// rather than as an error so handle it here.
|
||||
if (isOpen()) {
|
||||
// No close frame was received
|
||||
close(new EOFException());
|
||||
}
|
||||
// No data to process
|
||||
return;
|
||||
}
|
||||
response.flip();
|
||||
doResumeProcessing(true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void failed(Throwable exc, Void attachment) {
|
||||
if (exc instanceof ReadBufferOverflowException) {
|
||||
// response will be empty if this exception is thrown
|
||||
response = ByteBuffer
|
||||
.allocate(((ReadBufferOverflowException) exc).getMinBufferSize());
|
||||
response.flip();
|
||||
doResumeProcessing(false);
|
||||
} else {
|
||||
close(exc);
|
||||
}
|
||||
}
|
||||
|
||||
private void doResumeProcessing(boolean checkOpenOnError) {
|
||||
while (true) {
|
||||
switch (getReadState()) {
|
||||
case PROCESSING:
|
||||
if (!changeReadState(ReadState.PROCESSING, ReadState.WAITING)) {
|
||||
continue;
|
||||
}
|
||||
resumeProcessing(checkOpenOnError);
|
||||
return;
|
||||
case SUSPENDING_PROCESS:
|
||||
if (!changeReadState(ReadState.SUSPENDING_PROCESS, ReadState.SUSPENDED)) {
|
||||
continue;
|
||||
}
|
||||
return;
|
||||
default:
|
||||
throw new IllegalStateException(
|
||||
sm.getString("wsFrame.illegalReadState", getReadState()));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
protected void resumeProcessing() {
|
||||
resumeProcessing(true);
|
||||
}
|
||||
|
||||
private void resumeProcessing(boolean checkOpenOnError) {
|
||||
try {
|
||||
processSocketRead();
|
||||
} catch (IOException e) {
|
||||
if (checkOpenOnError) {
|
||||
// Only send a close message on an IOException if the client
|
||||
// has not yet received a close control message from the server
|
||||
// as the IOException may be in response to the client
|
||||
// continuing to send a message after the server sent a close
|
||||
// control message.
|
||||
if (isOpen()) {
|
||||
if (log.isDebugEnabled()) {
|
||||
log.debug(sm.getString("wsFrameClient.ioe"), e);
|
||||
}
|
||||
close(e);
|
||||
}
|
||||
} else {
|
||||
close(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
56
java/org/apache/tomcat/websocket/WsHandshakeResponse.java
Normal file
56
java/org/apache/tomcat/websocket/WsHandshakeResponse.java
Normal file
@@ -0,0 +1,56 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.apache.tomcat.websocket;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
|
||||
import javax.websocket.HandshakeResponse;
|
||||
|
||||
import org.apache.tomcat.util.collections.CaseInsensitiveKeyMap;
|
||||
|
||||
/**
|
||||
* Represents the response to a WebSocket handshake.
|
||||
*/
|
||||
public class WsHandshakeResponse implements HandshakeResponse {
|
||||
|
||||
private final Map<String,List<String>> headers = new CaseInsensitiveKeyMap<>();
|
||||
|
||||
|
||||
public WsHandshakeResponse() {
|
||||
}
|
||||
|
||||
|
||||
public WsHandshakeResponse(Map<String,List<String>> headers) {
|
||||
for (Entry<String,List<String>> entry : headers.entrySet()) {
|
||||
if (this.headers.containsKey(entry.getKey())) {
|
||||
this.headers.get(entry.getKey()).addAll(entry.getValue());
|
||||
} else {
|
||||
List<String> values = new ArrayList<>(entry.getValue());
|
||||
this.headers.put(entry.getKey(), values);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public Map<String,List<String>> getHeaders() {
|
||||
return headers;
|
||||
}
|
||||
}
|
||||
41
java/org/apache/tomcat/websocket/WsIOException.java
Normal file
41
java/org/apache/tomcat/websocket/WsIOException.java
Normal file
@@ -0,0 +1,41 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.apache.tomcat.websocket;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import javax.websocket.CloseReason;
|
||||
|
||||
/**
|
||||
* Allows the WebSocket implementation to throw an {@link IOException} that
|
||||
* includes a {@link CloseReason} specific to the error that can be passed back
|
||||
* to the client.
|
||||
*/
|
||||
public class WsIOException extends IOException {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
private final CloseReason closeReason;
|
||||
|
||||
public WsIOException(CloseReason closeReason) {
|
||||
this.closeReason = closeReason;
|
||||
}
|
||||
|
||||
public CloseReason getCloseReason() {
|
||||
return closeReason;
|
||||
}
|
||||
}
|
||||
39
java/org/apache/tomcat/websocket/WsPongMessage.java
Normal file
39
java/org/apache/tomcat/websocket/WsPongMessage.java
Normal file
@@ -0,0 +1,39 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.apache.tomcat.websocket;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
import javax.websocket.PongMessage;
|
||||
|
||||
public class WsPongMessage implements PongMessage {
|
||||
|
||||
private final ByteBuffer applicationData;
|
||||
|
||||
|
||||
public WsPongMessage(ByteBuffer applicationData) {
|
||||
byte[] dst = new byte[applicationData.limit()];
|
||||
applicationData.get(dst);
|
||||
this.applicationData = ByteBuffer.wrap(dst);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public ByteBuffer getApplicationData() {
|
||||
return applicationData;
|
||||
}
|
||||
}
|
||||
79
java/org/apache/tomcat/websocket/WsRemoteEndpointAsync.java
Normal file
79
java/org/apache/tomcat/websocket/WsRemoteEndpointAsync.java
Normal file
@@ -0,0 +1,79 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.apache.tomcat.websocket;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.concurrent.Future;
|
||||
|
||||
import javax.websocket.RemoteEndpoint;
|
||||
import javax.websocket.SendHandler;
|
||||
|
||||
public class WsRemoteEndpointAsync extends WsRemoteEndpointBase
|
||||
implements RemoteEndpoint.Async {
|
||||
|
||||
WsRemoteEndpointAsync(WsRemoteEndpointImplBase base) {
|
||||
super(base);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public long getSendTimeout() {
|
||||
return base.getSendTimeout();
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void setSendTimeout(long timeout) {
|
||||
base.setSendTimeout(timeout);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void sendText(String text, SendHandler completion) {
|
||||
base.sendStringByCompletion(text, completion);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public Future<Void> sendText(String text) {
|
||||
return base.sendStringByFuture(text);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public Future<Void> sendBinary(ByteBuffer data) {
|
||||
return base.sendBytesByFuture(data);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void sendBinary(ByteBuffer data, SendHandler completion) {
|
||||
base.sendBytesByCompletion(data, completion);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public Future<Void> sendObject(Object obj) {
|
||||
return base.sendObjectByFuture(obj);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void sendObject(Object obj, SendHandler completion) {
|
||||
base.sendObjectByCompletion(obj, completion);
|
||||
}
|
||||
}
|
||||
64
java/org/apache/tomcat/websocket/WsRemoteEndpointBase.java
Normal file
64
java/org/apache/tomcat/websocket/WsRemoteEndpointBase.java
Normal file
@@ -0,0 +1,64 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.apache.tomcat.websocket;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
import javax.websocket.RemoteEndpoint;
|
||||
|
||||
public abstract class WsRemoteEndpointBase implements RemoteEndpoint {
|
||||
|
||||
protected final WsRemoteEndpointImplBase base;
|
||||
|
||||
|
||||
WsRemoteEndpointBase(WsRemoteEndpointImplBase base) {
|
||||
this.base = base;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public final void setBatchingAllowed(boolean batchingAllowed) throws IOException {
|
||||
base.setBatchingAllowed(batchingAllowed);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public final boolean getBatchingAllowed() {
|
||||
return base.getBatchingAllowed();
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public final void flushBatch() throws IOException {
|
||||
base.flushBatch();
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public final void sendPing(ByteBuffer applicationData) throws IOException,
|
||||
IllegalArgumentException {
|
||||
base.sendPing(applicationData);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public final void sendPong(ByteBuffer applicationData) throws IOException,
|
||||
IllegalArgumentException {
|
||||
base.sendPong(applicationData);
|
||||
}
|
||||
}
|
||||
76
java/org/apache/tomcat/websocket/WsRemoteEndpointBasic.java
Normal file
76
java/org/apache/tomcat/websocket/WsRemoteEndpointBasic.java
Normal file
@@ -0,0 +1,76 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.apache.tomcat.websocket;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.io.Writer;
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
import javax.websocket.EncodeException;
|
||||
import javax.websocket.RemoteEndpoint;
|
||||
|
||||
public class WsRemoteEndpointBasic extends WsRemoteEndpointBase
|
||||
implements RemoteEndpoint.Basic {
|
||||
|
||||
WsRemoteEndpointBasic(WsRemoteEndpointImplBase base) {
|
||||
super(base);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void sendText(String text) throws IOException {
|
||||
base.sendString(text);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void sendBinary(ByteBuffer data) throws IOException {
|
||||
base.sendBytes(data);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void sendText(String fragment, boolean isLast) throws IOException {
|
||||
base.sendPartialString(fragment, isLast);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void sendBinary(ByteBuffer partialByte, boolean isLast)
|
||||
throws IOException {
|
||||
base.sendPartialBytes(partialByte, isLast);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public OutputStream getSendStream() throws IOException {
|
||||
return base.getSendStream();
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public Writer getSendWriter() throws IOException {
|
||||
return base.getSendWriter();
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void sendObject(Object o) throws IOException, EncodeException {
|
||||
base.sendObject(o);
|
||||
}
|
||||
}
|
||||
1272
java/org/apache/tomcat/websocket/WsRemoteEndpointImplBase.java
Normal file
1272
java/org/apache/tomcat/websocket/WsRemoteEndpointImplBase.java
Normal file
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,75 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.apache.tomcat.websocket;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.TimeoutException;
|
||||
|
||||
import javax.websocket.SendHandler;
|
||||
import javax.websocket.SendResult;
|
||||
|
||||
public class WsRemoteEndpointImplClient extends WsRemoteEndpointImplBase {
|
||||
|
||||
private final AsyncChannelWrapper channel;
|
||||
|
||||
public WsRemoteEndpointImplClient(AsyncChannelWrapper channel) {
|
||||
this.channel = channel;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
protected boolean isMasked() {
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
protected void doWrite(SendHandler handler, long blockingWriteTimeoutExpiry,
|
||||
ByteBuffer... data) {
|
||||
long timeout;
|
||||
for (ByteBuffer byteBuffer : data) {
|
||||
if (blockingWriteTimeoutExpiry == -1) {
|
||||
timeout = getSendTimeout();
|
||||
if (timeout < 1) {
|
||||
timeout = Long.MAX_VALUE;
|
||||
}
|
||||
} else {
|
||||
timeout = blockingWriteTimeoutExpiry - System.currentTimeMillis();
|
||||
if (timeout < 0) {
|
||||
SendResult sr = new SendResult(new IOException(sm.getString("wsRemoteEndpoint.writeTimeout")));
|
||||
handler.onResult(sr);
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
channel.write(byteBuffer).get(timeout, TimeUnit.MILLISECONDS);
|
||||
} catch (InterruptedException | ExecutionException | TimeoutException e) {
|
||||
handler.onResult(new SendResult(e));
|
||||
return;
|
||||
}
|
||||
}
|
||||
handler.onResult(SENDRESULT_OK);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doClose() {
|
||||
channel.close();
|
||||
}
|
||||
}
|
||||
867
java/org/apache/tomcat/websocket/WsSession.java
Normal file
867
java/org/apache/tomcat/websocket/WsSession.java
Normal file
@@ -0,0 +1,867 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.apache.tomcat.websocket;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.URI;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.channels.WritePendingException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.security.Principal;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.atomic.AtomicLong;
|
||||
|
||||
import javax.websocket.CloseReason;
|
||||
import javax.websocket.CloseReason.CloseCode;
|
||||
import javax.websocket.CloseReason.CloseCodes;
|
||||
import javax.websocket.DeploymentException;
|
||||
import javax.websocket.Endpoint;
|
||||
import javax.websocket.EndpointConfig;
|
||||
import javax.websocket.Extension;
|
||||
import javax.websocket.MessageHandler;
|
||||
import javax.websocket.MessageHandler.Partial;
|
||||
import javax.websocket.MessageHandler.Whole;
|
||||
import javax.websocket.PongMessage;
|
||||
import javax.websocket.RemoteEndpoint;
|
||||
import javax.websocket.SendResult;
|
||||
import javax.websocket.Session;
|
||||
import javax.websocket.WebSocketContainer;
|
||||
import javax.websocket.server.ServerEndpointConfig;
|
||||
|
||||
import org.apache.juli.logging.Log;
|
||||
import org.apache.juli.logging.LogFactory;
|
||||
import org.apache.tomcat.InstanceManager;
|
||||
import org.apache.tomcat.InstanceManagerBindings;
|
||||
import org.apache.tomcat.util.ExceptionUtils;
|
||||
import org.apache.tomcat.util.res.StringManager;
|
||||
|
||||
public class WsSession implements Session {
|
||||
|
||||
// An ellipsis is a single character that looks like three periods in a row
|
||||
// and is used to indicate a continuation.
|
||||
private static final byte[] ELLIPSIS_BYTES = "\u2026".getBytes(StandardCharsets.UTF_8);
|
||||
// An ellipsis is three bytes in UTF-8
|
||||
private static final int ELLIPSIS_BYTES_LEN = ELLIPSIS_BYTES.length;
|
||||
|
||||
private static final StringManager sm = StringManager.getManager(WsSession.class);
|
||||
private static AtomicLong ids = new AtomicLong(0);
|
||||
|
||||
private final Log log = LogFactory.getLog(WsSession.class); // must not be static
|
||||
|
||||
private final Endpoint localEndpoint;
|
||||
private final WsRemoteEndpointImplBase wsRemoteEndpoint;
|
||||
private final RemoteEndpoint.Async remoteEndpointAsync;
|
||||
private final RemoteEndpoint.Basic remoteEndpointBasic;
|
||||
private final ClassLoader applicationClassLoader;
|
||||
private final WsWebSocketContainer webSocketContainer;
|
||||
private final URI requestUri;
|
||||
private final Map<String, List<String>> requestParameterMap;
|
||||
private final String queryString;
|
||||
private final Principal userPrincipal;
|
||||
private final EndpointConfig endpointConfig;
|
||||
|
||||
private final List<Extension> negotiatedExtensions;
|
||||
private final String subProtocol;
|
||||
private final Map<String, String> pathParameters;
|
||||
private final boolean secure;
|
||||
private final String httpSessionId;
|
||||
private final String id;
|
||||
|
||||
// Expected to handle message types of <String> only
|
||||
private volatile MessageHandler textMessageHandler = null;
|
||||
// Expected to handle message types of <ByteBuffer> only
|
||||
private volatile MessageHandler binaryMessageHandler = null;
|
||||
private volatile MessageHandler.Whole<PongMessage> pongMessageHandler = null;
|
||||
private volatile State state = State.OPEN;
|
||||
private final Object stateLock = new Object();
|
||||
private final Map<String, Object> userProperties = new ConcurrentHashMap<>();
|
||||
private volatile int maxBinaryMessageBufferSize = Constants.DEFAULT_BUFFER_SIZE;
|
||||
private volatile int maxTextMessageBufferSize = Constants.DEFAULT_BUFFER_SIZE;
|
||||
private volatile long maxIdleTimeout = 0;
|
||||
private volatile long lastActive = System.currentTimeMillis();
|
||||
private Map<FutureToSendHandler, FutureToSendHandler> futures = new ConcurrentHashMap<>();
|
||||
|
||||
/**
|
||||
* Creates a new WebSocket session for communication between the two
|
||||
* provided end points. The result of {@link Thread#getContextClassLoader()}
|
||||
* at the time this constructor is called will be used when calling
|
||||
* {@link Endpoint#onClose(Session, CloseReason)}.
|
||||
*
|
||||
* @param localEndpoint The end point managed by this code
|
||||
* @param wsRemoteEndpoint The other / remote endpoint
|
||||
* @param wsWebSocketContainer The container that created this session
|
||||
* @param requestUri The URI used to connect to this endpoint or
|
||||
* <code>null</code> is this is a client session
|
||||
* @param requestParameterMap The parameters associated with the request
|
||||
* that initiated this session or
|
||||
* <code>null</code> if this is a client session
|
||||
* @param queryString The query string associated with the request
|
||||
* that initiated this session or
|
||||
* <code>null</code> if this is a client session
|
||||
* @param userPrincipal The principal associated with the request
|
||||
* that initiated this session or
|
||||
* <code>null</code> if this is a client session
|
||||
* @param httpSessionId The HTTP session ID associated with the
|
||||
* request that initiated this session or
|
||||
* <code>null</code> if this is a client session
|
||||
* @param negotiatedExtensions The agreed extensions to use for this session
|
||||
* @param subProtocol The agreed subprotocol to use for this
|
||||
* session
|
||||
* @param pathParameters The path parameters associated with the
|
||||
* request that initiated this session or
|
||||
* <code>null</code> if this is a client session
|
||||
* @param secure Was this session initiated over a secure
|
||||
* connection?
|
||||
* @param endpointConfig The configuration information for the
|
||||
* endpoint
|
||||
* @throws DeploymentException if an invalid encode is specified
|
||||
*/
|
||||
public WsSession(Endpoint localEndpoint,
|
||||
WsRemoteEndpointImplBase wsRemoteEndpoint,
|
||||
WsWebSocketContainer wsWebSocketContainer,
|
||||
URI requestUri, Map<String, List<String>> requestParameterMap,
|
||||
String queryString, Principal userPrincipal, String httpSessionId,
|
||||
List<Extension> negotiatedExtensions, String subProtocol, Map<String, String> pathParameters,
|
||||
boolean secure, EndpointConfig endpointConfig) throws DeploymentException {
|
||||
this.localEndpoint = localEndpoint;
|
||||
this.wsRemoteEndpoint = wsRemoteEndpoint;
|
||||
this.wsRemoteEndpoint.setSession(this);
|
||||
this.remoteEndpointAsync = new WsRemoteEndpointAsync(wsRemoteEndpoint);
|
||||
this.remoteEndpointBasic = new WsRemoteEndpointBasic(wsRemoteEndpoint);
|
||||
this.webSocketContainer = wsWebSocketContainer;
|
||||
applicationClassLoader = Thread.currentThread().getContextClassLoader();
|
||||
wsRemoteEndpoint.setSendTimeout(wsWebSocketContainer.getDefaultAsyncSendTimeout());
|
||||
this.maxBinaryMessageBufferSize = webSocketContainer.getDefaultMaxBinaryMessageBufferSize();
|
||||
this.maxTextMessageBufferSize = webSocketContainer.getDefaultMaxTextMessageBufferSize();
|
||||
this.maxIdleTimeout = webSocketContainer.getDefaultMaxSessionIdleTimeout();
|
||||
this.requestUri = requestUri;
|
||||
if (requestParameterMap == null) {
|
||||
this.requestParameterMap = Collections.emptyMap();
|
||||
} else {
|
||||
this.requestParameterMap = requestParameterMap;
|
||||
}
|
||||
this.queryString = queryString;
|
||||
this.userPrincipal = userPrincipal;
|
||||
this.httpSessionId = httpSessionId;
|
||||
this.negotiatedExtensions = negotiatedExtensions;
|
||||
if (subProtocol == null) {
|
||||
this.subProtocol = "";
|
||||
} else {
|
||||
this.subProtocol = subProtocol;
|
||||
}
|
||||
this.pathParameters = pathParameters;
|
||||
this.secure = secure;
|
||||
this.wsRemoteEndpoint.setEncoders(endpointConfig);
|
||||
this.endpointConfig = endpointConfig;
|
||||
|
||||
this.userProperties.putAll(endpointConfig.getUserProperties());
|
||||
this.id = Long.toHexString(ids.getAndIncrement());
|
||||
|
||||
InstanceManager instanceManager = webSocketContainer.getInstanceManager();
|
||||
if (instanceManager == null) {
|
||||
instanceManager = InstanceManagerBindings.get(applicationClassLoader);
|
||||
}
|
||||
if (instanceManager != null) {
|
||||
try {
|
||||
instanceManager.newInstance(localEndpoint);
|
||||
} catch (Exception e) {
|
||||
throw new DeploymentException(sm.getString("wsSession.instanceNew"), e);
|
||||
}
|
||||
}
|
||||
|
||||
if (log.isDebugEnabled()) {
|
||||
log.debug(sm.getString("wsSession.created", id));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public WebSocketContainer getContainer() {
|
||||
checkState();
|
||||
return webSocketContainer;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void addMessageHandler(MessageHandler listener) {
|
||||
Class<?> target = Util.getMessageType(listener);
|
||||
doAddMessageHandler(target, listener);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public <T> void addMessageHandler(Class<T> clazz, Partial<T> handler)
|
||||
throws IllegalStateException {
|
||||
doAddMessageHandler(clazz, handler);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public <T> void addMessageHandler(Class<T> clazz, Whole<T> handler)
|
||||
throws IllegalStateException {
|
||||
doAddMessageHandler(clazz, handler);
|
||||
}
|
||||
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private void doAddMessageHandler(Class<?> target, MessageHandler listener) {
|
||||
checkState();
|
||||
|
||||
// Message handlers that require decoders may map to text messages,
|
||||
// binary messages, both or neither.
|
||||
|
||||
// The frame processing code expects binary message handlers to
|
||||
// accept ByteBuffer
|
||||
|
||||
// Use the POJO message handler wrappers as they are designed to wrap
|
||||
// arbitrary objects with MessageHandlers and can wrap MessageHandlers
|
||||
// just as easily.
|
||||
|
||||
Set<MessageHandlerResult> mhResults = Util.getMessageHandlers(target, listener,
|
||||
endpointConfig, this);
|
||||
|
||||
for (MessageHandlerResult mhResult : mhResults) {
|
||||
switch (mhResult.getType()) {
|
||||
case TEXT: {
|
||||
if (textMessageHandler != null) {
|
||||
throw new IllegalStateException(sm.getString("wsSession.duplicateHandlerText"));
|
||||
}
|
||||
textMessageHandler = mhResult.getHandler();
|
||||
break;
|
||||
}
|
||||
case BINARY: {
|
||||
if (binaryMessageHandler != null) {
|
||||
throw new IllegalStateException(
|
||||
sm.getString("wsSession.duplicateHandlerBinary"));
|
||||
}
|
||||
binaryMessageHandler = mhResult.getHandler();
|
||||
break;
|
||||
}
|
||||
case PONG: {
|
||||
if (pongMessageHandler != null) {
|
||||
throw new IllegalStateException(sm.getString("wsSession.duplicateHandlerPong"));
|
||||
}
|
||||
MessageHandler handler = mhResult.getHandler();
|
||||
if (handler instanceof MessageHandler.Whole<?>) {
|
||||
pongMessageHandler = (MessageHandler.Whole<PongMessage>) handler;
|
||||
} else {
|
||||
throw new IllegalStateException(
|
||||
sm.getString("wsSession.invalidHandlerTypePong"));
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
throw new IllegalArgumentException(
|
||||
sm.getString("wsSession.unknownHandlerType", listener, mhResult.getType()));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public Set<MessageHandler> getMessageHandlers() {
|
||||
checkState();
|
||||
Set<MessageHandler> result = new HashSet<>();
|
||||
if (binaryMessageHandler != null) {
|
||||
result.add(binaryMessageHandler);
|
||||
}
|
||||
if (textMessageHandler != null) {
|
||||
result.add(textMessageHandler);
|
||||
}
|
||||
if (pongMessageHandler != null) {
|
||||
result.add(pongMessageHandler);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void removeMessageHandler(MessageHandler listener) {
|
||||
checkState();
|
||||
if (listener == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
MessageHandler wrapped = null;
|
||||
|
||||
if (listener instanceof WrappedMessageHandler) {
|
||||
wrapped = ((WrappedMessageHandler) listener).getWrappedHandler();
|
||||
}
|
||||
|
||||
if (wrapped == null) {
|
||||
wrapped = listener;
|
||||
}
|
||||
|
||||
boolean removed = false;
|
||||
if (wrapped.equals(textMessageHandler) || listener.equals(textMessageHandler)) {
|
||||
textMessageHandler = null;
|
||||
removed = true;
|
||||
}
|
||||
|
||||
if (wrapped.equals(binaryMessageHandler) || listener.equals(binaryMessageHandler)) {
|
||||
binaryMessageHandler = null;
|
||||
removed = true;
|
||||
}
|
||||
|
||||
if (wrapped.equals(pongMessageHandler) || listener.equals(pongMessageHandler)) {
|
||||
pongMessageHandler = null;
|
||||
removed = true;
|
||||
}
|
||||
|
||||
if (!removed) {
|
||||
// ISE for now. Could swallow this silently / log this if the ISE
|
||||
// becomes a problem
|
||||
throw new IllegalStateException(
|
||||
sm.getString("wsSession.removeHandlerFailed", listener));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public String getProtocolVersion() {
|
||||
checkState();
|
||||
return Constants.WS_VERSION_HEADER_VALUE;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public String getNegotiatedSubprotocol() {
|
||||
checkState();
|
||||
return subProtocol;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public List<Extension> getNegotiatedExtensions() {
|
||||
checkState();
|
||||
return negotiatedExtensions;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public boolean isSecure() {
|
||||
checkState();
|
||||
return secure;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public boolean isOpen() {
|
||||
return state == State.OPEN;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public long getMaxIdleTimeout() {
|
||||
checkState();
|
||||
return maxIdleTimeout;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void setMaxIdleTimeout(long timeout) {
|
||||
checkState();
|
||||
this.maxIdleTimeout = timeout;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void setMaxBinaryMessageBufferSize(int max) {
|
||||
checkState();
|
||||
this.maxBinaryMessageBufferSize = max;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public int getMaxBinaryMessageBufferSize() {
|
||||
checkState();
|
||||
return maxBinaryMessageBufferSize;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void setMaxTextMessageBufferSize(int max) {
|
||||
checkState();
|
||||
this.maxTextMessageBufferSize = max;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public int getMaxTextMessageBufferSize() {
|
||||
checkState();
|
||||
return maxTextMessageBufferSize;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public Set<Session> getOpenSessions() {
|
||||
checkState();
|
||||
return webSocketContainer.getOpenSessions(getSessionMapKey());
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public RemoteEndpoint.Async getAsyncRemote() {
|
||||
checkState();
|
||||
return remoteEndpointAsync;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public RemoteEndpoint.Basic getBasicRemote() {
|
||||
checkState();
|
||||
return remoteEndpointBasic;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void close() throws IOException {
|
||||
close(new CloseReason(CloseCodes.NORMAL_CLOSURE, ""));
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void close(CloseReason closeReason) throws IOException {
|
||||
doClose(closeReason, closeReason);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* WebSocket 1.0. Section 2.1.5.
|
||||
* Need internal close method as spec requires that the local endpoint
|
||||
* receives a 1006 on timeout.
|
||||
*
|
||||
* @param closeReasonMessage The close reason to pass to the remote endpoint
|
||||
* @param closeReasonLocal The close reason to pass to the local endpoint
|
||||
*/
|
||||
public void doClose(CloseReason closeReasonMessage, CloseReason closeReasonLocal) {
|
||||
doClose(closeReasonMessage, closeReasonLocal, false);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* WebSocket 1.0. Section 2.1.5.
|
||||
* Need internal close method as spec requires that the local endpoint
|
||||
* receives a 1006 on timeout.
|
||||
*
|
||||
* @param closeReasonMessage The close reason to pass to the remote endpoint
|
||||
* @param closeReasonLocal The close reason to pass to the local endpoint
|
||||
* @param closeSocket Should the socket be closed immediately rather than waiting
|
||||
* for the server to respond
|
||||
*/
|
||||
public void doClose(CloseReason closeReasonMessage, CloseReason closeReasonLocal,
|
||||
boolean closeSocket) {
|
||||
// Double-checked locking. OK because state is volatile
|
||||
if (state != State.OPEN) {
|
||||
return;
|
||||
}
|
||||
|
||||
synchronized (stateLock) {
|
||||
if (state != State.OPEN) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (log.isDebugEnabled()) {
|
||||
log.debug(sm.getString("wsSession.doClose", id));
|
||||
}
|
||||
try {
|
||||
wsRemoteEndpoint.setBatchingAllowed(false);
|
||||
} catch (IOException e) {
|
||||
log.warn(sm.getString("wsSession.flushFailOnClose"), e);
|
||||
fireEndpointOnError(e);
|
||||
}
|
||||
|
||||
state = State.OUTPUT_CLOSED;
|
||||
|
||||
sendCloseMessage(closeReasonMessage);
|
||||
if (closeSocket) {
|
||||
wsRemoteEndpoint.close();
|
||||
}
|
||||
fireEndpointOnClose(closeReasonLocal);
|
||||
}
|
||||
|
||||
IOException ioe = new IOException(sm.getString("wsSession.messageFailed"));
|
||||
SendResult sr = new SendResult(ioe);
|
||||
for (FutureToSendHandler f2sh : futures.keySet()) {
|
||||
f2sh.onResult(sr);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Called when a close message is received. Should only ever happen once.
|
||||
* Also called after a protocol error when the ProtocolHandler needs to
|
||||
* force the closing of the connection.
|
||||
*
|
||||
* @param closeReason The reason contained within the received close
|
||||
* message.
|
||||
*/
|
||||
public void onClose(CloseReason closeReason) {
|
||||
|
||||
synchronized (stateLock) {
|
||||
if (state != State.CLOSED) {
|
||||
try {
|
||||
wsRemoteEndpoint.setBatchingAllowed(false);
|
||||
} catch (IOException e) {
|
||||
log.warn(sm.getString("wsSession.flushFailOnClose"), e);
|
||||
fireEndpointOnError(e);
|
||||
}
|
||||
if (state == State.OPEN) {
|
||||
state = State.OUTPUT_CLOSED;
|
||||
sendCloseMessage(closeReason);
|
||||
fireEndpointOnClose(closeReason);
|
||||
}
|
||||
state = State.CLOSED;
|
||||
|
||||
// Close the socket
|
||||
wsRemoteEndpoint.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void fireEndpointOnClose(CloseReason closeReason) {
|
||||
|
||||
// Fire the onClose event
|
||||
Throwable throwable = null;
|
||||
InstanceManager instanceManager = webSocketContainer.getInstanceManager();
|
||||
if (instanceManager == null) {
|
||||
instanceManager = InstanceManagerBindings.get(applicationClassLoader);
|
||||
}
|
||||
Thread t = Thread.currentThread();
|
||||
ClassLoader cl = t.getContextClassLoader();
|
||||
t.setContextClassLoader(applicationClassLoader);
|
||||
try {
|
||||
localEndpoint.onClose(this, closeReason);
|
||||
} catch (Throwable t1) {
|
||||
ExceptionUtils.handleThrowable(t1);
|
||||
throwable = t1;
|
||||
} finally {
|
||||
if (instanceManager != null) {
|
||||
try {
|
||||
instanceManager.destroyInstance(localEndpoint);
|
||||
} catch (Throwable t2) {
|
||||
ExceptionUtils.handleThrowable(t2);
|
||||
if (throwable == null) {
|
||||
throwable = t2;
|
||||
}
|
||||
}
|
||||
}
|
||||
t.setContextClassLoader(cl);
|
||||
}
|
||||
|
||||
if (throwable != null) {
|
||||
fireEndpointOnError(throwable);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
private void fireEndpointOnError(Throwable throwable) {
|
||||
|
||||
// Fire the onError event
|
||||
Thread t = Thread.currentThread();
|
||||
ClassLoader cl = t.getContextClassLoader();
|
||||
t.setContextClassLoader(applicationClassLoader);
|
||||
try {
|
||||
localEndpoint.onError(this, throwable);
|
||||
} finally {
|
||||
t.setContextClassLoader(cl);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private void sendCloseMessage(CloseReason closeReason) {
|
||||
// 125 is maximum size for the payload of a control message
|
||||
ByteBuffer msg = ByteBuffer.allocate(125);
|
||||
CloseCode closeCode = closeReason.getCloseCode();
|
||||
// CLOSED_ABNORMALLY should not be put on the wire
|
||||
if (closeCode == CloseCodes.CLOSED_ABNORMALLY) {
|
||||
// PROTOCOL_ERROR is probably better than GOING_AWAY here
|
||||
msg.putShort((short) CloseCodes.PROTOCOL_ERROR.getCode());
|
||||
} else {
|
||||
msg.putShort((short) closeCode.getCode());
|
||||
}
|
||||
|
||||
String reason = closeReason.getReasonPhrase();
|
||||
if (reason != null && reason.length() > 0) {
|
||||
appendCloseReasonWithTruncation(msg, reason);
|
||||
}
|
||||
msg.flip();
|
||||
try {
|
||||
wsRemoteEndpoint.sendMessageBlock(Constants.OPCODE_CLOSE, msg, true);
|
||||
} catch (IOException | WritePendingException e) {
|
||||
// Failed to send close message. Close the socket and let the caller
|
||||
// deal with the Exception
|
||||
if (log.isDebugEnabled()) {
|
||||
log.debug(sm.getString("wsSession.sendCloseFail", id), e);
|
||||
}
|
||||
wsRemoteEndpoint.close();
|
||||
// Failure to send a close message is not unexpected in the case of
|
||||
// an abnormal closure (usually triggered by a failure to read/write
|
||||
// from/to the client. In this case do not trigger the endpoint's
|
||||
// error handling
|
||||
if (closeCode != CloseCodes.CLOSED_ABNORMALLY) {
|
||||
localEndpoint.onError(this, e);
|
||||
}
|
||||
} finally {
|
||||
webSocketContainer.unregisterSession(getSessionMapKey(), this);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private Object getSessionMapKey() {
|
||||
if (endpointConfig instanceof ServerEndpointConfig) {
|
||||
// Server
|
||||
return ((ServerEndpointConfig) endpointConfig).getPath();
|
||||
} else {
|
||||
// Client
|
||||
return localEndpoint;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Use protected so unit tests can access this method directly.
|
||||
* @param msg The message
|
||||
* @param reason The reason
|
||||
*/
|
||||
protected static void appendCloseReasonWithTruncation(ByteBuffer msg, String reason) {
|
||||
// Once the close code has been added there are a maximum of 123 bytes
|
||||
// left for the reason phrase. If it is truncated then care needs to be
|
||||
// taken to ensure the bytes are not truncated in the middle of a
|
||||
// multi-byte UTF-8 character.
|
||||
byte[] reasonBytes = reason.getBytes(StandardCharsets.UTF_8);
|
||||
|
||||
if (reasonBytes.length <= 123) {
|
||||
// No need to truncate
|
||||
msg.put(reasonBytes);
|
||||
} else {
|
||||
// Need to truncate
|
||||
int remaining = 123 - ELLIPSIS_BYTES_LEN;
|
||||
int pos = 0;
|
||||
byte[] bytesNext = reason.substring(pos, pos + 1).getBytes(StandardCharsets.UTF_8);
|
||||
while (remaining >= bytesNext.length) {
|
||||
msg.put(bytesNext);
|
||||
remaining -= bytesNext.length;
|
||||
pos++;
|
||||
bytesNext = reason.substring(pos, pos + 1).getBytes(StandardCharsets.UTF_8);
|
||||
}
|
||||
msg.put(ELLIPSIS_BYTES);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Make the session aware of a {@link FutureToSendHandler} that will need to
|
||||
* be forcibly closed if the session closes before the
|
||||
* {@link FutureToSendHandler} completes.
|
||||
* @param f2sh The handler
|
||||
*/
|
||||
protected void registerFuture(FutureToSendHandler f2sh) {
|
||||
// Ideally, this code should sync on stateLock so that the correct
|
||||
// action is taken based on the current state of the connection.
|
||||
// However, a sync on stateLock can't be used here as it will create the
|
||||
// possibility of a dead-lock. See BZ 61183.
|
||||
// Therefore, a slightly less efficient approach is used.
|
||||
|
||||
// Always register the future.
|
||||
futures.put(f2sh, f2sh);
|
||||
|
||||
if (state == State.OPEN) {
|
||||
// The session is open. The future has been registered with the open
|
||||
// session. Normal processing continues.
|
||||
return;
|
||||
}
|
||||
|
||||
// The session is closed. The future may or may not have been registered
|
||||
// in time for it to be processed during session closure.
|
||||
|
||||
if (f2sh.isDone()) {
|
||||
// The future has completed. It is not known if the future was
|
||||
// completed normally by the I/O layer or in error by doClose(). It
|
||||
// doesn't matter which. There is nothing more to do here.
|
||||
return;
|
||||
}
|
||||
|
||||
// The session is closed. The Future had not completed when last checked.
|
||||
// There is a small timing window that means the Future may have been
|
||||
// completed since the last check. There is also the possibility that
|
||||
// the Future was not registered in time to be cleaned up during session
|
||||
// close.
|
||||
// Attempt to complete the Future with an error result as this ensures
|
||||
// that the Future completes and any client code waiting on it does not
|
||||
// hang. It is slightly inefficient since the Future may have been
|
||||
// completed in another thread or another thread may be about to
|
||||
// complete the Future but knowing if this is the case requires the sync
|
||||
// on stateLock (see above).
|
||||
// Note: If multiple attempts are made to complete the Future, the
|
||||
// second and subsequent attempts are ignored.
|
||||
|
||||
IOException ioe = new IOException(sm.getString("wsSession.messageFailed"));
|
||||
SendResult sr = new SendResult(ioe);
|
||||
f2sh.onResult(sr);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Remove a {@link FutureToSendHandler} from the set of tracked instances.
|
||||
* @param f2sh The handler
|
||||
*/
|
||||
protected void unregisterFuture(FutureToSendHandler f2sh) {
|
||||
futures.remove(f2sh);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public URI getRequestURI() {
|
||||
checkState();
|
||||
return requestUri;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public Map<String, List<String>> getRequestParameterMap() {
|
||||
checkState();
|
||||
return requestParameterMap;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public String getQueryString() {
|
||||
checkState();
|
||||
return queryString;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public Principal getUserPrincipal() {
|
||||
checkState();
|
||||
return userPrincipal;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public Map<String, String> getPathParameters() {
|
||||
checkState();
|
||||
return pathParameters;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public String getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public Map<String, Object> getUserProperties() {
|
||||
checkState();
|
||||
return userProperties;
|
||||
}
|
||||
|
||||
|
||||
public Endpoint getLocal() {
|
||||
return localEndpoint;
|
||||
}
|
||||
|
||||
|
||||
public String getHttpSessionId() {
|
||||
return httpSessionId;
|
||||
}
|
||||
|
||||
|
||||
protected MessageHandler getTextMessageHandler() {
|
||||
return textMessageHandler;
|
||||
}
|
||||
|
||||
|
||||
protected MessageHandler getBinaryMessageHandler() {
|
||||
return binaryMessageHandler;
|
||||
}
|
||||
|
||||
|
||||
protected MessageHandler.Whole<PongMessage> getPongMessageHandler() {
|
||||
return pongMessageHandler;
|
||||
}
|
||||
|
||||
|
||||
protected void updateLastActive() {
|
||||
lastActive = System.currentTimeMillis();
|
||||
}
|
||||
|
||||
|
||||
protected void checkExpiration() {
|
||||
long timeout = maxIdleTimeout;
|
||||
if (timeout < 1) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (System.currentTimeMillis() - lastActive > timeout) {
|
||||
String msg = sm.getString("wsSession.timeout", getId());
|
||||
if (log.isDebugEnabled()) {
|
||||
log.debug(msg);
|
||||
}
|
||||
doClose(new CloseReason(CloseCodes.GOING_AWAY, msg),
|
||||
new CloseReason(CloseCodes.CLOSED_ABNORMALLY, msg));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private void checkState() {
|
||||
if (state == State.CLOSED) {
|
||||
/*
|
||||
* As per RFC 6455, a WebSocket connection is considered to be
|
||||
* closed once a peer has sent and received a WebSocket close frame.
|
||||
*/
|
||||
throw new IllegalStateException(sm.getString("wsSession.closed", id));
|
||||
}
|
||||
}
|
||||
|
||||
private enum State {
|
||||
OPEN,
|
||||
OUTPUT_CLOSED,
|
||||
CLOSED
|
||||
}
|
||||
|
||||
|
||||
private WsFrameBase wsFrame;
|
||||
void setWsFrame(WsFrameBase wsFrame) {
|
||||
this.wsFrame = wsFrame;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Suspends the reading of the incoming messages.
|
||||
*/
|
||||
public void suspend() {
|
||||
wsFrame.suspend();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Resumes the reading of the incoming messages.
|
||||
*/
|
||||
public void resume() {
|
||||
wsFrame.resume();
|
||||
}
|
||||
}
|
||||
1126
java/org/apache/tomcat/websocket/WsWebSocketContainer.java
Normal file
1126
java/org/apache/tomcat/websocket/WsWebSocketContainer.java
Normal file
File diff suppressed because it is too large
Load Diff
32
java/org/apache/tomcat/websocket/pojo/Constants.java
Normal file
32
java/org/apache/tomcat/websocket/pojo/Constants.java
Normal file
@@ -0,0 +1,32 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.apache.tomcat.websocket.pojo;
|
||||
|
||||
/**
|
||||
* Internal implementation constants.
|
||||
*/
|
||||
public class Constants {
|
||||
|
||||
public static final String POJO_PATH_PARAM_KEY =
|
||||
"org.apache.tomcat.websocket.pojo.PojoEndpoint.pathParams";
|
||||
public static final String POJO_METHOD_MAPPING_KEY =
|
||||
"org.apache.tomcat.websocket.pojo.PojoEndpoint.methodMapping";
|
||||
|
||||
private Constants() {
|
||||
// Hide default constructor
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
# Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
# contributor license agreements. See the NOTICE file distributed with
|
||||
# this work for additional information regarding copyright ownership.
|
||||
# The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
# (the "License"); you may not use this file except in compliance with
|
||||
# the License. You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
pojoEndpointBase.closeSessionFail=Failed to close WebSocket session during error handling
|
||||
pojoEndpointBase.onCloseFail=Failed to call onClose method of POJO end point for POJO of type [{0}]
|
||||
pojoEndpointBase.onError=No error handling configured for [{0}] and the following error occurred
|
||||
pojoEndpointBase.onErrorFail=Failed to call onError method of POJO end point for POJO of type [{0}]
|
||||
pojoEndpointBase.onOpenFail=Failed to call onOpen method of POJO end point for POJO of type [{0}]
|
||||
|
||||
pojoEndpointServer.getPojoInstanceFail=Failed to create instance of POJO of type [{0}]
|
||||
|
||||
pojoMessageHandlerWhole.decodeIoFail=IO error while decoding message
|
||||
pojoMessageHandlerWhole.maxBufferSize=The maximum supported message size for this implementation is Integer.MAX_VALUE
|
||||
|
||||
pojoMethodMapping.decodePathParamFail=Failed to decode path parameter value [{0}] to expected type [{1}]
|
||||
pojoMethodMapping.duplicateAnnotation=Duplicate annotations [{0}] present on class [{1}]
|
||||
pojoMethodMapping.duplicateLastParam=Multiple boolean (last) parameters present on the method [{0}] of class [{1}] that was annotated with OnMessage
|
||||
pojoMethodMapping.duplicateMessageParam=Multiple message parameters present on the method [{0}] of class [{1}] that was annotated with OnMessage
|
||||
pojoMethodMapping.duplicatePongMessageParam=Multiple PongMessage parameters present on the method [{0}] of class [{1}] that was annotated with OnMessage
|
||||
pojoMethodMapping.duplicateSessionParam=Multiple session parameters present on the method [{0}] of class [{1}] that was annotated with OnMessage
|
||||
pojoMethodMapping.invalidDecoder=The specified decoder of type [{0}] could not be instantiated
|
||||
pojoMethodMapping.methodNotPublic=The annotated method [{0}] is not public
|
||||
pojoMethodMapping.noDecoder=No decoder was found for message parameters present on the method [{0}] of class [{1}] that was annotated with OnMessage
|
||||
pojoMethodMapping.noPayload=No payload parameter present on the method [{0}] of class [{1}] that was annotated with OnMessage
|
||||
pojoMethodMapping.onErrorNoThrowable=No Throwable parameter was present on the method [{0}] of class [{1}] that was annotated with OnError
|
||||
pojoMethodMapping.paramWithoutAnnotation=A parameter of type [{0}] was found on method[{1}] of class [{2}] that did not have a @PathParam annotation
|
||||
pojoMethodMapping.partialInputStream=Invalid InputStream and boolean parameters present on the method [{0}] of class [{1}] that was annotated with OnMessage
|
||||
pojoMethodMapping.partialObject=Invalid Object and boolean parameters present on the method [{0}] of class [{1}] that was annotated with OnMessage
|
||||
pojoMethodMapping.partialPong=Invalid PongMessage and boolean parameters present on the method [{0}] of class [{1}] that was annotated with OnMessage
|
||||
pojoMethodMapping.partialReader=Invalid Reader and boolean parameters present on the method [{0}] of class [{1}] that was annotated with OnMessage
|
||||
pojoMethodMapping.pongWithPayload=Invalid PongMessage and Message parameters present on the method [{0}] of class [{1}] that was annotated with OnMessage
|
||||
|
||||
pojoPathParam.wrongType=The type [{0}] is not permitted as a path parameter. Parameters annotated with @PathParam may only be Strings, Java primitives or a boxed version thereof.
|
||||
@@ -0,0 +1,22 @@
|
||||
# Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
# contributor license agreements. See the NOTICE file distributed with
|
||||
# this work for additional information regarding copyright ownership.
|
||||
# The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
# (the "License"); you may not use this file except in compliance with
|
||||
# the License. You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
pojoEndpointServer.getPojoInstanceFail=Es konnte keine POJO Instanz des Typs [{0}] erzeugt werden
|
||||
|
||||
pojoMethodMapping.duplicateAnnotation=Doppelte Annotation [{0}] an Klasse [{1}] gefunden
|
||||
pojoMethodMapping.duplicateLastParam=Es sind mehrere Boolean (letzte) Parameter für die Methode [{0}] der Klasse [{1}], die mit OnMessage annotiert ist, vorhanden.
|
||||
pojoMethodMapping.invalidDecoder=Der angegeben Dekoder vom Typ [{0}] konnte nicht instanziiert werden
|
||||
pojoMethodMapping.methodNotPublic=Die annotierite Methode [{0}] ist nicht öffentlich
|
||||
pojoMethodMapping.paramWithoutAnnotation=Es wurde ein Parameter des Typs [{0}] an der Methode [{1}] der Klasse [{2}] gefunden der nicht die Annotation @PathParam hat
|
||||
@@ -0,0 +1,24 @@
|
||||
# Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
# contributor license agreements. See the NOTICE file distributed with
|
||||
# this work for additional information regarding copyright ownership.
|
||||
# The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
# (the "License"); you may not use this file except in compliance with
|
||||
# the License. You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
pojoEndpointBase.onCloseFail=Fallo al llamar el método onClose del endpoint POJO para el tipo POJO [{0}]
|
||||
pojoEndpointBase.onOpenFail=Fallo al llamar el método onOpen del end point POJO para el tipo POJO [{0}]\n
|
||||
|
||||
pojoEndpointServer.getPojoInstanceFail=Fallo al crear la instancia POJO de tipo [{0}]\n
|
||||
|
||||
pojoMethodMapping.duplicateAnnotation=Anotaciones duplicadas [{0}] presente en la clase [{1}]\n
|
||||
pojoMethodMapping.duplicatePongMessageParam=Varios parámetros de PongMessage estan presentes en el método [{0}] de la clase [{1}] que fue anotado con OnMessage
|
||||
pojoMethodMapping.invalidDecoder=El decodificador especificado de tipo [{0}] no puede ser instanciado\n
|
||||
pojoMethodMapping.onErrorNoThrowable=Parámetro no descartable estaba presente en el método [{0}] de clase [{1}] que fue apuntado con OnError
|
||||
@@ -0,0 +1,45 @@
|
||||
# Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
# contributor license agreements. See the NOTICE file distributed with
|
||||
# this work for additional information regarding copyright ownership.
|
||||
# The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
# (the "License"); you may not use this file except in compliance with
|
||||
# the License. You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
pojoEndpointBase.closeSessionFail=Echec de fermeture de la session WebSocket pendant le traitement d'erreur
|
||||
pojoEndpointBase.onCloseFail=Echec lors de l''appel de la méthode onClose du point de terminaison POJO de type [{0}]
|
||||
pojoEndpointBase.onError=Aucun gestionnaire d''erreur n''est configuré pour [{0}] et l''erreur suivante s''est produite
|
||||
pojoEndpointBase.onErrorFail=Echec de l''appel de la méthode onError du point de terminaison POJO pour le type [{0}]
|
||||
pojoEndpointBase.onOpenFail=Impossible d’appeler la méthode onOpen du point de terminaison POJO de type [{0}]
|
||||
|
||||
pojoEndpointServer.getPojoInstanceFail=Échec de création d''une instance de POJO de type [{0}]
|
||||
|
||||
pojoMessageHandlerWhole.decodeIoFail=Erreur d'IO lors du décodage du message
|
||||
pojoMessageHandlerWhole.maxBufferSize=La taille maximale de message supportée par cette implémentation est Integer.MAX_VALUE
|
||||
|
||||
pojoMethodMapping.decodePathParamFail=Echec de décodage de la valeur de paramètre de chemin [{0}] vers le type attendu [{1}]
|
||||
pojoMethodMapping.duplicateAnnotation=Annotations dupliquées [{0}] présentes pour la classe [{1}]
|
||||
pojoMethodMapping.duplicateLastParam=Multiple (derniers) paramètres booléens présents sur la méthode [{0}] de classe [{1}], qui était annotée par OnMessage
|
||||
pojoMethodMapping.duplicateMessageParam=De multiples paramètres de message sont présents sur la méthode [{0}] de la classe [{1}] qui a été annotée avec OnMessage
|
||||
pojoMethodMapping.duplicatePongMessageParam=De multiples paramètres PongMessage sont présents sur la méthode [{0}] de la classe [{1}] qui a été annotée avec OnMessage
|
||||
pojoMethodMapping.duplicateSessionParam=De multiples paramètres de session sont présents sur la méthode [{0}] de la classe [{1}] qui a été annotée avec OnMessage
|
||||
pojoMethodMapping.invalidDecoder=Le décodeur de type [{0}] spécifié n''a pas pu être instantié
|
||||
pojoMethodMapping.methodNotPublic=La méthode [{0}] annotée n''est pas publique
|
||||
pojoMethodMapping.noDecoder=Aucun décodeur n''a été trouvé pour les paramètres de message présents sur la méthode [{0}] de la classe [{1}] qui a été annotée avec OnMessage
|
||||
pojoMethodMapping.noPayload=Pas de paramètre de données présent sur la méthode [{0}] de la classe [{1}] qui a été annotée par OnMessage
|
||||
pojoMethodMapping.onErrorNoThrowable=Aucun paramètre Throwable n''est présent sur la méthode [{0}] de la classe [{1}] qui est annotée par OnError
|
||||
pojoMethodMapping.paramWithoutAnnotation=Un paramètre de type [{0}] a été trouvé sur la méthode [{1}] de la classe [{2}] qui n''avait pas d''annotation @PathParam
|
||||
pojoMethodMapping.partialInputStream=L''InputStream et les paramètres booléens présents sur la méthode [{0}] de la classe [{1}] qui a été annotée par OnMessage sont invalides
|
||||
pojoMethodMapping.partialObject=L''objet et la paramètres booléens présents sur la méthode [{0}] de la classe [{1}] qui a été annotée par OnMessage sont invalides
|
||||
pojoMethodMapping.partialPong=Le PongMessage et les paramètres booléens présents sur la méthode [{0}] de la classe [{1}] qui a été annotée par OnMessage sont invalides
|
||||
pojoMethodMapping.partialReader=Le Reader et les paramètres booléens présents sur la méthode [{0}] de la classe [{1}] qui a été annotée par OnMessage sont invalides
|
||||
pojoMethodMapping.pongWithPayload=Le PongMessage et les paramètres de message présents sur la méthode [{0}] de la classe [{1}] qui a été annotée par OnMessage sont invalides
|
||||
|
||||
pojoPathParam.wrongType=Le type [{0}] n''est pas autorisé en tant que paramètre de chemin, les paramètres annotés avec @PathParm doivent être uniquement des String, des primitives Java ou des versions encapsulées de celles ci
|
||||
@@ -0,0 +1,45 @@
|
||||
# Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
# contributor license agreements. See the NOTICE file distributed with
|
||||
# this work for additional information regarding copyright ownership.
|
||||
# The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
# (the "License"); you may not use this file except in compliance with
|
||||
# the License. You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
pojoEndpointBase.closeSessionFail=エラー処理中に WebSocket セッションを切断できませんでした。
|
||||
pojoEndpointBase.onCloseFail=タイプ[{0}]のPOJOのPOJOエンドポイントのonCloseメソッドの呼び出しに失敗しました
|
||||
pojoEndpointBase.onError=[{0}]に対してエラー処理が構成されておらず、次のエラーが発生しました。
|
||||
pojoEndpointBase.onErrorFail=タイプ[{0}]のPOJOのPOJOエンドポイントのonErrorメソッドの呼び出しに失敗しました
|
||||
pojoEndpointBase.onOpenFail=タイプ[{0}]のPOJOのPOJOエンドポイントのonOpenメソッドの呼び出しに失敗しました。
|
||||
|
||||
pojoEndpointServer.getPojoInstanceFail=POJO クラス [{0}] をインスタンス化できませんでした。
|
||||
|
||||
pojoMessageHandlerWhole.decodeIoFail=メッセージの復号中に入出力エラーが発生しました。
|
||||
pojoMessageHandlerWhole.maxBufferSize=この実装で対応可能なメッセージサイズの上限値は Integer.MAX_VALUE です。
|
||||
|
||||
pojoMethodMapping.decodePathParamFail=パスパラメータの値 [{0}] を [{1}] 型として解釈できません。
|
||||
pojoMethodMapping.duplicateAnnotation=クラス [{1}] にアノテーション [{0}] が重複しています。
|
||||
pojoMethodMapping.duplicateLastParam=OnMessageでアノテーション付けされたクラス[{1}]のメソッド[{0}]に複数のbooleanパラメータが存在します。
|
||||
pojoMethodMapping.duplicateMessageParam=OnMessage アノテーションで修飾されたクラス [{1}] のメソッド [{0}] に複数のメッセージパラメーターが存在します。
|
||||
pojoMethodMapping.duplicatePongMessageParam=OnMessageでアノテーションされたクラス[{1}]のメソッド[{0}]に複数のPongMessageパラメータが存在します。
|
||||
pojoMethodMapping.duplicateSessionParam=OnMessage アノテーションで修飾したクラス [{1}] のメソッド [{0}] に複数のセッションパラメーターが存在します。
|
||||
pojoMethodMapping.invalidDecoder=指定されたデコーダークラス [{0}] をインスタンス化できませんでした。
|
||||
pojoMethodMapping.methodNotPublic=アノテーション付きめそっぢがpublicではありません。
|
||||
pojoMethodMapping.noDecoder=OnMessageでアノテーションが付けられたクラス[{1}]のメソッド[{0}]に存在するメッセージパラメータのデコーダが見つかりませんでした。
|
||||
pojoMethodMapping.noPayload=OnMessage アノテーションで修飾されたクラス [{1}] のメソッド [{0}] にはペイロードに対応するパラメーターがありません。
|
||||
pojoMethodMapping.onErrorNoThrowable=OnErrorでアノテーション付けされたクラス[{1}]のメソッド[{0}]に、Throwableパラメータがありませんでした。
|
||||
pojoMethodMapping.paramWithoutAnnotation=@PathParamアノテーションを持たなかったクラス[{2}]のメソッド[{1}]に[{0}]型のパラメータが見つかりました
|
||||
pojoMethodMapping.partialInputStream=OnMessage アノテーションで修飾されたクラス [{1}] のメソッド [{0}] に入力ストリームと boolean の不正なパラメーターが存在します。
|
||||
pojoMethodMapping.partialObject=OnMessageでアノテーションされたクラス[{1}]のメソッド[{0}]に無効なオブジェクトおよびboolean パラメータがあります
|
||||
pojoMethodMapping.partialPong=OnMessageでアノテーション付けされたクラス[{1}]のメソッド[{0}]に存在するPongMessageパラメータおよびbooleanパラメータが無効です。
|
||||
pojoMethodMapping.partialReader=OnMessage アノテーションで修飾したクラス [{1}] のメソッド [{0}] に Reader および boolean の不正なパラメーターがあります。
|
||||
pojoMethodMapping.pongWithPayload=OnMessageでアノテーションが付けられたクラス[{1}]のメソッド[{0}]に、無効なPongMessageおよびMessageパラメータがあります。
|
||||
|
||||
pojoPathParam.wrongType=タイプ[{0}]はパスパラメータとして許可されていません。 @PathParamでアノテーションが付けられたパラメータは、文字列、Javaプリミティブ、またはそれらのボックス版のみです。
|
||||
@@ -0,0 +1,45 @@
|
||||
# Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
# contributor license agreements. See the NOTICE file distributed with
|
||||
# this work for additional information regarding copyright ownership.
|
||||
# The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
# (the "License"); you may not use this file except in compliance with
|
||||
# the License. You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
pojoEndpointBase.closeSessionFail=오류 처리 중 웹소켓 세션을 닫지 못했습니다.
|
||||
pojoEndpointBase.onCloseFail=타입이 [{0}]인 POJO를 위한 POJO 엔드포인트의 onClose 메소드를 호출하지 못했습니다.
|
||||
pojoEndpointBase.onError=[{0}]을(를) 위한 오류 핸들링이 설정되지 않았으며, 다음과 같은 오류가 발생했습니다.
|
||||
pojoEndpointBase.onErrorFail=타입이 [{0}]인 POJO를 위한 POJO 엔드포인트의 onError를 호출하지 못했습니다.
|
||||
pojoEndpointBase.onOpenFail=타입이 [{0}]인 POJO를 위한, POJO 엔드포인트의 onOpen 메소드를 호출하지 못했습니다.
|
||||
|
||||
pojoEndpointServer.getPojoInstanceFail=타입이 [{0}]인 POJO 인스턴스를 생성하지 못했습니다.
|
||||
|
||||
pojoMessageHandlerWhole.decodeIoFail=메시지를 디코딩하는 중 IO 오류 발생
|
||||
pojoMessageHandlerWhole.maxBufferSize=이 구현을 위해 지원되는 최대 메시지 크기는 Integer.MAX_VALUE입니다.
|
||||
|
||||
pojoMethodMapping.decodePathParamFail=경로 파라미터 값 [{0}]을(를), 요구되는 타입 [{1}](으)로 디코드하지 못했습니다.
|
||||
pojoMethodMapping.duplicateAnnotation=중복된 [{0}] annotation들이 클래스 [{1}]에 존재합니다.
|
||||
pojoMethodMapping.duplicateLastParam=OnMessage로 annotate된 클래스 [{1}]의 메소드 [{0}]에, 여러 개의 boolean 타입의 (마지막) 파라미터들이 존재합니다.
|
||||
pojoMethodMapping.duplicateMessageParam=OnMessage로 annotate된 클래스 [{1}]의 메소드 [{0}]에 여러 개의 메시지 파라미터들이 존재합니다.
|
||||
pojoMethodMapping.duplicatePongMessageParam=OnMessage로 annotate된 클래스 [{1}]의 메소드 [{0}]에 여러 개의 PongMessage 파라미터들이 존재합니다.
|
||||
pojoMethodMapping.duplicateSessionParam=OnMessage로 annotate된 클래스 [{1}]의 메소드 [{0}]에 여러 개의 세션 파라미터들이 존재합니다.
|
||||
pojoMethodMapping.invalidDecoder=지정된 타입 [{0}]의 디코더를 생성할 수 없었습니다.
|
||||
pojoMethodMapping.methodNotPublic=Annotate된 메소드 [{0}]이(가) public 메소드가 아닙니다.
|
||||
pojoMethodMapping.noDecoder=클래스 [{1}]에서, OnMessage로 annotate된 메소드 [{0}]에 존재하는 메시지 파라미터들을 위한 디코더를 찾을 수 없습니다.
|
||||
pojoMethodMapping.noPayload=OnMessage로 annotate된 클래스 [{1}]의 메소드 [{0}]에, payload 파라미터가 존재하지 않습니다.
|
||||
pojoMethodMapping.onErrorNoThrowable=OnError로 annotate된 클래스 [{1}]의 메소드 [{0}]에 Throwable 파라미터가 없습니다.
|
||||
pojoMethodMapping.paramWithoutAnnotation=@PathParam으로 annotate 되지 않은 클래스 [{2}]의 메소드 [{1}]에서, 타입이 [{0}]인 파라미터가 발견되었습니다.
|
||||
pojoMethodMapping.partialInputStream=OnMessage로 annotate된 클래스 [{1}]의 메소드 [{0}]에 유효하지 않은 InputStream과 boolean 파라미터들이 존재합니다.
|
||||
pojoMethodMapping.partialObject=OnMessage로 annotate된 클래스 [{1}]의 메소드 [{0}]에 유효하지 않은 Object와 boolean 파라미터들이 존재합니다.
|
||||
pojoMethodMapping.partialPong=OnMessage로 annotate된 클래스 [{1}]의 메소드 [{0}]에 존재하는 PongMessage와 boolean 파라미터들은 유효하지 않습니다.
|
||||
pojoMethodMapping.partialReader=OnMessage로 annotate된 클래스 [{1}]의 메소드 [{0}]에, 유효하지 않은 Reader와 boolean 파라미터들이 존재합니다.
|
||||
pojoMethodMapping.pongWithPayload=OnMessage로 annotate된 클래스 [{1}]의 메소드 [{0}]에 존재하는 PongMessage와 Message 파라미터들은 유효하지 않습니다.
|
||||
|
||||
pojoPathParam.wrongType=타입 [{0}]은(는) 경로 파라미터로서 허용되지 않습니다. @PathParam으로 annotate된 파라미터들은 오직 문자열들, 또는 자바 원시타입들 또는 그것들의 박싱된 버전들이어야 합니다.
|
||||
@@ -0,0 +1,16 @@
|
||||
# Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
# contributor license agreements. See the NOTICE file distributed with
|
||||
# this work for additional information regarding copyright ownership.
|
||||
# The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
# (the "License"); you may not use this file except in compliance with
|
||||
# the License. You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
pojoMethodMapping.duplicateAnnotation=Аналогичная аннотация [{0}] существует в классе [{1}]
|
||||
@@ -0,0 +1,35 @@
|
||||
# Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
# contributor license agreements. See the NOTICE file distributed with
|
||||
# this work for additional information regarding copyright ownership.
|
||||
# The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
# (the "License"); you may not use this file except in compliance with
|
||||
# the License. You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
pojoEndpointBase.onCloseFail=):无法为类型为[{0}]的POJO调用POJO端点的onClose方法
|
||||
pojoEndpointBase.onError=在错误发生后,没有为[{0}]配置错误处理
|
||||
pojoEndpointBase.onOpenFail=无法为类型为[{0}]的POJO调用POJO端点的onOpen方法
|
||||
|
||||
pojoEndpointServer.getPojoInstanceFail=创建类型为 [{0}] 的 POJO 实例失败
|
||||
|
||||
pojoMessageHandlerWhole.decodeIoFail=解码消息时出现IO错误
|
||||
|
||||
pojoMethodMapping.duplicateAnnotation=类[{1}]上存在的重复注释[{0}]
|
||||
pojoMethodMapping.duplicateLastParam=用OnMessage注释的类[{1}]的方法[{0}]上存在多个布尔参数(最后的)
|
||||
pojoMethodMapping.duplicatePongMessageParam=使用OnMessage注释的类[{1}]的方法[{0}]上存在多个PongMessage参数
|
||||
pojoMethodMapping.invalidDecoder=这个特定类型的解码器[{0}]无法被实例化
|
||||
pojoMethodMapping.methodNotPublic=注解方法[{0}]不为公共方法
|
||||
pojoMethodMapping.noDecoder=在用onMessage注释的类[{1}]的方法[{0}]上找不到消息参数的解码器。
|
||||
pojoMethodMapping.onErrorNoThrowable=):类[{1}]的方法[{0}]上不存在用OnError注释的抛出参数
|
||||
pojoMethodMapping.paramWithoutAnnotation=在类[{2}]的方法[{1}]上找到了类型为[{0}]的参数,该方法没有@PathParam 注释
|
||||
pojoMethodMapping.partialObject=用onMessage注释的类[{1}]的方法[{0}]中存在无效的对象和布尔参数
|
||||
pojoMethodMapping.pongWithPayload=类[{1}]的方法[{0}]中存在无效的PongMessage 和消息参数,该方法是用onMessage注释的
|
||||
|
||||
pojoPathParam.wrongType=不允许将类型[{0}]作为路径参数。用@PathParam注释的参数只能是字符串、Java原语或盒装版本。
|
||||
156
java/org/apache/tomcat/websocket/pojo/PojoEndpointBase.java
Normal file
156
java/org/apache/tomcat/websocket/pojo/PojoEndpointBase.java
Normal file
@@ -0,0 +1,156 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.apache.tomcat.websocket.pojo;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import javax.websocket.CloseReason;
|
||||
import javax.websocket.Endpoint;
|
||||
import javax.websocket.EndpointConfig;
|
||||
import javax.websocket.MessageHandler;
|
||||
import javax.websocket.Session;
|
||||
|
||||
import org.apache.juli.logging.Log;
|
||||
import org.apache.juli.logging.LogFactory;
|
||||
import org.apache.tomcat.util.ExceptionUtils;
|
||||
import org.apache.tomcat.util.res.StringManager;
|
||||
|
||||
/**
|
||||
* Base implementation (client and server have different concrete
|
||||
* implementations) of the wrapper that converts a POJO instance into a
|
||||
* WebSocket endpoint instance.
|
||||
*/
|
||||
public abstract class PojoEndpointBase extends Endpoint {
|
||||
|
||||
private final Log log = LogFactory.getLog(PojoEndpointBase.class); // must not be static
|
||||
private static final StringManager sm = StringManager.getManager(PojoEndpointBase.class);
|
||||
|
||||
private Object pojo;
|
||||
private Map<String,String> pathParameters;
|
||||
private PojoMethodMapping methodMapping;
|
||||
|
||||
|
||||
protected final void doOnOpen(Session session, EndpointConfig config) {
|
||||
PojoMethodMapping methodMapping = getMethodMapping();
|
||||
Object pojo = getPojo();
|
||||
Map<String,String> pathParameters = getPathParameters();
|
||||
|
||||
// Add message handlers before calling onOpen since that may trigger a
|
||||
// message which in turn could trigger a response and/or close the
|
||||
// session
|
||||
for (MessageHandler mh : methodMapping.getMessageHandlers(pojo,
|
||||
pathParameters, session, config)) {
|
||||
session.addMessageHandler(mh);
|
||||
}
|
||||
|
||||
if (methodMapping.getOnOpen() != null) {
|
||||
try {
|
||||
methodMapping.getOnOpen().invoke(pojo,
|
||||
methodMapping.getOnOpenArgs(
|
||||
pathParameters, session, config));
|
||||
|
||||
} catch (IllegalAccessException e) {
|
||||
// Reflection related problems
|
||||
log.error(sm.getString(
|
||||
"pojoEndpointBase.onOpenFail",
|
||||
pojo.getClass().getName()), e);
|
||||
handleOnOpenOrCloseError(session, e);
|
||||
} catch (InvocationTargetException e) {
|
||||
Throwable cause = e.getCause();
|
||||
handleOnOpenOrCloseError(session, cause);
|
||||
} catch (Throwable t) {
|
||||
handleOnOpenOrCloseError(session, t);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private void handleOnOpenOrCloseError(Session session, Throwable t) {
|
||||
// If really fatal - re-throw
|
||||
ExceptionUtils.handleThrowable(t);
|
||||
|
||||
// Trigger the error handler and close the session
|
||||
onError(session, t);
|
||||
try {
|
||||
session.close();
|
||||
} catch (IOException ioe) {
|
||||
log.warn(sm.getString("pojoEndpointBase.closeSessionFail"), ioe);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public final void onClose(Session session, CloseReason closeReason) {
|
||||
|
||||
if (methodMapping.getOnClose() != null) {
|
||||
try {
|
||||
methodMapping.getOnClose().invoke(pojo,
|
||||
methodMapping.getOnCloseArgs(pathParameters, session, closeReason));
|
||||
} catch (Throwable t) {
|
||||
log.error(sm.getString("pojoEndpointBase.onCloseFail",
|
||||
pojo.getClass().getName()), t);
|
||||
handleOnOpenOrCloseError(session, t);
|
||||
}
|
||||
}
|
||||
|
||||
// Trigger the destroy method for any associated decoders
|
||||
Set<MessageHandler> messageHandlers = session.getMessageHandlers();
|
||||
for (MessageHandler messageHandler : messageHandlers) {
|
||||
if (messageHandler instanceof PojoMessageHandlerWholeBase<?>) {
|
||||
((PojoMessageHandlerWholeBase<?>) messageHandler).onClose();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public final void onError(Session session, Throwable throwable) {
|
||||
|
||||
if (methodMapping.getOnError() == null) {
|
||||
log.error(sm.getString("pojoEndpointBase.onError",
|
||||
pojo.getClass().getName()), throwable);
|
||||
} else {
|
||||
try {
|
||||
methodMapping.getOnError().invoke(
|
||||
pojo,
|
||||
methodMapping.getOnErrorArgs(pathParameters, session,
|
||||
throwable));
|
||||
} catch (Throwable t) {
|
||||
ExceptionUtils.handleThrowable(t);
|
||||
log.error(sm.getString("pojoEndpointBase.onErrorFail",
|
||||
pojo.getClass().getName()), t);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected Object getPojo() { return pojo; }
|
||||
protected void setPojo(Object pojo) { this.pojo = pojo; }
|
||||
|
||||
|
||||
protected Map<String,String> getPathParameters() { return pathParameters; }
|
||||
protected void setPathParameters(Map<String,String> pathParameters) {
|
||||
this.pathParameters = pathParameters;
|
||||
}
|
||||
|
||||
|
||||
protected PojoMethodMapping getMethodMapping() { return methodMapping; }
|
||||
protected void setMethodMapping(PojoMethodMapping methodMapping) {
|
||||
this.methodMapping = methodMapping;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.apache.tomcat.websocket.pojo;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import javax.websocket.Decoder;
|
||||
import javax.websocket.DeploymentException;
|
||||
import javax.websocket.EndpointConfig;
|
||||
import javax.websocket.Session;
|
||||
|
||||
|
||||
/**
|
||||
* Wrapper class for instances of POJOs annotated with
|
||||
* {@link javax.websocket.ClientEndpoint} so they appear as standard
|
||||
* {@link javax.websocket.Endpoint} instances.
|
||||
*/
|
||||
public class PojoEndpointClient extends PojoEndpointBase {
|
||||
|
||||
public PojoEndpointClient(Object pojo,
|
||||
List<Class<? extends Decoder>> decoders) throws DeploymentException {
|
||||
setPojo(pojo);
|
||||
setMethodMapping(
|
||||
new PojoMethodMapping(pojo.getClass(), decoders, null));
|
||||
setPathParameters(Collections.<String,String>emptyMap());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onOpen(Session session, EndpointConfig config) {
|
||||
doOnOpen(session, config);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.apache.tomcat.websocket.pojo;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import javax.websocket.EndpointConfig;
|
||||
import javax.websocket.Session;
|
||||
import javax.websocket.server.ServerEndpointConfig;
|
||||
|
||||
import org.apache.tomcat.util.res.StringManager;
|
||||
|
||||
/**
|
||||
* Wrapper class for instances of POJOs annotated with
|
||||
* {@link javax.websocket.server.ServerEndpoint} so they appear as standard
|
||||
* {@link javax.websocket.Endpoint} instances.
|
||||
*/
|
||||
public class PojoEndpointServer extends PojoEndpointBase {
|
||||
|
||||
private static final StringManager sm =
|
||||
StringManager.getManager(PojoEndpointServer.class);
|
||||
|
||||
@Override
|
||||
public void onOpen(Session session, EndpointConfig endpointConfig) {
|
||||
|
||||
ServerEndpointConfig sec = (ServerEndpointConfig) endpointConfig;
|
||||
|
||||
Object pojo;
|
||||
try {
|
||||
pojo = sec.getConfigurator().getEndpointInstance(
|
||||
sec.getEndpointClass());
|
||||
} catch (InstantiationException e) {
|
||||
throw new IllegalArgumentException(sm.getString(
|
||||
"pojoEndpointServer.getPojoInstanceFail",
|
||||
sec.getEndpointClass().getName()), e);
|
||||
}
|
||||
setPojo(pojo);
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
Map<String,String> pathParameters =
|
||||
(Map<String, String>) sec.getUserProperties().get(
|
||||
Constants.POJO_PATH_PARAM_KEY);
|
||||
setPathParameters(pathParameters);
|
||||
|
||||
PojoMethodMapping methodMapping =
|
||||
(PojoMethodMapping) sec.getUserProperties().get(
|
||||
Constants.POJO_METHOD_MAPPING_KEY);
|
||||
setMethodMapping(methodMapping);
|
||||
|
||||
doOnOpen(session, endpointConfig);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,122 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.apache.tomcat.websocket.pojo;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.lang.reflect.Method;
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
import javax.websocket.EncodeException;
|
||||
import javax.websocket.MessageHandler;
|
||||
import javax.websocket.RemoteEndpoint;
|
||||
import javax.websocket.Session;
|
||||
|
||||
import org.apache.tomcat.util.ExceptionUtils;
|
||||
import org.apache.tomcat.websocket.WrappedMessageHandler;
|
||||
|
||||
/**
|
||||
* Common implementation code for the POJO message handlers.
|
||||
*
|
||||
* @param <T> The type of message to handle
|
||||
*/
|
||||
public abstract class PojoMessageHandlerBase<T>
|
||||
implements WrappedMessageHandler {
|
||||
|
||||
protected final Object pojo;
|
||||
protected final Method method;
|
||||
protected final Session session;
|
||||
protected final Object[] params;
|
||||
protected final int indexPayload;
|
||||
protected final boolean convert;
|
||||
protected final int indexSession;
|
||||
protected final long maxMessageSize;
|
||||
|
||||
public PojoMessageHandlerBase(Object pojo, Method method,
|
||||
Session session, Object[] params, int indexPayload, boolean convert,
|
||||
int indexSession, long maxMessageSize) {
|
||||
this.pojo = pojo;
|
||||
this.method = method;
|
||||
// TODO: The method should already be accessible here but the following
|
||||
// code seems to be necessary in some as yet not fully understood cases.
|
||||
try {
|
||||
this.method.setAccessible(true);
|
||||
} catch (Exception e) {
|
||||
// It is better to make sure the method is accessible, but
|
||||
// ignore exceptions and hope for the best
|
||||
}
|
||||
this.session = session;
|
||||
this.params = params;
|
||||
this.indexPayload = indexPayload;
|
||||
this.convert = convert;
|
||||
this.indexSession = indexSession;
|
||||
this.maxMessageSize = maxMessageSize;
|
||||
}
|
||||
|
||||
|
||||
protected final void processResult(Object result) {
|
||||
if (result == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
RemoteEndpoint.Basic remoteEndpoint = session.getBasicRemote();
|
||||
try {
|
||||
if (result instanceof String) {
|
||||
remoteEndpoint.sendText((String) result);
|
||||
} else if (result instanceof ByteBuffer) {
|
||||
remoteEndpoint.sendBinary((ByteBuffer) result);
|
||||
} else if (result instanceof byte[]) {
|
||||
remoteEndpoint.sendBinary(ByteBuffer.wrap((byte[]) result));
|
||||
} else {
|
||||
remoteEndpoint.sendObject(result);
|
||||
}
|
||||
} catch (IOException | EncodeException ioe) {
|
||||
throw new IllegalStateException(ioe);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Expose the POJO if it is a message handler so the Session is able to
|
||||
* match requests to remove handlers if the original handler has been
|
||||
* wrapped.
|
||||
*/
|
||||
@Override
|
||||
public final MessageHandler getWrappedHandler() {
|
||||
if (pojo instanceof MessageHandler) {
|
||||
return (MessageHandler) pojo;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public final long getMaxMessageSize() {
|
||||
return maxMessageSize;
|
||||
}
|
||||
|
||||
|
||||
protected final void handlePojoMethodException(Throwable t) {
|
||||
t = ExceptionUtils.unwrapInvocationTargetException(t);
|
||||
ExceptionUtils.handleThrowable(t);
|
||||
if (t instanceof RuntimeException) {
|
||||
throw (RuntimeException) t;
|
||||
} else {
|
||||
throw new RuntimeException(t.getMessage(), t);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.apache.tomcat.websocket.pojo;
|
||||
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Method;
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
import javax.websocket.DecodeException;
|
||||
import javax.websocket.MessageHandler;
|
||||
import javax.websocket.Session;
|
||||
|
||||
import org.apache.tomcat.websocket.WsSession;
|
||||
|
||||
/**
|
||||
* Common implementation code for the POJO partial message handlers. All
|
||||
* the real work is done in this class and in the superclass.
|
||||
*
|
||||
* @param <T> The type of message to handle
|
||||
*/
|
||||
public abstract class PojoMessageHandlerPartialBase<T>
|
||||
extends PojoMessageHandlerBase<T> implements MessageHandler.Partial<T> {
|
||||
|
||||
private final int indexBoolean;
|
||||
|
||||
public PojoMessageHandlerPartialBase(Object pojo, Method method,
|
||||
Session session, Object[] params, int indexPayload,
|
||||
boolean convert, int indexBoolean, int indexSession,
|
||||
long maxMessageSize) {
|
||||
super(pojo, method, session, params, indexPayload, convert,
|
||||
indexSession, maxMessageSize);
|
||||
this.indexBoolean = indexBoolean;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public final void onMessage(T message, boolean last) {
|
||||
if (params.length == 1 && params[0] instanceof DecodeException) {
|
||||
((WsSession) session).getLocal().onError(session,
|
||||
(DecodeException) params[0]);
|
||||
return;
|
||||
}
|
||||
Object[] parameters = params.clone();
|
||||
if (indexBoolean != -1) {
|
||||
parameters[indexBoolean] = Boolean.valueOf(last);
|
||||
}
|
||||
if (indexSession != -1) {
|
||||
parameters[indexSession] = session;
|
||||
}
|
||||
if (convert) {
|
||||
parameters[indexPayload] = ((ByteBuffer) message).array();
|
||||
} else {
|
||||
parameters[indexPayload] = message;
|
||||
}
|
||||
Object result = null;
|
||||
try {
|
||||
result = method.invoke(pojo, parameters);
|
||||
} catch (IllegalAccessException | InvocationTargetException e) {
|
||||
handlePojoMethodException(e);
|
||||
}
|
||||
processResult(result);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.apache.tomcat.websocket.pojo;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
import javax.websocket.Session;
|
||||
|
||||
/**
|
||||
* ByteBuffer specific concrete implementation for handling partial messages.
|
||||
*/
|
||||
public class PojoMessageHandlerPartialBinary
|
||||
extends PojoMessageHandlerPartialBase<ByteBuffer> {
|
||||
|
||||
public PojoMessageHandlerPartialBinary(Object pojo, Method method,
|
||||
Session session, Object[] params, int indexPayload, boolean convert,
|
||||
int indexBoolean, int indexSession, long maxMessageSize) {
|
||||
super(pojo, method, session, params, indexPayload, convert, indexBoolean,
|
||||
indexSession, maxMessageSize);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.apache.tomcat.websocket.pojo;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
|
||||
import javax.websocket.Session;
|
||||
|
||||
/**
|
||||
* Text specific concrete implementation for handling partial messages.
|
||||
*/
|
||||
public class PojoMessageHandlerPartialText
|
||||
extends PojoMessageHandlerPartialBase<String> {
|
||||
|
||||
public PojoMessageHandlerPartialText(Object pojo, Method method,
|
||||
Session session, Object[] params, int indexPayload, boolean convert,
|
||||
int indexBoolean, int indexSession, long maxMessageSize) {
|
||||
super(pojo, method, session, params, indexPayload, convert, indexBoolean,
|
||||
indexSession, maxMessageSize);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,94 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.apache.tomcat.websocket.pojo;
|
||||
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Method;
|
||||
|
||||
import javax.websocket.DecodeException;
|
||||
import javax.websocket.MessageHandler;
|
||||
import javax.websocket.Session;
|
||||
|
||||
import org.apache.tomcat.websocket.WsSession;
|
||||
|
||||
/**
|
||||
* Common implementation code for the POJO whole message handlers. All the real
|
||||
* work is done in this class and in the superclass.
|
||||
*
|
||||
* @param <T> The type of message to handle
|
||||
*/
|
||||
public abstract class PojoMessageHandlerWholeBase<T>
|
||||
extends PojoMessageHandlerBase<T> implements MessageHandler.Whole<T> {
|
||||
|
||||
public PojoMessageHandlerWholeBase(Object pojo, Method method,
|
||||
Session session, Object[] params, int indexPayload,
|
||||
boolean convert, int indexSession, long maxMessageSize) {
|
||||
super(pojo, method, session, params, indexPayload, convert,
|
||||
indexSession, maxMessageSize);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public final void onMessage(T message) {
|
||||
|
||||
if (params.length == 1 && params[0] instanceof DecodeException) {
|
||||
((WsSession) session).getLocal().onError(session,
|
||||
(DecodeException) params[0]);
|
||||
return;
|
||||
}
|
||||
|
||||
// Can this message be decoded?
|
||||
Object payload;
|
||||
try {
|
||||
payload = decode(message);
|
||||
} catch (DecodeException de) {
|
||||
((WsSession) session).getLocal().onError(session, de);
|
||||
return;
|
||||
}
|
||||
|
||||
if (payload == null) {
|
||||
// Not decoded. Convert if required.
|
||||
if (convert) {
|
||||
payload = convert(message);
|
||||
} else {
|
||||
payload = message;
|
||||
}
|
||||
}
|
||||
|
||||
Object[] parameters = params.clone();
|
||||
if (indexSession != -1) {
|
||||
parameters[indexSession] = session;
|
||||
}
|
||||
parameters[indexPayload] = payload;
|
||||
|
||||
Object result = null;
|
||||
try {
|
||||
result = method.invoke(pojo, parameters);
|
||||
} catch (IllegalAccessException | InvocationTargetException e) {
|
||||
handlePojoMethodException(e);
|
||||
}
|
||||
processResult(result);
|
||||
}
|
||||
|
||||
protected Object convert(T message) {
|
||||
return message;
|
||||
}
|
||||
|
||||
|
||||
protected abstract Object decode(T message) throws DecodeException;
|
||||
protected abstract void onClose();
|
||||
}
|
||||
@@ -0,0 +1,131 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.apache.tomcat.websocket.pojo;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.IOException;
|
||||
import java.lang.reflect.Method;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import javax.websocket.DecodeException;
|
||||
import javax.websocket.Decoder;
|
||||
import javax.websocket.Decoder.Binary;
|
||||
import javax.websocket.Decoder.BinaryStream;
|
||||
import javax.websocket.EndpointConfig;
|
||||
import javax.websocket.Session;
|
||||
|
||||
import org.apache.tomcat.util.res.StringManager;
|
||||
|
||||
/**
|
||||
* ByteBuffer specific concrete implementation for handling whole messages.
|
||||
*/
|
||||
public class PojoMessageHandlerWholeBinary
|
||||
extends PojoMessageHandlerWholeBase<ByteBuffer> {
|
||||
|
||||
private static final StringManager sm =
|
||||
StringManager.getManager(PojoMessageHandlerWholeBinary.class);
|
||||
|
||||
private final List<Decoder> decoders = new ArrayList<>();
|
||||
|
||||
private final boolean isForInputStream;
|
||||
|
||||
public PojoMessageHandlerWholeBinary(Object pojo, Method method,
|
||||
Session session, EndpointConfig config,
|
||||
List<Class<? extends Decoder>> decoderClazzes, Object[] params,
|
||||
int indexPayload, boolean convert, int indexSession,
|
||||
boolean isForInputStream, long maxMessageSize) {
|
||||
super(pojo, method, session, params, indexPayload, convert,
|
||||
indexSession, maxMessageSize);
|
||||
|
||||
// Update binary text size handled by session
|
||||
if (maxMessageSize > -1 && maxMessageSize > session.getMaxBinaryMessageBufferSize()) {
|
||||
if (maxMessageSize > Integer.MAX_VALUE) {
|
||||
throw new IllegalArgumentException(sm.getString(
|
||||
"pojoMessageHandlerWhole.maxBufferSize"));
|
||||
}
|
||||
session.setMaxBinaryMessageBufferSize((int) maxMessageSize);
|
||||
}
|
||||
|
||||
try {
|
||||
if (decoderClazzes != null) {
|
||||
for (Class<? extends Decoder> decoderClazz : decoderClazzes) {
|
||||
if (Binary.class.isAssignableFrom(decoderClazz)) {
|
||||
Binary<?> decoder = (Binary<?>) decoderClazz.getConstructor().newInstance();
|
||||
decoder.init(config);
|
||||
decoders.add(decoder);
|
||||
} else if (BinaryStream.class.isAssignableFrom(
|
||||
decoderClazz)) {
|
||||
BinaryStream<?> decoder = (BinaryStream<?>)
|
||||
decoderClazz.getConstructor().newInstance();
|
||||
decoder.init(config);
|
||||
decoders.add(decoder);
|
||||
} else {
|
||||
// Text decoder - ignore it
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (ReflectiveOperationException e) {
|
||||
throw new IllegalArgumentException(e);
|
||||
}
|
||||
this.isForInputStream = isForInputStream;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
protected Object decode(ByteBuffer message) throws DecodeException {
|
||||
for (Decoder decoder : decoders) {
|
||||
if (decoder instanceof Binary) {
|
||||
if (((Binary<?>) decoder).willDecode(message)) {
|
||||
return ((Binary<?>) decoder).decode(message);
|
||||
}
|
||||
} else {
|
||||
byte[] array = new byte[message.limit() - message.position()];
|
||||
message.get(array);
|
||||
ByteArrayInputStream bais = new ByteArrayInputStream(array);
|
||||
try {
|
||||
return ((BinaryStream<?>) decoder).decode(bais);
|
||||
} catch (IOException ioe) {
|
||||
throw new DecodeException(message, sm.getString(
|
||||
"pojoMessageHandlerWhole.decodeIoFail"), ioe);
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
protected Object convert(ByteBuffer message) {
|
||||
byte[] array = new byte[message.remaining()];
|
||||
message.get(array);
|
||||
if (isForInputStream) {
|
||||
return new ByteArrayInputStream(array);
|
||||
} else {
|
||||
return array;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
protected void onClose() {
|
||||
for (Decoder decoder : decoders) {
|
||||
decoder.destroy();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.apache.tomcat.websocket.pojo;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
|
||||
import javax.websocket.PongMessage;
|
||||
import javax.websocket.Session;
|
||||
|
||||
/**
|
||||
* PongMessage specific concrete implementation for handling whole messages.
|
||||
*/
|
||||
public class PojoMessageHandlerWholePong
|
||||
extends PojoMessageHandlerWholeBase<PongMessage> {
|
||||
|
||||
public PojoMessageHandlerWholePong(Object pojo, Method method,
|
||||
Session session, Object[] params, int indexPayload, boolean convert,
|
||||
int indexSession) {
|
||||
super(pojo, method, session, params, indexPayload, convert,
|
||||
indexSession, -1);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Object decode(PongMessage message) {
|
||||
// Never decoded
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
protected void onClose() {
|
||||
// NO-OP
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,136 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.apache.tomcat.websocket.pojo;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.StringReader;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import javax.websocket.DecodeException;
|
||||
import javax.websocket.Decoder;
|
||||
import javax.websocket.Decoder.Text;
|
||||
import javax.websocket.Decoder.TextStream;
|
||||
import javax.websocket.EndpointConfig;
|
||||
import javax.websocket.Session;
|
||||
|
||||
import org.apache.tomcat.util.res.StringManager;
|
||||
import org.apache.tomcat.websocket.Util;
|
||||
|
||||
|
||||
/**
|
||||
* Text specific concrete implementation for handling whole messages.
|
||||
*/
|
||||
public class PojoMessageHandlerWholeText
|
||||
extends PojoMessageHandlerWholeBase<String> {
|
||||
|
||||
private static final StringManager sm =
|
||||
StringManager.getManager(PojoMessageHandlerWholeText.class);
|
||||
|
||||
private final List<Decoder> decoders = new ArrayList<>();
|
||||
private final Class<?> primitiveType;
|
||||
|
||||
public PojoMessageHandlerWholeText(Object pojo, Method method,
|
||||
Session session, EndpointConfig config,
|
||||
List<Class<? extends Decoder>> decoderClazzes, Object[] params,
|
||||
int indexPayload, boolean convert, int indexSession,
|
||||
long maxMessageSize) {
|
||||
super(pojo, method, session, params, indexPayload, convert,
|
||||
indexSession, maxMessageSize);
|
||||
|
||||
// Update max text size handled by session
|
||||
if (maxMessageSize > -1 && maxMessageSize > session.getMaxTextMessageBufferSize()) {
|
||||
if (maxMessageSize > Integer.MAX_VALUE) {
|
||||
throw new IllegalArgumentException(sm.getString(
|
||||
"pojoMessageHandlerWhole.maxBufferSize"));
|
||||
}
|
||||
session.setMaxTextMessageBufferSize((int) maxMessageSize);
|
||||
}
|
||||
|
||||
// Check for primitives
|
||||
Class<?> type = method.getParameterTypes()[indexPayload];
|
||||
if (Util.isPrimitive(type)) {
|
||||
primitiveType = type;
|
||||
return;
|
||||
} else {
|
||||
primitiveType = null;
|
||||
}
|
||||
|
||||
try {
|
||||
if (decoderClazzes != null) {
|
||||
for (Class<? extends Decoder> decoderClazz : decoderClazzes) {
|
||||
if (Text.class.isAssignableFrom(decoderClazz)) {
|
||||
Text<?> decoder = (Text<?>) decoderClazz.getConstructor().newInstance();
|
||||
decoder.init(config);
|
||||
decoders.add(decoder);
|
||||
} else if (TextStream.class.isAssignableFrom(
|
||||
decoderClazz)) {
|
||||
TextStream<?> decoder =
|
||||
(TextStream<?>) decoderClazz.getConstructor().newInstance();
|
||||
decoder.init(config);
|
||||
decoders.add(decoder);
|
||||
} else {
|
||||
// Binary decoder - ignore it
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (ReflectiveOperationException e) {
|
||||
throw new IllegalArgumentException(e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
protected Object decode(String message) throws DecodeException {
|
||||
// Handle primitives
|
||||
if (primitiveType != null) {
|
||||
return Util.coerceToType(primitiveType, message);
|
||||
}
|
||||
// Handle full decoders
|
||||
for (Decoder decoder : decoders) {
|
||||
if (decoder instanceof Text) {
|
||||
if (((Text<?>) decoder).willDecode(message)) {
|
||||
return ((Text<?>) decoder).decode(message);
|
||||
}
|
||||
} else {
|
||||
StringReader r = new StringReader(message);
|
||||
try {
|
||||
return ((TextStream<?>) decoder).decode(r);
|
||||
} catch (IOException ioe) {
|
||||
throw new DecodeException(message, sm.getString(
|
||||
"pojoMessageHandlerWhole.decodeIoFail"), ioe);
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
protected Object convert(String message) {
|
||||
return new StringReader(message);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
protected void onClose() {
|
||||
for (Decoder decoder : decoders) {
|
||||
decoder.destroy();
|
||||
}
|
||||
}
|
||||
}
|
||||
725
java/org/apache/tomcat/websocket/pojo/PojoMethodMapping.java
Normal file
725
java/org/apache/tomcat/websocket/pojo/PojoMethodMapping.java
Normal file
@@ -0,0 +1,725 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.apache.tomcat.websocket.pojo;
|
||||
|
||||
import java.io.InputStream;
|
||||
import java.io.Reader;
|
||||
import java.lang.annotation.Annotation;
|
||||
import java.lang.reflect.Method;
|
||||
import java.lang.reflect.Modifier;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import javax.websocket.CloseReason;
|
||||
import javax.websocket.DecodeException;
|
||||
import javax.websocket.Decoder;
|
||||
import javax.websocket.DeploymentException;
|
||||
import javax.websocket.EndpointConfig;
|
||||
import javax.websocket.MessageHandler;
|
||||
import javax.websocket.OnClose;
|
||||
import javax.websocket.OnError;
|
||||
import javax.websocket.OnMessage;
|
||||
import javax.websocket.OnOpen;
|
||||
import javax.websocket.PongMessage;
|
||||
import javax.websocket.Session;
|
||||
import javax.websocket.server.PathParam;
|
||||
|
||||
import org.apache.tomcat.util.res.StringManager;
|
||||
import org.apache.tomcat.websocket.DecoderEntry;
|
||||
import org.apache.tomcat.websocket.Util;
|
||||
import org.apache.tomcat.websocket.Util.DecoderMatch;
|
||||
|
||||
/**
|
||||
* For a POJO class annotated with
|
||||
* {@link javax.websocket.server.ServerEndpoint}, an instance of this class
|
||||
* creates and caches the method handler, method information and parameter
|
||||
* information for the onXXX calls.
|
||||
*/
|
||||
public class PojoMethodMapping {
|
||||
|
||||
private static final StringManager sm =
|
||||
StringManager.getManager(PojoMethodMapping.class);
|
||||
|
||||
private final Method onOpen;
|
||||
private final Method onClose;
|
||||
private final Method onError;
|
||||
private final PojoPathParam[] onOpenParams;
|
||||
private final PojoPathParam[] onCloseParams;
|
||||
private final PojoPathParam[] onErrorParams;
|
||||
private final List<MessageHandlerInfo> onMessage = new ArrayList<>();
|
||||
private final String wsPath;
|
||||
|
||||
|
||||
public PojoMethodMapping(Class<?> clazzPojo,
|
||||
List<Class<? extends Decoder>> decoderClazzes, String wsPath)
|
||||
throws DeploymentException {
|
||||
|
||||
this.wsPath = wsPath;
|
||||
|
||||
List<DecoderEntry> decoders = Util.getDecoders(decoderClazzes);
|
||||
Method open = null;
|
||||
Method close = null;
|
||||
Method error = null;
|
||||
Method[] clazzPojoMethods = null;
|
||||
Class<?> currentClazz = clazzPojo;
|
||||
while (!currentClazz.equals(Object.class)) {
|
||||
Method[] currentClazzMethods = currentClazz.getDeclaredMethods();
|
||||
if (currentClazz == clazzPojo) {
|
||||
clazzPojoMethods = currentClazzMethods;
|
||||
}
|
||||
for (Method method : currentClazzMethods) {
|
||||
if (method.isSynthetic()) {
|
||||
// Skip all synthetic methods.
|
||||
// They may have copies of annotations from methods we are
|
||||
// interested in and they will use the wrong parameter type
|
||||
// (they always use Object) so we can't used them here.
|
||||
continue;
|
||||
}
|
||||
if (method.getAnnotation(OnOpen.class) != null) {
|
||||
checkPublic(method);
|
||||
if (open == null) {
|
||||
open = method;
|
||||
} else {
|
||||
if (currentClazz == clazzPojo ||
|
||||
!isMethodOverride(open, method)) {
|
||||
// Duplicate annotation
|
||||
throw new DeploymentException(sm.getString(
|
||||
"pojoMethodMapping.duplicateAnnotation",
|
||||
OnOpen.class, currentClazz));
|
||||
}
|
||||
}
|
||||
} else if (method.getAnnotation(OnClose.class) != null) {
|
||||
checkPublic(method);
|
||||
if (close == null) {
|
||||
close = method;
|
||||
} else {
|
||||
if (currentClazz == clazzPojo ||
|
||||
!isMethodOverride(close, method)) {
|
||||
// Duplicate annotation
|
||||
throw new DeploymentException(sm.getString(
|
||||
"pojoMethodMapping.duplicateAnnotation",
|
||||
OnClose.class, currentClazz));
|
||||
}
|
||||
}
|
||||
} else if (method.getAnnotation(OnError.class) != null) {
|
||||
checkPublic(method);
|
||||
if (error == null) {
|
||||
error = method;
|
||||
} else {
|
||||
if (currentClazz == clazzPojo ||
|
||||
!isMethodOverride(error, method)) {
|
||||
// Duplicate annotation
|
||||
throw new DeploymentException(sm.getString(
|
||||
"pojoMethodMapping.duplicateAnnotation",
|
||||
OnError.class, currentClazz));
|
||||
}
|
||||
}
|
||||
} else if (method.getAnnotation(OnMessage.class) != null) {
|
||||
checkPublic(method);
|
||||
MessageHandlerInfo messageHandler = new MessageHandlerInfo(method, decoders);
|
||||
boolean found = false;
|
||||
for (MessageHandlerInfo otherMessageHandler : onMessage) {
|
||||
if (messageHandler.targetsSameWebSocketMessageType(otherMessageHandler)) {
|
||||
found = true;
|
||||
if (currentClazz == clazzPojo ||
|
||||
!isMethodOverride(messageHandler.m, otherMessageHandler.m)) {
|
||||
// Duplicate annotation
|
||||
throw new DeploymentException(sm.getString(
|
||||
"pojoMethodMapping.duplicateAnnotation",
|
||||
OnMessage.class, currentClazz));
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!found) {
|
||||
onMessage.add(messageHandler);
|
||||
}
|
||||
} else {
|
||||
// Method not annotated
|
||||
}
|
||||
}
|
||||
currentClazz = currentClazz.getSuperclass();
|
||||
}
|
||||
// If the methods are not on clazzPojo and they are overridden
|
||||
// by a non annotated method in clazzPojo, they should be ignored
|
||||
if (open != null && open.getDeclaringClass() != clazzPojo) {
|
||||
if (isOverridenWithoutAnnotation(clazzPojoMethods, open, OnOpen.class)) {
|
||||
open = null;
|
||||
}
|
||||
}
|
||||
if (close != null && close.getDeclaringClass() != clazzPojo) {
|
||||
if (isOverridenWithoutAnnotation(clazzPojoMethods, close, OnClose.class)) {
|
||||
close = null;
|
||||
}
|
||||
}
|
||||
if (error != null && error.getDeclaringClass() != clazzPojo) {
|
||||
if (isOverridenWithoutAnnotation(clazzPojoMethods, error, OnError.class)) {
|
||||
error = null;
|
||||
}
|
||||
}
|
||||
List<MessageHandlerInfo> overriddenOnMessage = new ArrayList<>();
|
||||
for (MessageHandlerInfo messageHandler : onMessage) {
|
||||
if (messageHandler.m.getDeclaringClass() != clazzPojo
|
||||
&& isOverridenWithoutAnnotation(clazzPojoMethods, messageHandler.m, OnMessage.class)) {
|
||||
overriddenOnMessage.add(messageHandler);
|
||||
}
|
||||
}
|
||||
for (MessageHandlerInfo messageHandler : overriddenOnMessage) {
|
||||
onMessage.remove(messageHandler);
|
||||
}
|
||||
this.onOpen = open;
|
||||
this.onClose = close;
|
||||
this.onError = error;
|
||||
onOpenParams = getPathParams(onOpen, MethodType.ON_OPEN);
|
||||
onCloseParams = getPathParams(onClose, MethodType.ON_CLOSE);
|
||||
onErrorParams = getPathParams(onError, MethodType.ON_ERROR);
|
||||
}
|
||||
|
||||
|
||||
private void checkPublic(Method m) throws DeploymentException {
|
||||
if (!Modifier.isPublic(m.getModifiers())) {
|
||||
throw new DeploymentException(sm.getString(
|
||||
"pojoMethodMapping.methodNotPublic", m.getName()));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private boolean isMethodOverride(Method method1, Method method2) {
|
||||
return method1.getName().equals(method2.getName())
|
||||
&& method1.getReturnType().equals(method2.getReturnType())
|
||||
&& Arrays.equals(method1.getParameterTypes(), method2.getParameterTypes());
|
||||
}
|
||||
|
||||
|
||||
private boolean isOverridenWithoutAnnotation(Method[] methods,
|
||||
Method superclazzMethod, Class<? extends Annotation> annotation) {
|
||||
for (Method method : methods) {
|
||||
if (isMethodOverride(method, superclazzMethod)
|
||||
&& (method.getAnnotation(annotation) == null)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
public String getWsPath() {
|
||||
return wsPath;
|
||||
}
|
||||
|
||||
|
||||
public Method getOnOpen() {
|
||||
return onOpen;
|
||||
}
|
||||
|
||||
|
||||
public Object[] getOnOpenArgs(Map<String,String> pathParameters,
|
||||
Session session, EndpointConfig config) throws DecodeException {
|
||||
return buildArgs(onOpenParams, pathParameters, session, config, null,
|
||||
null);
|
||||
}
|
||||
|
||||
|
||||
public Method getOnClose() {
|
||||
return onClose;
|
||||
}
|
||||
|
||||
|
||||
public Object[] getOnCloseArgs(Map<String,String> pathParameters,
|
||||
Session session, CloseReason closeReason) throws DecodeException {
|
||||
return buildArgs(onCloseParams, pathParameters, session, null, null,
|
||||
closeReason);
|
||||
}
|
||||
|
||||
|
||||
public Method getOnError() {
|
||||
return onError;
|
||||
}
|
||||
|
||||
|
||||
public Object[] getOnErrorArgs(Map<String,String> pathParameters,
|
||||
Session session, Throwable throwable) throws DecodeException {
|
||||
return buildArgs(onErrorParams, pathParameters, session, null,
|
||||
throwable, null);
|
||||
}
|
||||
|
||||
|
||||
public boolean hasMessageHandlers() {
|
||||
return !onMessage.isEmpty();
|
||||
}
|
||||
|
||||
|
||||
public Set<MessageHandler> getMessageHandlers(Object pojo,
|
||||
Map<String,String> pathParameters, Session session,
|
||||
EndpointConfig config) {
|
||||
Set<MessageHandler> result = new HashSet<>();
|
||||
for (MessageHandlerInfo messageMethod : onMessage) {
|
||||
result.addAll(messageMethod.getMessageHandlers(pojo, pathParameters,
|
||||
session, config));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
private static PojoPathParam[] getPathParams(Method m,
|
||||
MethodType methodType) throws DeploymentException {
|
||||
if (m == null) {
|
||||
return new PojoPathParam[0];
|
||||
}
|
||||
boolean foundThrowable = false;
|
||||
Class<?>[] types = m.getParameterTypes();
|
||||
Annotation[][] paramsAnnotations = m.getParameterAnnotations();
|
||||
PojoPathParam[] result = new PojoPathParam[types.length];
|
||||
for (int i = 0; i < types.length; i++) {
|
||||
Class<?> type = types[i];
|
||||
if (type.equals(Session.class)) {
|
||||
result[i] = new PojoPathParam(type, null);
|
||||
} else if (methodType == MethodType.ON_OPEN &&
|
||||
type.equals(EndpointConfig.class)) {
|
||||
result[i] = new PojoPathParam(type, null);
|
||||
} else if (methodType == MethodType.ON_ERROR
|
||||
&& type.equals(Throwable.class)) {
|
||||
foundThrowable = true;
|
||||
result[i] = new PojoPathParam(type, null);
|
||||
} else if (methodType == MethodType.ON_CLOSE &&
|
||||
type.equals(CloseReason.class)) {
|
||||
result[i] = new PojoPathParam(type, null);
|
||||
} else {
|
||||
Annotation[] paramAnnotations = paramsAnnotations[i];
|
||||
for (Annotation paramAnnotation : paramAnnotations) {
|
||||
if (paramAnnotation.annotationType().equals(
|
||||
PathParam.class)) {
|
||||
result[i] = new PojoPathParam(type,
|
||||
((PathParam) paramAnnotation).value());
|
||||
break;
|
||||
}
|
||||
}
|
||||
// Parameters without annotations are not permitted
|
||||
if (result[i] == null) {
|
||||
throw new DeploymentException(sm.getString(
|
||||
"pojoMethodMapping.paramWithoutAnnotation",
|
||||
type, m.getName(), m.getClass().getName()));
|
||||
}
|
||||
}
|
||||
}
|
||||
if (methodType == MethodType.ON_ERROR && !foundThrowable) {
|
||||
throw new DeploymentException(sm.getString(
|
||||
"pojoMethodMapping.onErrorNoThrowable",
|
||||
m.getName(), m.getDeclaringClass().getName()));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
private static Object[] buildArgs(PojoPathParam[] pathParams,
|
||||
Map<String,String> pathParameters, Session session,
|
||||
EndpointConfig config, Throwable throwable, CloseReason closeReason)
|
||||
throws DecodeException {
|
||||
Object[] result = new Object[pathParams.length];
|
||||
for (int i = 0; i < pathParams.length; i++) {
|
||||
Class<?> type = pathParams[i].getType();
|
||||
if (type.equals(Session.class)) {
|
||||
result[i] = session;
|
||||
} else if (type.equals(EndpointConfig.class)) {
|
||||
result[i] = config;
|
||||
} else if (type.equals(Throwable.class)) {
|
||||
result[i] = throwable;
|
||||
} else if (type.equals(CloseReason.class)) {
|
||||
result[i] = closeReason;
|
||||
} else {
|
||||
String name = pathParams[i].getName();
|
||||
String value = pathParameters.get(name);
|
||||
try {
|
||||
result[i] = Util.coerceToType(type, value);
|
||||
} catch (Exception e) {
|
||||
throw new DecodeException(value, sm.getString(
|
||||
"pojoMethodMapping.decodePathParamFail",
|
||||
value, type), e);
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
private static class MessageHandlerInfo {
|
||||
|
||||
private final Method m;
|
||||
private int indexString = -1;
|
||||
private int indexByteArray = -1;
|
||||
private int indexByteBuffer = -1;
|
||||
private int indexPong = -1;
|
||||
private int indexBoolean = -1;
|
||||
private int indexSession = -1;
|
||||
private int indexInputStream = -1;
|
||||
private int indexReader = -1;
|
||||
private int indexPrimitive = -1;
|
||||
private Map<Integer,PojoPathParam> indexPathParams = new HashMap<>();
|
||||
private int indexPayload = -1;
|
||||
private DecoderMatch decoderMatch = null;
|
||||
private long maxMessageSize = -1;
|
||||
|
||||
public MessageHandlerInfo(Method m, List<DecoderEntry> decoderEntries)
|
||||
throws DeploymentException {
|
||||
|
||||
this.m = m;
|
||||
|
||||
Class<?>[] types = m.getParameterTypes();
|
||||
Annotation[][] paramsAnnotations = m.getParameterAnnotations();
|
||||
|
||||
for (int i = 0; i < types.length; i++) {
|
||||
boolean paramFound = false;
|
||||
Annotation[] paramAnnotations = paramsAnnotations[i];
|
||||
for (Annotation paramAnnotation : paramAnnotations) {
|
||||
if (paramAnnotation.annotationType().equals(
|
||||
PathParam.class)) {
|
||||
indexPathParams.put(
|
||||
Integer.valueOf(i), new PojoPathParam(types[i],
|
||||
((PathParam) paramAnnotation).value()));
|
||||
paramFound = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (paramFound) {
|
||||
continue;
|
||||
}
|
||||
if (String.class.isAssignableFrom(types[i])) {
|
||||
if (indexString == -1) {
|
||||
indexString = i;
|
||||
} else {
|
||||
throw new DeploymentException(sm.getString(
|
||||
"pojoMethodMapping.duplicateMessageParam",
|
||||
m.getName(), m.getDeclaringClass().getName()));
|
||||
}
|
||||
} else if (Reader.class.isAssignableFrom(types[i])) {
|
||||
if (indexReader == -1) {
|
||||
indexReader = i;
|
||||
} else {
|
||||
throw new DeploymentException(sm.getString(
|
||||
"pojoMethodMapping.duplicateMessageParam",
|
||||
m.getName(), m.getDeclaringClass().getName()));
|
||||
}
|
||||
} else if (boolean.class == types[i]) {
|
||||
if (indexBoolean == -1) {
|
||||
indexBoolean = i;
|
||||
} else {
|
||||
throw new DeploymentException(sm.getString(
|
||||
"pojoMethodMapping.duplicateLastParam",
|
||||
m.getName(), m.getDeclaringClass().getName()));
|
||||
}
|
||||
} else if (ByteBuffer.class.isAssignableFrom(types[i])) {
|
||||
if (indexByteBuffer == -1) {
|
||||
indexByteBuffer = i;
|
||||
} else {
|
||||
throw new DeploymentException(sm.getString(
|
||||
"pojoMethodMapping.duplicateMessageParam",
|
||||
m.getName(), m.getDeclaringClass().getName()));
|
||||
}
|
||||
} else if (byte[].class == types[i]) {
|
||||
if (indexByteArray == -1) {
|
||||
indexByteArray = i;
|
||||
} else {
|
||||
throw new DeploymentException(sm.getString(
|
||||
"pojoMethodMapping.duplicateMessageParam",
|
||||
m.getName(), m.getDeclaringClass().getName()));
|
||||
}
|
||||
} else if (InputStream.class.isAssignableFrom(types[i])) {
|
||||
if (indexInputStream == -1) {
|
||||
indexInputStream = i;
|
||||
} else {
|
||||
throw new DeploymentException(sm.getString(
|
||||
"pojoMethodMapping.duplicateMessageParam",
|
||||
m.getName(), m.getDeclaringClass().getName()));
|
||||
}
|
||||
} else if (Util.isPrimitive(types[i])) {
|
||||
if (indexPrimitive == -1) {
|
||||
indexPrimitive = i;
|
||||
} else {
|
||||
throw new DeploymentException(sm.getString(
|
||||
"pojoMethodMapping.duplicateMessageParam",
|
||||
m.getName(), m.getDeclaringClass().getName()));
|
||||
}
|
||||
} else if (Session.class.isAssignableFrom(types[i])) {
|
||||
if (indexSession == -1) {
|
||||
indexSession = i;
|
||||
} else {
|
||||
throw new DeploymentException(sm.getString(
|
||||
"pojoMethodMapping.duplicateSessionParam",
|
||||
m.getName(), m.getDeclaringClass().getName()));
|
||||
}
|
||||
} else if (PongMessage.class.isAssignableFrom(types[i])) {
|
||||
if (indexPong == -1) {
|
||||
indexPong = i;
|
||||
} else {
|
||||
throw new DeploymentException(sm.getString(
|
||||
"pojoMethodMapping.duplicatePongMessageParam",
|
||||
m.getName(), m.getDeclaringClass().getName()));
|
||||
}
|
||||
} else {
|
||||
if (decoderMatch != null && decoderMatch.hasMatches()) {
|
||||
throw new DeploymentException(sm.getString(
|
||||
"pojoMethodMapping.duplicateMessageParam",
|
||||
m.getName(), m.getDeclaringClass().getName()));
|
||||
}
|
||||
decoderMatch = new DecoderMatch(types[i], decoderEntries);
|
||||
|
||||
if (decoderMatch.hasMatches()) {
|
||||
indexPayload = i;
|
||||
} else {
|
||||
throw new DeploymentException(sm.getString(
|
||||
"pojoMethodMapping.noDecoder",
|
||||
m.getName(), m.getDeclaringClass().getName()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Additional checks required
|
||||
if (indexString != -1) {
|
||||
if (indexPayload != -1) {
|
||||
throw new DeploymentException(sm.getString(
|
||||
"pojoMethodMapping.duplicateMessageParam",
|
||||
m.getName(), m.getDeclaringClass().getName()));
|
||||
} else {
|
||||
indexPayload = indexString;
|
||||
}
|
||||
}
|
||||
if (indexReader != -1) {
|
||||
if (indexPayload != -1) {
|
||||
throw new DeploymentException(sm.getString(
|
||||
"pojoMethodMapping.duplicateMessageParam",
|
||||
m.getName(), m.getDeclaringClass().getName()));
|
||||
} else {
|
||||
indexPayload = indexReader;
|
||||
}
|
||||
}
|
||||
if (indexByteArray != -1) {
|
||||
if (indexPayload != -1) {
|
||||
throw new DeploymentException(sm.getString(
|
||||
"pojoMethodMapping.duplicateMessageParam",
|
||||
m.getName(), m.getDeclaringClass().getName()));
|
||||
} else {
|
||||
indexPayload = indexByteArray;
|
||||
}
|
||||
}
|
||||
if (indexByteBuffer != -1) {
|
||||
if (indexPayload != -1) {
|
||||
throw new DeploymentException(sm.getString(
|
||||
"pojoMethodMapping.duplicateMessageParam",
|
||||
m.getName(), m.getDeclaringClass().getName()));
|
||||
} else {
|
||||
indexPayload = indexByteBuffer;
|
||||
}
|
||||
}
|
||||
if (indexInputStream != -1) {
|
||||
if (indexPayload != -1) {
|
||||
throw new DeploymentException(sm.getString(
|
||||
"pojoMethodMapping.duplicateMessageParam",
|
||||
m.getName(), m.getDeclaringClass().getName()));
|
||||
} else {
|
||||
indexPayload = indexInputStream;
|
||||
}
|
||||
}
|
||||
if (indexPrimitive != -1) {
|
||||
if (indexPayload != -1) {
|
||||
throw new DeploymentException(sm.getString(
|
||||
"pojoMethodMapping.duplicateMessageParam",
|
||||
m.getName(), m.getDeclaringClass().getName()));
|
||||
} else {
|
||||
indexPayload = indexPrimitive;
|
||||
}
|
||||
}
|
||||
if (indexPong != -1) {
|
||||
if (indexPayload != -1) {
|
||||
throw new DeploymentException(sm.getString(
|
||||
"pojoMethodMapping.pongWithPayload",
|
||||
m.getName(), m.getDeclaringClass().getName()));
|
||||
} else {
|
||||
indexPayload = indexPong;
|
||||
}
|
||||
}
|
||||
if (indexPayload == -1 && indexPrimitive == -1 &&
|
||||
indexBoolean != -1) {
|
||||
// The boolean we found is a payload, not a last flag
|
||||
indexPayload = indexBoolean;
|
||||
indexPrimitive = indexBoolean;
|
||||
indexBoolean = -1;
|
||||
}
|
||||
if (indexPayload == -1) {
|
||||
throw new DeploymentException(sm.getString(
|
||||
"pojoMethodMapping.noPayload",
|
||||
m.getName(), m.getDeclaringClass().getName()));
|
||||
}
|
||||
if (indexPong != -1 && indexBoolean != -1) {
|
||||
throw new DeploymentException(sm.getString(
|
||||
"pojoMethodMapping.partialPong",
|
||||
m.getName(), m.getDeclaringClass().getName()));
|
||||
}
|
||||
if(indexReader != -1 && indexBoolean != -1) {
|
||||
throw new DeploymentException(sm.getString(
|
||||
"pojoMethodMapping.partialReader",
|
||||
m.getName(), m.getDeclaringClass().getName()));
|
||||
}
|
||||
if(indexInputStream != -1 && indexBoolean != -1) {
|
||||
throw new DeploymentException(sm.getString(
|
||||
"pojoMethodMapping.partialInputStream",
|
||||
m.getName(), m.getDeclaringClass().getName()));
|
||||
}
|
||||
if (decoderMatch != null && decoderMatch.hasMatches() &&
|
||||
indexBoolean != -1) {
|
||||
throw new DeploymentException(sm.getString(
|
||||
"pojoMethodMapping.partialObject",
|
||||
m.getName(), m.getDeclaringClass().getName()));
|
||||
}
|
||||
|
||||
maxMessageSize = m.getAnnotation(OnMessage.class).maxMessageSize();
|
||||
}
|
||||
|
||||
|
||||
public boolean targetsSameWebSocketMessageType(MessageHandlerInfo otherHandler) {
|
||||
if (otherHandler == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return isPong() && otherHandler.isPong() || isBinary() && otherHandler.isBinary() ||
|
||||
isText() && otherHandler.isText();
|
||||
}
|
||||
|
||||
|
||||
private boolean isPong() {
|
||||
return indexPong >= 0;
|
||||
}
|
||||
|
||||
|
||||
private boolean isText() {
|
||||
return indexString >= 0 || indexPrimitive >= 0 || indexReader >= 0 ||
|
||||
(decoderMatch != null && decoderMatch.getTextDecoders().size() > 0);
|
||||
}
|
||||
|
||||
|
||||
private boolean isBinary() {
|
||||
return indexByteArray >= 0 || indexByteBuffer >= 0 || indexInputStream >= 0 ||
|
||||
(decoderMatch != null && decoderMatch.getBinaryDecoders().size() > 0);
|
||||
}
|
||||
|
||||
|
||||
public Set<MessageHandler> getMessageHandlers(Object pojo,
|
||||
Map<String,String> pathParameters, Session session,
|
||||
EndpointConfig config) {
|
||||
Object[] params = new Object[m.getParameterTypes().length];
|
||||
|
||||
for (Map.Entry<Integer,PojoPathParam> entry :
|
||||
indexPathParams.entrySet()) {
|
||||
PojoPathParam pathParam = entry.getValue();
|
||||
String valueString = pathParameters.get(pathParam.getName());
|
||||
Object value = null;
|
||||
try {
|
||||
value = Util.coerceToType(pathParam.getType(), valueString);
|
||||
} catch (Exception e) {
|
||||
DecodeException de = new DecodeException(valueString,
|
||||
sm.getString(
|
||||
"pojoMethodMapping.decodePathParamFail",
|
||||
valueString, pathParam.getType()), e);
|
||||
params = new Object[] { de };
|
||||
break;
|
||||
}
|
||||
params[entry.getKey().intValue()] = value;
|
||||
}
|
||||
|
||||
Set<MessageHandler> results = new HashSet<>(2);
|
||||
if (indexBoolean == -1) {
|
||||
// Basic
|
||||
if (indexString != -1 || indexPrimitive != -1) {
|
||||
MessageHandler mh = new PojoMessageHandlerWholeText(pojo, m,
|
||||
session, config, null, params, indexPayload, false,
|
||||
indexSession, maxMessageSize);
|
||||
results.add(mh);
|
||||
} else if (indexReader != -1) {
|
||||
MessageHandler mh = new PojoMessageHandlerWholeText(pojo, m,
|
||||
session, config, null, params, indexReader, true,
|
||||
indexSession, maxMessageSize);
|
||||
results.add(mh);
|
||||
} else if (indexByteArray != -1) {
|
||||
MessageHandler mh = new PojoMessageHandlerWholeBinary(pojo,
|
||||
m, session, config, null, params, indexByteArray,
|
||||
true, indexSession, false, maxMessageSize);
|
||||
results.add(mh);
|
||||
} else if (indexByteBuffer != -1) {
|
||||
MessageHandler mh = new PojoMessageHandlerWholeBinary(pojo,
|
||||
m, session, config, null, params, indexByteBuffer,
|
||||
false, indexSession, false, maxMessageSize);
|
||||
results.add(mh);
|
||||
} else if (indexInputStream != -1) {
|
||||
MessageHandler mh = new PojoMessageHandlerWholeBinary(pojo,
|
||||
m, session, config, null, params, indexInputStream,
|
||||
true, indexSession, true, maxMessageSize);
|
||||
results.add(mh);
|
||||
} else if (decoderMatch != null && decoderMatch.hasMatches()) {
|
||||
if (decoderMatch.getBinaryDecoders().size() > 0) {
|
||||
MessageHandler mh = new PojoMessageHandlerWholeBinary(
|
||||
pojo, m, session, config,
|
||||
decoderMatch.getBinaryDecoders(), params,
|
||||
indexPayload, true, indexSession, true,
|
||||
maxMessageSize);
|
||||
results.add(mh);
|
||||
}
|
||||
if (decoderMatch.getTextDecoders().size() > 0) {
|
||||
MessageHandler mh = new PojoMessageHandlerWholeText(
|
||||
pojo, m, session, config,
|
||||
decoderMatch.getTextDecoders(), params,
|
||||
indexPayload, true, indexSession, maxMessageSize);
|
||||
results.add(mh);
|
||||
}
|
||||
} else {
|
||||
MessageHandler mh = new PojoMessageHandlerWholePong(pojo, m,
|
||||
session, params, indexPong, false, indexSession);
|
||||
results.add(mh);
|
||||
}
|
||||
} else {
|
||||
// ASync
|
||||
if (indexString != -1) {
|
||||
MessageHandler mh = new PojoMessageHandlerPartialText(pojo,
|
||||
m, session, params, indexString, false,
|
||||
indexBoolean, indexSession, maxMessageSize);
|
||||
results.add(mh);
|
||||
} else if (indexByteArray != -1) {
|
||||
MessageHandler mh = new PojoMessageHandlerPartialBinary(
|
||||
pojo, m, session, params, indexByteArray, true,
|
||||
indexBoolean, indexSession, maxMessageSize);
|
||||
results.add(mh);
|
||||
} else {
|
||||
MessageHandler mh = new PojoMessageHandlerPartialBinary(
|
||||
pojo, m, session, params, indexByteBuffer, false,
|
||||
indexBoolean, indexSession, maxMessageSize);
|
||||
results.add(mh);
|
||||
}
|
||||
}
|
||||
return results;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private enum MethodType {
|
||||
ON_OPEN,
|
||||
ON_CLOSE,
|
||||
ON_ERROR
|
||||
}
|
||||
}
|
||||
69
java/org/apache/tomcat/websocket/pojo/PojoPathParam.java
Normal file
69
java/org/apache/tomcat/websocket/pojo/PojoPathParam.java
Normal file
@@ -0,0 +1,69 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.apache.tomcat.websocket.pojo;
|
||||
|
||||
import javax.websocket.DeploymentException;
|
||||
|
||||
import org.apache.tomcat.util.res.StringManager;
|
||||
import org.apache.tomcat.websocket.Util;
|
||||
|
||||
/**
|
||||
* Stores the parameter type and name for a parameter that needs to be passed to
|
||||
* an onXxx method of {@link javax.websocket.Endpoint}. The name is only present
|
||||
* for parameters annotated with
|
||||
* {@link javax.websocket.server.PathParam}. For the
|
||||
* {@link javax.websocket.Session} and {@link java.lang.Throwable} parameters,
|
||||
* {@link #getName()} will always return <code>null</code>.
|
||||
*/
|
||||
public class PojoPathParam {
|
||||
|
||||
private static final StringManager sm = StringManager.getManager(PojoPathParam.class);
|
||||
|
||||
private final Class<?> type;
|
||||
private final String name;
|
||||
|
||||
|
||||
public PojoPathParam(Class<?> type, String name) throws DeploymentException {
|
||||
if (name != null) {
|
||||
// Annotated as @PathParam so validate type
|
||||
validateType(type);
|
||||
}
|
||||
this.type = type;
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
|
||||
public Class<?> getType() {
|
||||
return type;
|
||||
}
|
||||
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
|
||||
private static void validateType(Class<?> type) throws DeploymentException {
|
||||
if (String.class == type) {
|
||||
return;
|
||||
}
|
||||
if (Util.isPrimitive(type)) {
|
||||
return;
|
||||
}
|
||||
throw new DeploymentException(sm.getString("pojoPathParam.wrongType", type.getName()));
|
||||
}
|
||||
}
|
||||
21
java/org/apache/tomcat/websocket/pojo/package-info.java
Normal file
21
java/org/apache/tomcat/websocket/pojo/package-info.java
Normal file
@@ -0,0 +1,21 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
/**
|
||||
* This package provides the necessary plumbing to convert an annotated POJO
|
||||
* into a WebSocket {@link javax.websocket.Endpoint}.
|
||||
*/
|
||||
package org.apache.tomcat.websocket.pojo;
|
||||
38
java/org/apache/tomcat/websocket/server/Constants.java
Normal file
38
java/org/apache/tomcat/websocket/server/Constants.java
Normal file
@@ -0,0 +1,38 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.apache.tomcat.websocket.server;
|
||||
|
||||
/**
|
||||
* Internal implementation constants.
|
||||
*/
|
||||
public class Constants {
|
||||
|
||||
public static final String BINARY_BUFFER_SIZE_SERVLET_CONTEXT_INIT_PARAM =
|
||||
"org.apache.tomcat.websocket.binaryBufferSize";
|
||||
public static final String TEXT_BUFFER_SIZE_SERVLET_CONTEXT_INIT_PARAM =
|
||||
"org.apache.tomcat.websocket.textBufferSize";
|
||||
public static final String ENFORCE_NO_ADD_AFTER_HANDSHAKE_CONTEXT_INIT_PARAM =
|
||||
"org.apache.tomcat.websocket.noAddAfterHandshake";
|
||||
|
||||
public static final String SERVER_CONTAINER_SERVLET_CONTEXT_ATTRIBUTE =
|
||||
"javax.websocket.server.ServerContainer";
|
||||
|
||||
|
||||
private Constants() {
|
||||
// Hide default constructor
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.apache.tomcat.websocket.server;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
import javax.websocket.Extension;
|
||||
import javax.websocket.HandshakeResponse;
|
||||
import javax.websocket.server.HandshakeRequest;
|
||||
import javax.websocket.server.ServerEndpointConfig;
|
||||
|
||||
public class DefaultServerEndpointConfigurator
|
||||
extends ServerEndpointConfig.Configurator {
|
||||
|
||||
@Override
|
||||
public <T> T getEndpointInstance(Class<T> clazz)
|
||||
throws InstantiationException {
|
||||
try {
|
||||
return clazz.getConstructor().newInstance();
|
||||
} catch (InstantiationException e) {
|
||||
throw e;
|
||||
} catch (ReflectiveOperationException e) {
|
||||
InstantiationException ie = new InstantiationException();
|
||||
ie.initCause(e);
|
||||
throw ie;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public String getNegotiatedSubprotocol(List<String> supported,
|
||||
List<String> requested) {
|
||||
|
||||
for (String request : requested) {
|
||||
if (supported.contains(request)) {
|
||||
return request;
|
||||
}
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public List<Extension> getNegotiatedExtensions(List<Extension> installed,
|
||||
List<Extension> requested) {
|
||||
Set<String> installedNames = new HashSet<>();
|
||||
for (Extension e : installed) {
|
||||
installedNames.add(e.getName());
|
||||
}
|
||||
List<Extension> result = new ArrayList<>();
|
||||
for (Extension request : requested) {
|
||||
if (installedNames.contains(request.getName())) {
|
||||
result.add(request);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public boolean checkOrigin(String originHeaderValue) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void modifyHandshake(ServerEndpointConfig sec,
|
||||
HandshakeRequest request, HandshakeResponse response) {
|
||||
// NO-OP
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
# Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
# contributor license agreements. See the NOTICE file distributed with
|
||||
# this work for additional information regarding copyright ownership.
|
||||
# The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
# (the "License"); you may not use this file except in compliance with
|
||||
# the License. You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
serverContainer.addNotAllowed=No further Endpoints may be registered once an attempt has been made to use one of the previously registered endpoints
|
||||
serverContainer.configuratorFail=Failed to create configurator of type [{0}] for POJO of type [{1}]
|
||||
serverContainer.duplicatePaths=Multiple Endpoints may not be deployed to the same path [{0}] : existing endpoint was [{1}] and new endpoint is [{2}]
|
||||
serverContainer.encoderFail=Unable to create encoder of type [{0}]
|
||||
serverContainer.failedDeployment=Deployment of WebSocket Endpoints to the web application with path [{0}] in host [{1}] is not permitted due to the failure of a previous deployment
|
||||
serverContainer.missingAnnotation=Cannot deploy POJO class [{0}] as it is not annotated with @ServerEndpoint
|
||||
serverContainer.servletContextMissing=No ServletContext was specified
|
||||
|
||||
upgradeUtil.incompatibleRsv=Extensions were specified that have incompatible RSV bit usage
|
||||
|
||||
uriTemplate.duplicateParameter=The parameter [{0}] appears more than once in the path which is not permitted
|
||||
uriTemplate.emptySegment=The path [{0}] contains one or more empty segments which are is not permitted
|
||||
uriTemplate.invalidPath=The path [{0}] is not valid.
|
||||
uriTemplate.invalidSegment=The segment [{0}] is not valid in the provided path [{1}]
|
||||
|
||||
wsFrameServer.bytesRead=Read [{0}] bytes into input buffer ready for processing
|
||||
wsFrameServer.illegalReadState=Unexpected read state [{0}]
|
||||
wsFrameServer.onDataAvailable=Method entry
|
||||
|
||||
wsHttpUpgradeHandler.closeOnError=Closing WebSocket connection due to an error
|
||||
wsHttpUpgradeHandler.destroyFailed=Failed to close WebConnection while destroying the WebSocket HttpUpgradeHandler
|
||||
wsHttpUpgradeHandler.noPreInit=The preInit() method must be called to configure the WebSocket HttpUpgradeHandler before the container calls init(). Usually, this means the Servlet that created the WsHttpUpgradeHandler instance should also call preInit()
|
||||
wsHttpUpgradeHandler.serverStop=The server is stopping
|
||||
|
||||
wsRemoteEndpointServer.closeFailed=Failed to close the ServletOutputStream connection cleanly
|
||||
@@ -0,0 +1,21 @@
|
||||
# Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
# contributor license agreements. See the NOTICE file distributed with
|
||||
# this work for additional information regarding copyright ownership.
|
||||
# The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
# (the "License"); you may not use this file except in compliance with
|
||||
# the License. You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
serverContainer.missingAnnotation=Die POJO Klasse [{0}] kann nicht deployed werden da sie nicht mit @ServerEndpoint annotiert ist.
|
||||
serverContainer.servletContextMissing=Es wurde kein ServletContext angegeben
|
||||
|
||||
upgradeUtil.incompatibleRsv=Es wurden Erweiterungen spezifiziert, die eine inkompatible RSV Bit Konstellation erzeugen
|
||||
|
||||
uriTemplate.invalidPath=Der Pfad [{0}] ist nicht gültig.
|
||||
@@ -0,0 +1,20 @@
|
||||
# Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
# contributor license agreements. See the NOTICE file distributed with
|
||||
# this work for additional information regarding copyright ownership.
|
||||
# The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
# (the "License"); you may not use this file except in compliance with
|
||||
# the License. You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
serverContainer.configuratorFail=Fallo al crear configurador de tipo [{0}] para el POJO de tipo [{1}]
|
||||
|
||||
uriTemplate.invalidPath=El camino [{0}] no es válido.\n
|
||||
|
||||
wsFrameServer.bytesRead=[{0}] leídos del bufer de entrada llistos para ser procesados
|
||||
@@ -0,0 +1,40 @@
|
||||
# Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
# contributor license agreements. See the NOTICE file distributed with
|
||||
# this work for additional information regarding copyright ownership.
|
||||
# The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
# (the "License"); you may not use this file except in compliance with
|
||||
# the License. You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
serverContainer.addNotAllowed=Aucune terminaison ne peut être enregistré une fois qu'une tentative d'utilisation d'une des terminaisons précédemment enregistrée a été faite
|
||||
serverContainer.configuratorFail=Echec de création du configurateur de type [{0}] pour le POJO de type [{1}]
|
||||
serverContainer.duplicatePaths=Plusieurs terminaisons ne peuvent pas être déployés vers le même chemin [{0}]: la terminaison existante était [{1}] et la nouvelle est [{2}]
|
||||
serverContainer.encoderFail=Impossible de créer un encodeur de type [{0}]
|
||||
serverContainer.failedDeployment=Le déploiement de terminaisons WebSocket dans l''application web au chemin [{0}] dans l''hôte [{1}] n''est pas autorisé à cause de l''échec lors d''un précédent déploiement
|
||||
serverContainer.missingAnnotation=Impossible de déployer la classe POJO [{0}] car elle n''a pas été annotée avec @ServerEndpoint
|
||||
serverContainer.servletContextMissing=Aucun ServletContext n'a été spécifié
|
||||
|
||||
upgradeUtil.incompatibleRsv=Des extensions qui ont été spécifiées ont une utilisation incompatible du bit RSV
|
||||
|
||||
uriTemplate.duplicateParameter=Le paramètre [{0}] apparaît plus d''une fois dans le chemin ce qui n''est pas permis
|
||||
uriTemplate.emptySegment=Le chemin [{0}] contient un ou plusieurs segments vide ce qui n''est pas autorisé
|
||||
uriTemplate.invalidPath=Le chemin [{0}] est invalide
|
||||
uriTemplate.invalidSegment=Le segment [{0}] est invalide pour le chemin fourni [{1}]
|
||||
|
||||
wsFrameServer.bytesRead=Lu [{0}] octets dans le buffer de réception prêts à être traités
|
||||
wsFrameServer.illegalReadState=Etat de lecture inattendu [{0}]
|
||||
wsFrameServer.onDataAvailable=Entrée de méthode
|
||||
|
||||
wsHttpUpgradeHandler.closeOnError=Fermeture de la connection WebSocket à cause d'une erreur
|
||||
wsHttpUpgradeHandler.destroyFailed=Echec de la fermeture de la WebConnection lors de la destruction du HttpUpgradeHandler de WebSocket
|
||||
wsHttpUpgradeHandler.noPreInit=La méthode preInit() doit être appelée pour configurer le HttpUpgradeHandler de Websockets avant que le container n'appelle init(), cela veut habituellement dire que le Servlet qui a crée l'instance du WsHttpUpgradeHandler doit aussi appeler preInit()
|
||||
wsHttpUpgradeHandler.serverStop=Le serveur est en train de s'arrêter
|
||||
|
||||
wsRemoteEndpointServer.closeFailed=Impossible de fermer le ServletOutputStream proprement
|
||||
@@ -0,0 +1,40 @@
|
||||
# Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
# contributor license agreements. See the NOTICE file distributed with
|
||||
# this work for additional information regarding copyright ownership.
|
||||
# The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
# (the "License"); you may not use this file except in compliance with
|
||||
# the License. You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
serverContainer.addNotAllowed=以前に登録したことのあるエンドポイントには登録できません。
|
||||
serverContainer.configuratorFail=POJO クラス [{1}] のインスタンスを構成するクラス [{0}] のインスタンスを作成できません。
|
||||
serverContainer.duplicatePaths=複数のエンドポイントを同じパス[{0}]に配備することはできません:既存のエンドポイントは[{1}]で、新しいエンドポイントは[{2}]です。
|
||||
serverContainer.encoderFail=タイプ[{0}]のエンコーダを作成できません
|
||||
serverContainer.failedDeployment=以前のデプロイメントが失敗したため、ホスト[{1}]内のパス[{0}]を持つWebアプリケーションへのWebSocketエンドポイントのデプロイメントは許可されていません。
|
||||
serverContainer.missingAnnotation=POJOクラス[{0}]は@ServerEndpointでアノテーション付けされていないため、デプロイ出来ません。
|
||||
serverContainer.servletContextMissing=ServletContextが指定されていません
|
||||
|
||||
upgradeUtil.incompatibleRsv=互換性のないRSVビットの使用法を持つ拡張が指定されました
|
||||
|
||||
uriTemplate.duplicateParameter=パス中にパラメーター [{0}] を複数回登場させることはできません。
|
||||
uriTemplate.emptySegment=パス [{0}] に一つ以上の空セグメントを含めることはできません。
|
||||
uriTemplate.invalidPath=[{0}] は不正なパスです。
|
||||
uriTemplate.invalidSegment=パス [{1}] に存在しないセグメント [{0}] が指定されました。
|
||||
|
||||
wsFrameServer.bytesRead=入力バッファーに読み込んだ [{0}] バイトのデータは処理可能です。
|
||||
wsFrameServer.illegalReadState=予期しない読み取り状態[{0}]
|
||||
wsFrameServer.onDataAvailable=メソッドエントリ
|
||||
|
||||
wsHttpUpgradeHandler.closeOnError=エラーが発生したため WebSocket コネクションを切断します。
|
||||
wsHttpUpgradeHandler.destroyFailed=WebSocket HttpUpgradeHandlerを破棄している間にWebConnectionを閉じることができませんでした。
|
||||
wsHttpUpgradeHandler.noPreInit=コンテナがinit()を呼び出す前に、preInit()メソッドを呼び出すようにWebSocket HttpUpgradeHandlerを設定する必要があります。 通常、これはWsHttpUpgradeHandlerインスタンスを作成したサーブレットがpreInit()を呼び出す必要があることを意味します。
|
||||
wsHttpUpgradeHandler.serverStop=サーバ停止中
|
||||
|
||||
wsRemoteEndpointServer.closeFailed=ServletOutputStreamコネクションを正常に閉じることができませんでした
|
||||
@@ -0,0 +1,40 @@
|
||||
# Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
# contributor license agreements. See the NOTICE file distributed with
|
||||
# this work for additional information regarding copyright ownership.
|
||||
# The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
# (the "License"); you may not use this file except in compliance with
|
||||
# the License. You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
serverContainer.addNotAllowed=이전에 등록된 엔드포인트들 중 하나라도 사용하려고 시도하게 되면, 엔드포인트들을 더 이상 등록할 수 없습니다.
|
||||
serverContainer.configuratorFail=타입이 [{1}]인 POJO를 위한 타입 [{0}]의 Configurator를 생성하지 못했습니다.
|
||||
serverContainer.duplicatePaths=여러 개의 엔드포인트들이 동일한 경로 [{0}]에 배치될 수 없습니다: 기존 엔드포인트는 [{1}]였으며 신규 엔드포인트는 [{2}]입니다.
|
||||
serverContainer.encoderFail=타입이 [{0}]인 인코더를 생성할 수 없습니다.
|
||||
serverContainer.failedDeployment=이전 배치의 실패로 인하여, 호스트 [{1}] 내에 경로 [{0}]의 웹 애플리케이션에 대한 웹소켓 엔드포인트들의 배치가 허용되지 않습니다.
|
||||
serverContainer.missingAnnotation=클래스가 @ServerEndpoint로 annotate되어 있지 않기에, POJO 클래스 [{0}]을(를) 배치할 수 없습니다.
|
||||
serverContainer.servletContextMissing=지정된 ServletContext가 없습니다.
|
||||
|
||||
upgradeUtil.incompatibleRsv=호환되지 않는 RSV 비트를 사용하여, Extension들이 지정되었습니다.
|
||||
|
||||
uriTemplate.duplicateParameter=허용되지 않는 경로에서, 파라미터 [{0}]이(가) 두번 이상 나타나고 있습니다.
|
||||
uriTemplate.emptySegment=경로 [{0}]이(가), 하나 이상의 허용되지 않는 empty segment들을 포함하고 있습니다.
|
||||
uriTemplate.invalidPath=경로 [{0}](은)는 유효하지 않습니다.
|
||||
uriTemplate.invalidSegment=세그먼트 [{0}]은(는) 제공된 경로 [{1}] 내에 유효하지 않습니다.
|
||||
|
||||
wsFrameServer.bytesRead=[{0}] 바이트를 입력 버퍼에 읽어 처리를 준비합니다.
|
||||
wsFrameServer.illegalReadState=예기치 않은 읽기 상태 [{0}]
|
||||
wsFrameServer.onDataAvailable=메소드 엔트리
|
||||
|
||||
wsHttpUpgradeHandler.closeOnError=오류 발생으로 인하여, 웹소켓 연결을 닫습니다.
|
||||
wsHttpUpgradeHandler.destroyFailed=웹소켓 HttpUpgradeHandler를 소멸시키는 중, WebConnection을 닫지 못했습니다.
|
||||
wsHttpUpgradeHandler.noPreInit=컨테이너가 init()을 호출하기 전에 웹소켓 HttpUpgradeHandler를 설정하기 위하여, preInit() 메소드가 반드시 호출되어야만 합니다. 통상 이는 WsHttpUpgradeHandler 인스턴스를 생성한 서블릿도 preInit()을 호출해야 함을 의미합니다.
|
||||
wsHttpUpgradeHandler.serverStop=서버가 중지되고 있는 중입니다.
|
||||
|
||||
wsRemoteEndpointServer.closeFailed=해당 ServletOutputStream의 연결을 깨끗하게 닫지 못했습니다.
|
||||
@@ -0,0 +1,31 @@
|
||||
# Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
# contributor license agreements. See the NOTICE file distributed with
|
||||
# this work for additional information regarding copyright ownership.
|
||||
# The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
# (the "License"); you may not use this file except in compliance with
|
||||
# the License. You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
serverContainer.configuratorFail=无法为[{1}]类型的POJO创建类型[{0}]的配置程序
|
||||
serverContainer.duplicatePaths=多个端点可能不能发不到同一个路径[{0}]:已经存在的端点[{1}]和新的端点[{2}]
|
||||
serverContainer.encoderFail=无法创建[{0}]类型的编码器
|
||||
serverContainer.failedDeployment=由于以前的部署失败,不允许将WebSocket终结点部署到主机[{1}]中路径为[{0}]的Web应用程序
|
||||
serverContainer.servletContextMissing=没有指定ServletContext
|
||||
|
||||
upgradeUtil.incompatibleRsv=指定扩展名具有不兼容的RSV位使用
|
||||
|
||||
uriTemplate.invalidPath=路径 [{0}] 无效。
|
||||
|
||||
wsFrameServer.bytesRead=将[{0}]个字节读入输入缓冲区,准备进行处理
|
||||
wsFrameServer.onDataAvailable=进入方法
|
||||
|
||||
wsHttpUpgradeHandler.noPreInit=在容器调用init()之前,必须调用preinit()方法来配置WebSocket HttpUpgradeHandler。通常,这意味着创建WsHttpUpgradeHandler 实例的servlet也应该调用preinit()
|
||||
|
||||
wsRemoteEndpointServer.closeFailed=无法完全关闭ServletOutputStream 连接
|
||||
339
java/org/apache/tomcat/websocket/server/UpgradeUtil.java
Normal file
339
java/org/apache/tomcat/websocket/server/UpgradeUtil.java
Normal file
@@ -0,0 +1,339 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.apache.tomcat.websocket.server;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.Enumeration;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
|
||||
import javax.servlet.ServletException;
|
||||
import javax.servlet.ServletRequest;
|
||||
import javax.servlet.ServletResponse;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import javax.websocket.Endpoint;
|
||||
import javax.websocket.Extension;
|
||||
import javax.websocket.HandshakeResponse;
|
||||
import javax.websocket.server.ServerEndpointConfig;
|
||||
|
||||
import org.apache.tomcat.util.codec.binary.Base64;
|
||||
import org.apache.tomcat.util.res.StringManager;
|
||||
import org.apache.tomcat.util.security.ConcurrentMessageDigest;
|
||||
import org.apache.tomcat.websocket.Constants;
|
||||
import org.apache.tomcat.websocket.Transformation;
|
||||
import org.apache.tomcat.websocket.TransformationFactory;
|
||||
import org.apache.tomcat.websocket.Util;
|
||||
import org.apache.tomcat.websocket.WsHandshakeResponse;
|
||||
import org.apache.tomcat.websocket.pojo.PojoEndpointServer;
|
||||
|
||||
public class UpgradeUtil {
|
||||
|
||||
private static final StringManager sm =
|
||||
StringManager.getManager(UpgradeUtil.class.getPackage().getName());
|
||||
private static final byte[] WS_ACCEPT =
|
||||
"258EAFA5-E914-47DA-95CA-C5AB0DC85B11".getBytes(
|
||||
StandardCharsets.ISO_8859_1);
|
||||
|
||||
private UpgradeUtil() {
|
||||
// Utility class. Hide default constructor.
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks to see if this is an HTTP request that includes a valid upgrade
|
||||
* request to web socket.
|
||||
* <p>
|
||||
* Note: RFC 2616 does not limit HTTP upgrade to GET requests but the Java
|
||||
* WebSocket spec 1.0, section 8.2 implies such a limitation and RFC
|
||||
* 6455 section 4.1 requires that a WebSocket Upgrade uses GET.
|
||||
* @param request The request to check if it is an HTTP upgrade request for
|
||||
* a WebSocket connection
|
||||
* @param response The response associated with the request
|
||||
* @return <code>true</code> if the request includes an HTTP Upgrade request
|
||||
* for the WebSocket protocol, otherwise <code>false</code>
|
||||
*/
|
||||
public static boolean isWebSocketUpgradeRequest(ServletRequest request,
|
||||
ServletResponse response) {
|
||||
|
||||
return ((request instanceof HttpServletRequest) &&
|
||||
(response instanceof HttpServletResponse) &&
|
||||
headerContainsToken((HttpServletRequest) request,
|
||||
Constants.UPGRADE_HEADER_NAME,
|
||||
Constants.UPGRADE_HEADER_VALUE) &&
|
||||
"GET".equals(((HttpServletRequest) request).getMethod()));
|
||||
}
|
||||
|
||||
|
||||
public static void doUpgrade(WsServerContainer sc, HttpServletRequest req,
|
||||
HttpServletResponse resp, ServerEndpointConfig sec,
|
||||
Map<String,String> pathParams)
|
||||
throws ServletException, IOException {
|
||||
|
||||
// Validate the rest of the headers and reject the request if that
|
||||
// validation fails
|
||||
String key;
|
||||
String subProtocol = null;
|
||||
if (!headerContainsToken(req, Constants.CONNECTION_HEADER_NAME,
|
||||
Constants.CONNECTION_HEADER_VALUE)) {
|
||||
resp.sendError(HttpServletResponse.SC_BAD_REQUEST);
|
||||
return;
|
||||
}
|
||||
if (!headerContainsToken(req, Constants.WS_VERSION_HEADER_NAME,
|
||||
Constants.WS_VERSION_HEADER_VALUE)) {
|
||||
resp.setStatus(426);
|
||||
resp.setHeader(Constants.WS_VERSION_HEADER_NAME,
|
||||
Constants.WS_VERSION_HEADER_VALUE);
|
||||
return;
|
||||
}
|
||||
key = req.getHeader(Constants.WS_KEY_HEADER_NAME);
|
||||
if (key == null) {
|
||||
resp.sendError(HttpServletResponse.SC_BAD_REQUEST);
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
// Origin check
|
||||
String origin = req.getHeader(Constants.ORIGIN_HEADER_NAME);
|
||||
if (!sec.getConfigurator().checkOrigin(origin)) {
|
||||
resp.sendError(HttpServletResponse.SC_FORBIDDEN);
|
||||
return;
|
||||
}
|
||||
// Sub-protocols
|
||||
List<String> subProtocols = getTokensFromHeader(req,
|
||||
Constants.WS_PROTOCOL_HEADER_NAME);
|
||||
subProtocol = sec.getConfigurator().getNegotiatedSubprotocol(
|
||||
sec.getSubprotocols(), subProtocols);
|
||||
|
||||
// Extensions
|
||||
// Should normally only be one header but handle the case of multiple
|
||||
// headers
|
||||
List<Extension> extensionsRequested = new ArrayList<>();
|
||||
Enumeration<String> extHeaders = req.getHeaders(Constants.WS_EXTENSIONS_HEADER_NAME);
|
||||
while (extHeaders.hasMoreElements()) {
|
||||
Util.parseExtensionHeader(extensionsRequested, extHeaders.nextElement());
|
||||
}
|
||||
// Negotiation phase 1. By default this simply filters out the
|
||||
// extensions that the server does not support but applications could
|
||||
// use a custom configurator to do more than this.
|
||||
List<Extension> installedExtensions = null;
|
||||
if (sec.getExtensions().size() == 0) {
|
||||
installedExtensions = Constants.INSTALLED_EXTENSIONS;
|
||||
} else {
|
||||
installedExtensions = new ArrayList<>();
|
||||
installedExtensions.addAll(sec.getExtensions());
|
||||
installedExtensions.addAll(Constants.INSTALLED_EXTENSIONS);
|
||||
}
|
||||
List<Extension> negotiatedExtensionsPhase1 = sec.getConfigurator().getNegotiatedExtensions(
|
||||
installedExtensions, extensionsRequested);
|
||||
|
||||
// Negotiation phase 2. Create the Transformations that will be applied
|
||||
// to this connection. Note than an extension may be dropped at this
|
||||
// point if the client has requested a configuration that the server is
|
||||
// unable to support.
|
||||
List<Transformation> transformations = createTransformations(negotiatedExtensionsPhase1);
|
||||
|
||||
List<Extension> negotiatedExtensionsPhase2;
|
||||
if (transformations.isEmpty()) {
|
||||
negotiatedExtensionsPhase2 = Collections.emptyList();
|
||||
} else {
|
||||
negotiatedExtensionsPhase2 = new ArrayList<>(transformations.size());
|
||||
for (Transformation t : transformations) {
|
||||
negotiatedExtensionsPhase2.add(t.getExtensionResponse());
|
||||
}
|
||||
}
|
||||
|
||||
// Build the transformation pipeline
|
||||
Transformation transformation = null;
|
||||
StringBuilder responseHeaderExtensions = new StringBuilder();
|
||||
boolean first = true;
|
||||
for (Transformation t : transformations) {
|
||||
if (first) {
|
||||
first = false;
|
||||
} else {
|
||||
responseHeaderExtensions.append(',');
|
||||
}
|
||||
append(responseHeaderExtensions, t.getExtensionResponse());
|
||||
if (transformation == null) {
|
||||
transformation = t;
|
||||
} else {
|
||||
transformation.setNext(t);
|
||||
}
|
||||
}
|
||||
|
||||
// Now we have the full pipeline, validate the use of the RSV bits.
|
||||
if (transformation != null && !transformation.validateRsvBits(0)) {
|
||||
throw new ServletException(sm.getString("upgradeUtil.incompatibleRsv"));
|
||||
}
|
||||
|
||||
// If we got this far, all is good. Accept the connection.
|
||||
resp.setHeader(Constants.UPGRADE_HEADER_NAME,
|
||||
Constants.UPGRADE_HEADER_VALUE);
|
||||
resp.setHeader(Constants.CONNECTION_HEADER_NAME,
|
||||
Constants.CONNECTION_HEADER_VALUE);
|
||||
resp.setHeader(HandshakeResponse.SEC_WEBSOCKET_ACCEPT,
|
||||
getWebSocketAccept(key));
|
||||
if (subProtocol != null && subProtocol.length() > 0) {
|
||||
// RFC6455 4.2.2 explicitly states "" is not valid here
|
||||
resp.setHeader(Constants.WS_PROTOCOL_HEADER_NAME, subProtocol);
|
||||
}
|
||||
if (!transformations.isEmpty()) {
|
||||
resp.setHeader(Constants.WS_EXTENSIONS_HEADER_NAME, responseHeaderExtensions.toString());
|
||||
}
|
||||
|
||||
WsHandshakeRequest wsRequest = new WsHandshakeRequest(req, pathParams);
|
||||
WsHandshakeResponse wsResponse = new WsHandshakeResponse();
|
||||
WsPerSessionServerEndpointConfig perSessionServerEndpointConfig =
|
||||
new WsPerSessionServerEndpointConfig(sec);
|
||||
sec.getConfigurator().modifyHandshake(perSessionServerEndpointConfig,
|
||||
wsRequest, wsResponse);
|
||||
wsRequest.finished();
|
||||
|
||||
// Add any additional headers
|
||||
for (Entry<String,List<String>> entry :
|
||||
wsResponse.getHeaders().entrySet()) {
|
||||
for (String headerValue: entry.getValue()) {
|
||||
resp.addHeader(entry.getKey(), headerValue);
|
||||
}
|
||||
}
|
||||
|
||||
Endpoint ep;
|
||||
try {
|
||||
Class<?> clazz = sec.getEndpointClass();
|
||||
if (Endpoint.class.isAssignableFrom(clazz)) {
|
||||
ep = (Endpoint) sec.getConfigurator().getEndpointInstance(
|
||||
clazz);
|
||||
} else {
|
||||
ep = new PojoEndpointServer();
|
||||
// Need to make path params available to POJO
|
||||
perSessionServerEndpointConfig.getUserProperties().put(
|
||||
org.apache.tomcat.websocket.pojo.Constants.POJO_PATH_PARAM_KEY, pathParams);
|
||||
}
|
||||
} catch (InstantiationException e) {
|
||||
throw new ServletException(e);
|
||||
}
|
||||
|
||||
WsHttpUpgradeHandler wsHandler =
|
||||
req.upgrade(WsHttpUpgradeHandler.class);
|
||||
wsHandler.preInit(ep, perSessionServerEndpointConfig, sc, wsRequest,
|
||||
negotiatedExtensionsPhase2, subProtocol, transformation, pathParams,
|
||||
req.isSecure());
|
||||
|
||||
}
|
||||
|
||||
|
||||
private static List<Transformation> createTransformations(
|
||||
List<Extension> negotiatedExtensions) {
|
||||
|
||||
TransformationFactory factory = TransformationFactory.getInstance();
|
||||
|
||||
LinkedHashMap<String,List<List<Extension.Parameter>>> extensionPreferences =
|
||||
new LinkedHashMap<>();
|
||||
|
||||
// Result will likely be smaller than this
|
||||
List<Transformation> result = new ArrayList<>(negotiatedExtensions.size());
|
||||
|
||||
for (Extension extension : negotiatedExtensions) {
|
||||
List<List<Extension.Parameter>> preferences =
|
||||
extensionPreferences.get(extension.getName());
|
||||
|
||||
if (preferences == null) {
|
||||
preferences = new ArrayList<>();
|
||||
extensionPreferences.put(extension.getName(), preferences);
|
||||
}
|
||||
|
||||
preferences.add(extension.getParameters());
|
||||
}
|
||||
|
||||
for (Map.Entry<String,List<List<Extension.Parameter>>> entry :
|
||||
extensionPreferences.entrySet()) {
|
||||
Transformation transformation = factory.create(entry.getKey(), entry.getValue(), true);
|
||||
if (transformation != null) {
|
||||
result.add(transformation);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
private static void append(StringBuilder sb, Extension extension) {
|
||||
if (extension == null || extension.getName() == null || extension.getName().length() == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
sb.append(extension.getName());
|
||||
|
||||
for (Extension.Parameter p : extension.getParameters()) {
|
||||
sb.append(';');
|
||||
sb.append(p.getName());
|
||||
if (p.getValue() != null) {
|
||||
sb.append('=');
|
||||
sb.append(p.getValue());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* This only works for tokens. Quoted strings need more sophisticated
|
||||
* parsing.
|
||||
*/
|
||||
private static boolean headerContainsToken(HttpServletRequest req,
|
||||
String headerName, String target) {
|
||||
Enumeration<String> headers = req.getHeaders(headerName);
|
||||
while (headers.hasMoreElements()) {
|
||||
String header = headers.nextElement();
|
||||
String[] tokens = header.split(",");
|
||||
for (String token : tokens) {
|
||||
if (target.equalsIgnoreCase(token.trim())) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* This only works for tokens. Quoted strings need more sophisticated
|
||||
* parsing.
|
||||
*/
|
||||
private static List<String> getTokensFromHeader(HttpServletRequest req,
|
||||
String headerName) {
|
||||
List<String> result = new ArrayList<>();
|
||||
Enumeration<String> headers = req.getHeaders(headerName);
|
||||
while (headers.hasMoreElements()) {
|
||||
String header = headers.nextElement();
|
||||
String[] tokens = header.split(",");
|
||||
for (String token : tokens) {
|
||||
result.add(token.trim());
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
private static String getWebSocketAccept(String key) {
|
||||
byte[] digest = ConcurrentMessageDigest.digestSHA1(
|
||||
key.getBytes(StandardCharsets.ISO_8859_1), WS_ACCEPT);
|
||||
return Base64.encodeBase64String(digest);
|
||||
}
|
||||
}
|
||||
177
java/org/apache/tomcat/websocket/server/UriTemplate.java
Normal file
177
java/org/apache/tomcat/websocket/server/UriTemplate.java
Normal file
@@ -0,0 +1,177 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.apache.tomcat.websocket.server;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import javax.websocket.DeploymentException;
|
||||
|
||||
import org.apache.tomcat.util.res.StringManager;
|
||||
|
||||
/**
|
||||
* Extracts path parameters from URIs used to create web socket connections
|
||||
* using the URI template defined for the associated Endpoint.
|
||||
*/
|
||||
public class UriTemplate {
|
||||
|
||||
private static final StringManager sm = StringManager.getManager(UriTemplate.class);
|
||||
|
||||
private final String normalized;
|
||||
private final List<Segment> segments = new ArrayList<>();
|
||||
private final boolean hasParameters;
|
||||
|
||||
|
||||
public UriTemplate(String path) throws DeploymentException {
|
||||
|
||||
if (path == null || path.length() ==0 || !path.startsWith("/")) {
|
||||
throw new DeploymentException(
|
||||
sm.getString("uriTemplate.invalidPath", path));
|
||||
}
|
||||
|
||||
StringBuilder normalized = new StringBuilder(path.length());
|
||||
Set<String> paramNames = new HashSet<>();
|
||||
|
||||
// Include empty segments.
|
||||
String[] segments = path.split("/", -1);
|
||||
int paramCount = 0;
|
||||
int segmentCount = 0;
|
||||
|
||||
for (int i = 0; i < segments.length; i++) {
|
||||
String segment = segments[i];
|
||||
if (segment.length() == 0) {
|
||||
if (i == 0 || (i == segments.length - 1 && paramCount == 0)) {
|
||||
// Ignore the first empty segment as the path must always
|
||||
// start with '/'
|
||||
// Ending with a '/' is also OK for instances used for
|
||||
// matches but not for parameterised templates.
|
||||
continue;
|
||||
} else {
|
||||
// As per EG discussion, all other empty segments are
|
||||
// invalid
|
||||
throw new IllegalArgumentException(sm.getString(
|
||||
"uriTemplate.emptySegment", path));
|
||||
}
|
||||
}
|
||||
normalized.append('/');
|
||||
int index = -1;
|
||||
if (segment.startsWith("{") && segment.endsWith("}")) {
|
||||
index = segmentCount;
|
||||
segment = segment.substring(1, segment.length() - 1);
|
||||
normalized.append('{');
|
||||
normalized.append(paramCount++);
|
||||
normalized.append('}');
|
||||
if (!paramNames.add(segment)) {
|
||||
throw new IllegalArgumentException(sm.getString(
|
||||
"uriTemplate.duplicateParameter", segment));
|
||||
}
|
||||
} else {
|
||||
if (segment.contains("{") || segment.contains("}")) {
|
||||
throw new IllegalArgumentException(sm.getString(
|
||||
"uriTemplate.invalidSegment", segment, path));
|
||||
}
|
||||
normalized.append(segment);
|
||||
}
|
||||
this.segments.add(new Segment(index, segment));
|
||||
segmentCount++;
|
||||
}
|
||||
|
||||
this.normalized = normalized.toString();
|
||||
this.hasParameters = paramCount > 0;
|
||||
}
|
||||
|
||||
|
||||
public Map<String,String> match(UriTemplate candidate) {
|
||||
|
||||
Map<String,String> result = new HashMap<>();
|
||||
|
||||
// Should not happen but for safety
|
||||
if (candidate.getSegmentCount() != getSegmentCount()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
Iterator<Segment> candidateSegments =
|
||||
candidate.getSegments().iterator();
|
||||
Iterator<Segment> targetSegments = segments.iterator();
|
||||
|
||||
while (candidateSegments.hasNext()) {
|
||||
Segment candidateSegment = candidateSegments.next();
|
||||
Segment targetSegment = targetSegments.next();
|
||||
|
||||
if (targetSegment.getParameterIndex() == -1) {
|
||||
// Not a parameter - values must match
|
||||
if (!targetSegment.getValue().equals(
|
||||
candidateSegment.getValue())) {
|
||||
// Not a match. Stop here
|
||||
return null;
|
||||
}
|
||||
} else {
|
||||
// Parameter
|
||||
result.put(targetSegment.getValue(),
|
||||
candidateSegment.getValue());
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
public boolean hasParameters() {
|
||||
return hasParameters;
|
||||
}
|
||||
|
||||
|
||||
public int getSegmentCount() {
|
||||
return segments.size();
|
||||
}
|
||||
|
||||
|
||||
public String getNormalizedPath() {
|
||||
return normalized;
|
||||
}
|
||||
|
||||
|
||||
private List<Segment> getSegments() {
|
||||
return segments;
|
||||
}
|
||||
|
||||
|
||||
private static class Segment {
|
||||
private final int parameterIndex;
|
||||
private final String value;
|
||||
|
||||
public Segment(int parameterIndex, String value) {
|
||||
this.parameterIndex = parameterIndex;
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
|
||||
public int getParameterIndex() {
|
||||
return parameterIndex;
|
||||
}
|
||||
|
||||
|
||||
public String getValue() {
|
||||
return value;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.apache.tomcat.websocket.server;
|
||||
|
||||
import javax.servlet.ServletContext;
|
||||
import javax.servlet.ServletContextEvent;
|
||||
import javax.servlet.ServletContextListener;
|
||||
|
||||
/**
|
||||
* In normal usage, this {@link ServletContextListener} does not need to be
|
||||
* explicitly configured as the {@link WsSci} performs all the necessary
|
||||
* bootstrap and installs this listener in the {@link ServletContext}. If the
|
||||
* {@link WsSci} is disabled, this listener must be added manually to every
|
||||
* {@link ServletContext} that uses WebSocket to bootstrap the
|
||||
* {@link WsServerContainer} correctly.
|
||||
*/
|
||||
public class WsContextListener implements ServletContextListener {
|
||||
|
||||
@Override
|
||||
public void contextInitialized(ServletContextEvent sce) {
|
||||
ServletContext sc = sce.getServletContext();
|
||||
// Don't trigger WebSocket initialization if a WebSocket Server
|
||||
// Container is already present
|
||||
if (sc.getAttribute(Constants.SERVER_CONTAINER_SERVLET_CONTEXT_ATTRIBUTE) == null) {
|
||||
WsSci.init(sce.getServletContext(), false);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void contextDestroyed(ServletContextEvent sce) {
|
||||
ServletContext sc = sce.getServletContext();
|
||||
Object obj = sc.getAttribute(Constants.SERVER_CONTAINER_SERVLET_CONTEXT_ATTRIBUTE);
|
||||
if (obj instanceof WsServerContainer) {
|
||||
((WsServerContainer) obj).destroy();
|
||||
}
|
||||
}
|
||||
}
|
||||
86
java/org/apache/tomcat/websocket/server/WsFilter.java
Normal file
86
java/org/apache/tomcat/websocket/server/WsFilter.java
Normal file
@@ -0,0 +1,86 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.apache.tomcat.websocket.server;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import javax.servlet.Filter;
|
||||
import javax.servlet.FilterChain;
|
||||
import javax.servlet.FilterConfig;
|
||||
import javax.servlet.ServletException;
|
||||
import javax.servlet.ServletRequest;
|
||||
import javax.servlet.ServletResponse;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
/**
|
||||
* Handles the initial HTTP connection for WebSocket connections.
|
||||
*/
|
||||
public class WsFilter implements Filter {
|
||||
|
||||
private WsServerContainer sc;
|
||||
|
||||
|
||||
@Override
|
||||
public void init(FilterConfig filterConfig) throws ServletException {
|
||||
sc = (WsServerContainer) filterConfig.getServletContext().getAttribute(
|
||||
Constants.SERVER_CONTAINER_SERVLET_CONTEXT_ATTRIBUTE);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void doFilter(ServletRequest request, ServletResponse response,
|
||||
FilterChain chain) throws IOException, ServletException {
|
||||
|
||||
// This filter only needs to handle WebSocket upgrade requests
|
||||
if (!sc.areEndpointsRegistered() ||
|
||||
!UpgradeUtil.isWebSocketUpgradeRequest(request, response)) {
|
||||
chain.doFilter(request, response);
|
||||
return;
|
||||
}
|
||||
|
||||
// HTTP request with an upgrade header for WebSocket present
|
||||
HttpServletRequest req = (HttpServletRequest) request;
|
||||
HttpServletResponse resp = (HttpServletResponse) response;
|
||||
|
||||
// Check to see if this WebSocket implementation has a matching mapping
|
||||
String path;
|
||||
String pathInfo = req.getPathInfo();
|
||||
if (pathInfo == null) {
|
||||
path = req.getServletPath();
|
||||
} else {
|
||||
path = req.getServletPath() + pathInfo;
|
||||
}
|
||||
WsMappingResult mappingResult = sc.findMapping(path);
|
||||
|
||||
if (mappingResult == null) {
|
||||
// No endpoint registered for the requested path. Let the
|
||||
// application handle it (it might redirect or forward for example)
|
||||
chain.doFilter(request, response);
|
||||
return;
|
||||
}
|
||||
|
||||
UpgradeUtil.doUpgrade(sc, req, resp, mappingResult.getConfig(),
|
||||
mappingResult.getPathParams());
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void destroy() {
|
||||
// NO-OP
|
||||
}
|
||||
}
|
||||
192
java/org/apache/tomcat/websocket/server/WsFrameServer.java
Normal file
192
java/org/apache/tomcat/websocket/server/WsFrameServer.java
Normal file
@@ -0,0 +1,192 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.apache.tomcat.websocket.server;
|
||||
|
||||
import java.io.EOFException;
|
||||
import java.io.IOException;
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
import org.apache.juli.logging.Log;
|
||||
import org.apache.juli.logging.LogFactory;
|
||||
import org.apache.tomcat.util.net.AbstractEndpoint.Handler.SocketState;
|
||||
import org.apache.tomcat.util.net.SocketEvent;
|
||||
import org.apache.tomcat.util.net.SocketWrapperBase;
|
||||
import org.apache.tomcat.util.res.StringManager;
|
||||
import org.apache.tomcat.websocket.Transformation;
|
||||
import org.apache.tomcat.websocket.WsFrameBase;
|
||||
import org.apache.tomcat.websocket.WsIOException;
|
||||
import org.apache.tomcat.websocket.WsSession;
|
||||
|
||||
public class WsFrameServer extends WsFrameBase {
|
||||
|
||||
private final Log log = LogFactory.getLog(WsFrameServer.class); // must not be static
|
||||
private static final StringManager sm = StringManager.getManager(WsFrameServer.class);
|
||||
|
||||
private final SocketWrapperBase<?> socketWrapper;
|
||||
private final ClassLoader applicationClassLoader;
|
||||
|
||||
|
||||
public WsFrameServer(SocketWrapperBase<?> socketWrapper, WsSession wsSession,
|
||||
Transformation transformation, ClassLoader applicationClassLoader) {
|
||||
super(wsSession, transformation);
|
||||
this.socketWrapper = socketWrapper;
|
||||
this.applicationClassLoader = applicationClassLoader;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Called when there is data in the ServletInputStream to process.
|
||||
*
|
||||
* @throws IOException if an I/O error occurs while processing the available
|
||||
* data
|
||||
*/
|
||||
private void onDataAvailable() throws IOException {
|
||||
if (log.isDebugEnabled()) {
|
||||
log.debug("wsFrameServer.onDataAvailable");
|
||||
}
|
||||
if (isOpen() && inputBuffer.hasRemaining() && !isSuspended()) {
|
||||
// There might be a data that was left in the buffer when
|
||||
// the read has been suspended.
|
||||
// Consume this data before reading from the socket.
|
||||
processInputBuffer();
|
||||
}
|
||||
|
||||
while (isOpen() && !isSuspended()) {
|
||||
// Fill up the input buffer with as much data as we can
|
||||
inputBuffer.mark();
|
||||
inputBuffer.position(inputBuffer.limit()).limit(inputBuffer.capacity());
|
||||
int read = socketWrapper.read(false, inputBuffer);
|
||||
inputBuffer.limit(inputBuffer.position()).reset();
|
||||
if (read < 0) {
|
||||
throw new EOFException();
|
||||
} else if (read == 0) {
|
||||
return;
|
||||
}
|
||||
if (log.isDebugEnabled()) {
|
||||
log.debug(sm.getString("wsFrameServer.bytesRead", Integer.toString(read)));
|
||||
}
|
||||
processInputBuffer();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
protected boolean isMasked() {
|
||||
// Data is from the client so it should be masked
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
protected Transformation getTransformation() {
|
||||
// Overridden to make it visible to other classes in this package
|
||||
return super.getTransformation();
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
protected boolean isOpen() {
|
||||
// Overridden to make it visible to other classes in this package
|
||||
return super.isOpen();
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
protected Log getLog() {
|
||||
return log;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
protected void sendMessageText(boolean last) throws WsIOException {
|
||||
ClassLoader cl = Thread.currentThread().getContextClassLoader();
|
||||
try {
|
||||
Thread.currentThread().setContextClassLoader(applicationClassLoader);
|
||||
super.sendMessageText(last);
|
||||
} finally {
|
||||
Thread.currentThread().setContextClassLoader(cl);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
protected void sendMessageBinary(ByteBuffer msg, boolean last) throws WsIOException {
|
||||
ClassLoader cl = Thread.currentThread().getContextClassLoader();
|
||||
try {
|
||||
Thread.currentThread().setContextClassLoader(applicationClassLoader);
|
||||
super.sendMessageBinary(msg, last);
|
||||
} finally {
|
||||
Thread.currentThread().setContextClassLoader(cl);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
protected void resumeProcessing() {
|
||||
socketWrapper.processSocket(SocketEvent.OPEN_READ, true);
|
||||
}
|
||||
|
||||
SocketState notifyDataAvailable() throws IOException {
|
||||
while (isOpen()) {
|
||||
switch (getReadState()) {
|
||||
case WAITING:
|
||||
if (!changeReadState(ReadState.WAITING, ReadState.PROCESSING)) {
|
||||
continue;
|
||||
}
|
||||
try {
|
||||
return doOnDataAvailable();
|
||||
} catch (IOException e) {
|
||||
changeReadState(ReadState.CLOSING);
|
||||
throw e;
|
||||
}
|
||||
case SUSPENDING_WAIT:
|
||||
if (!changeReadState(ReadState.SUSPENDING_WAIT, ReadState.SUSPENDED)) {
|
||||
continue;
|
||||
}
|
||||
return SocketState.SUSPENDED;
|
||||
default:
|
||||
throw new IllegalStateException(
|
||||
sm.getString("wsFrameServer.illegalReadState", getReadState()));
|
||||
}
|
||||
}
|
||||
|
||||
return SocketState.CLOSED;
|
||||
}
|
||||
|
||||
private SocketState doOnDataAvailable() throws IOException {
|
||||
onDataAvailable();
|
||||
while (isOpen()) {
|
||||
switch (getReadState()) {
|
||||
case PROCESSING:
|
||||
if (!changeReadState(ReadState.PROCESSING, ReadState.WAITING)) {
|
||||
continue;
|
||||
}
|
||||
return SocketState.UPGRADED;
|
||||
case SUSPENDING_PROCESS:
|
||||
if (!changeReadState(ReadState.SUSPENDING_PROCESS, ReadState.SUSPENDED)) {
|
||||
continue;
|
||||
}
|
||||
return SocketState.SUSPENDED;
|
||||
default:
|
||||
throw new IllegalStateException(
|
||||
sm.getString("wsFrameServer.illegalReadState", getReadState()));
|
||||
}
|
||||
}
|
||||
|
||||
return SocketState.CLOSED;
|
||||
}
|
||||
}
|
||||
189
java/org/apache/tomcat/websocket/server/WsHandshakeRequest.java
Normal file
189
java/org/apache/tomcat/websocket/server/WsHandshakeRequest.java
Normal file
@@ -0,0 +1,189 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.apache.tomcat.websocket.server;
|
||||
|
||||
import java.net.URI;
|
||||
import java.net.URISyntaxException;
|
||||
import java.security.Principal;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.Enumeration;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.websocket.server.HandshakeRequest;
|
||||
|
||||
import org.apache.tomcat.util.collections.CaseInsensitiveKeyMap;
|
||||
import org.apache.tomcat.util.res.StringManager;
|
||||
|
||||
/**
|
||||
* Represents the request that this session was opened under.
|
||||
*/
|
||||
public class WsHandshakeRequest implements HandshakeRequest {
|
||||
|
||||
private static final StringManager sm = StringManager.getManager(WsHandshakeRequest.class);
|
||||
|
||||
private final URI requestUri;
|
||||
private final Map<String,List<String>> parameterMap;
|
||||
private final String queryString;
|
||||
private final Principal userPrincipal;
|
||||
private final Map<String,List<String>> headers;
|
||||
private final Object httpSession;
|
||||
|
||||
private volatile HttpServletRequest request;
|
||||
|
||||
|
||||
public WsHandshakeRequest(HttpServletRequest request, Map<String,String> pathParams) {
|
||||
|
||||
this.request = request;
|
||||
|
||||
queryString = request.getQueryString();
|
||||
userPrincipal = request.getUserPrincipal();
|
||||
httpSession = request.getSession(false);
|
||||
requestUri = buildRequestUri(request);
|
||||
|
||||
// ParameterMap
|
||||
Map<String,String[]> originalParameters = request.getParameterMap();
|
||||
Map<String,List<String>> newParameters =
|
||||
new HashMap<>(originalParameters.size());
|
||||
for (Entry<String,String[]> entry : originalParameters.entrySet()) {
|
||||
newParameters.put(entry.getKey(),
|
||||
Collections.unmodifiableList(
|
||||
Arrays.asList(entry.getValue())));
|
||||
}
|
||||
for (Entry<String,String> entry : pathParams.entrySet()) {
|
||||
newParameters.put(entry.getKey(), Collections.singletonList(entry.getValue()));
|
||||
}
|
||||
parameterMap = Collections.unmodifiableMap(newParameters);
|
||||
|
||||
// Headers
|
||||
Map<String,List<String>> newHeaders = new CaseInsensitiveKeyMap<>();
|
||||
|
||||
Enumeration<String> headerNames = request.getHeaderNames();
|
||||
while (headerNames.hasMoreElements()) {
|
||||
String headerName = headerNames.nextElement();
|
||||
|
||||
newHeaders.put(headerName, Collections.unmodifiableList(
|
||||
Collections.list(request.getHeaders(headerName))));
|
||||
}
|
||||
|
||||
headers = Collections.unmodifiableMap(newHeaders);
|
||||
}
|
||||
|
||||
@Override
|
||||
public URI getRequestURI() {
|
||||
return requestUri;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String,List<String>> getParameterMap() {
|
||||
return parameterMap;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getQueryString() {
|
||||
return queryString;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Principal getUserPrincipal() {
|
||||
return userPrincipal;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String,List<String>> getHeaders() {
|
||||
return headers;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isUserInRole(String role) {
|
||||
if (request == null) {
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
|
||||
return request.isUserInRole(role);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getHttpSession() {
|
||||
return httpSession;
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when the HandshakeRequest is no longer required. Since an instance
|
||||
* of this class retains a reference to the current HttpServletRequest that
|
||||
* reference needs to be cleared as the HttpServletRequest may be reused.
|
||||
*
|
||||
* There is no reason for instances of this class to be accessed once the
|
||||
* handshake has been completed.
|
||||
*/
|
||||
void finished() {
|
||||
request = null;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* See RequestUtil.getRequestURL()
|
||||
*/
|
||||
private static URI buildRequestUri(HttpServletRequest req) {
|
||||
|
||||
StringBuffer uri = new StringBuffer();
|
||||
String scheme = req.getScheme();
|
||||
int port = req.getServerPort();
|
||||
if (port < 0) {
|
||||
// Work around java.net.URL bug
|
||||
port = 80;
|
||||
}
|
||||
|
||||
if ("http".equals(scheme)) {
|
||||
uri.append("ws");
|
||||
} else if ("https".equals(scheme)) {
|
||||
uri.append("wss");
|
||||
} else {
|
||||
// Should never happen
|
||||
throw new IllegalArgumentException(
|
||||
sm.getString("wsHandshakeRequest.unknownScheme", scheme));
|
||||
}
|
||||
|
||||
uri.append("://");
|
||||
uri.append(req.getServerName());
|
||||
|
||||
if ((scheme.equals("http") && (port != 80))
|
||||
|| (scheme.equals("https") && (port != 443))) {
|
||||
uri.append(':');
|
||||
uri.append(port);
|
||||
}
|
||||
|
||||
uri.append(req.getRequestURI());
|
||||
|
||||
if (req.getQueryString() != null) {
|
||||
uri.append("?");
|
||||
uri.append(req.getQueryString());
|
||||
}
|
||||
|
||||
try {
|
||||
return new URI(uri.toString());
|
||||
} catch (URISyntaxException e) {
|
||||
// Should never happen
|
||||
throw new IllegalArgumentException(
|
||||
sm.getString("wsHandshakeRequest.invalidUri", uri.toString()), e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,248 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.apache.tomcat.websocket.server;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import javax.servlet.http.HttpSession;
|
||||
import javax.servlet.http.WebConnection;
|
||||
import javax.websocket.CloseReason;
|
||||
import javax.websocket.CloseReason.CloseCodes;
|
||||
import javax.websocket.DeploymentException;
|
||||
import javax.websocket.Endpoint;
|
||||
import javax.websocket.Extension;
|
||||
import javax.websocket.server.ServerEndpointConfig;
|
||||
|
||||
import org.apache.coyote.http11.upgrade.InternalHttpUpgradeHandler;
|
||||
import org.apache.juli.logging.Log;
|
||||
import org.apache.juli.logging.LogFactory;
|
||||
import org.apache.tomcat.util.net.AbstractEndpoint.Handler.SocketState;
|
||||
import org.apache.tomcat.util.net.SSLSupport;
|
||||
import org.apache.tomcat.util.net.SocketEvent;
|
||||
import org.apache.tomcat.util.net.SocketWrapperBase;
|
||||
import org.apache.tomcat.util.res.StringManager;
|
||||
import org.apache.tomcat.websocket.Transformation;
|
||||
import org.apache.tomcat.websocket.WsIOException;
|
||||
import org.apache.tomcat.websocket.WsSession;
|
||||
|
||||
/**
|
||||
* Servlet 3.1 HTTP upgrade handler for WebSocket connections.
|
||||
*/
|
||||
public class WsHttpUpgradeHandler implements InternalHttpUpgradeHandler {
|
||||
|
||||
private final Log log = LogFactory.getLog(WsHttpUpgradeHandler.class); // must not be static
|
||||
private static final StringManager sm = StringManager.getManager(WsHttpUpgradeHandler.class);
|
||||
|
||||
private final ClassLoader applicationClassLoader;
|
||||
|
||||
private SocketWrapperBase<?> socketWrapper;
|
||||
|
||||
private Endpoint ep;
|
||||
private ServerEndpointConfig serverEndpointConfig;
|
||||
private WsServerContainer webSocketContainer;
|
||||
private WsHandshakeRequest handshakeRequest;
|
||||
private List<Extension> negotiatedExtensions;
|
||||
private String subProtocol;
|
||||
private Transformation transformation;
|
||||
private Map<String,String> pathParameters;
|
||||
private boolean secure;
|
||||
private WebConnection connection;
|
||||
|
||||
private WsRemoteEndpointImplServer wsRemoteEndpointServer;
|
||||
private WsFrameServer wsFrame;
|
||||
private WsSession wsSession;
|
||||
|
||||
|
||||
public WsHttpUpgradeHandler() {
|
||||
applicationClassLoader = Thread.currentThread().getContextClassLoader();
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void setSocketWrapper(SocketWrapperBase<?> socketWrapper) {
|
||||
this.socketWrapper = socketWrapper;
|
||||
}
|
||||
|
||||
|
||||
public void preInit(Endpoint ep, ServerEndpointConfig serverEndpointConfig,
|
||||
WsServerContainer wsc, WsHandshakeRequest handshakeRequest,
|
||||
List<Extension> negotiatedExtensionsPhase2, String subProtocol,
|
||||
Transformation transformation, Map<String,String> pathParameters,
|
||||
boolean secure) {
|
||||
this.ep = ep;
|
||||
this.serverEndpointConfig = serverEndpointConfig;
|
||||
this.webSocketContainer = wsc;
|
||||
this.handshakeRequest = handshakeRequest;
|
||||
this.negotiatedExtensions = negotiatedExtensionsPhase2;
|
||||
this.subProtocol = subProtocol;
|
||||
this.transformation = transformation;
|
||||
this.pathParameters = pathParameters;
|
||||
this.secure = secure;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void init(WebConnection connection) {
|
||||
if (ep == null) {
|
||||
throw new IllegalStateException(
|
||||
sm.getString("wsHttpUpgradeHandler.noPreInit"));
|
||||
}
|
||||
|
||||
String httpSessionId = null;
|
||||
Object session = handshakeRequest.getHttpSession();
|
||||
if (session != null ) {
|
||||
httpSessionId = ((HttpSession) session).getId();
|
||||
}
|
||||
|
||||
// Need to call onOpen using the web application's class loader
|
||||
// Create the frame using the application's class loader so it can pick
|
||||
// up application specific config from the ServerContainerImpl
|
||||
Thread t = Thread.currentThread();
|
||||
ClassLoader cl = t.getContextClassLoader();
|
||||
t.setContextClassLoader(applicationClassLoader);
|
||||
try {
|
||||
wsRemoteEndpointServer = new WsRemoteEndpointImplServer(socketWrapper, webSocketContainer);
|
||||
wsSession = new WsSession(ep, wsRemoteEndpointServer,
|
||||
webSocketContainer, handshakeRequest.getRequestURI(),
|
||||
handshakeRequest.getParameterMap(),
|
||||
handshakeRequest.getQueryString(),
|
||||
handshakeRequest.getUserPrincipal(), httpSessionId,
|
||||
negotiatedExtensions, subProtocol, pathParameters, secure,
|
||||
serverEndpointConfig);
|
||||
wsFrame = new WsFrameServer(socketWrapper, wsSession, transformation,
|
||||
applicationClassLoader);
|
||||
// WsFrame adds the necessary final transformations. Copy the
|
||||
// completed transformation chain to the remote end point.
|
||||
wsRemoteEndpointServer.setTransformation(wsFrame.getTransformation());
|
||||
ep.onOpen(wsSession, serverEndpointConfig);
|
||||
webSocketContainer.registerSession(serverEndpointConfig.getPath(), wsSession);
|
||||
} catch (DeploymentException e) {
|
||||
throw new IllegalArgumentException(e);
|
||||
} finally {
|
||||
t.setContextClassLoader(cl);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public SocketState upgradeDispatch(SocketEvent status) {
|
||||
switch (status) {
|
||||
case OPEN_READ:
|
||||
try {
|
||||
return wsFrame.notifyDataAvailable();
|
||||
} catch (WsIOException ws) {
|
||||
close(ws.getCloseReason());
|
||||
} catch (IOException ioe) {
|
||||
onError(ioe);
|
||||
CloseReason cr = new CloseReason(
|
||||
CloseCodes.CLOSED_ABNORMALLY, ioe.getMessage());
|
||||
close(cr);
|
||||
}
|
||||
return SocketState.CLOSED;
|
||||
case OPEN_WRITE:
|
||||
wsRemoteEndpointServer.onWritePossible(false);
|
||||
break;
|
||||
case STOP:
|
||||
CloseReason cr = new CloseReason(CloseCodes.GOING_AWAY,
|
||||
sm.getString("wsHttpUpgradeHandler.serverStop"));
|
||||
try {
|
||||
wsSession.close(cr);
|
||||
} catch (IOException ioe) {
|
||||
onError(ioe);
|
||||
cr = new CloseReason(
|
||||
CloseCodes.CLOSED_ABNORMALLY, ioe.getMessage());
|
||||
close(cr);
|
||||
return SocketState.CLOSED;
|
||||
}
|
||||
break;
|
||||
case ERROR:
|
||||
String msg = sm.getString("wsHttpUpgradeHandler.closeOnError");
|
||||
wsSession.doClose(new CloseReason(CloseCodes.GOING_AWAY, msg),
|
||||
new CloseReason(CloseCodes.CLOSED_ABNORMALLY, msg));
|
||||
//$FALL-THROUGH$
|
||||
case DISCONNECT:
|
||||
case TIMEOUT:
|
||||
case CONNECT_FAIL:
|
||||
return SocketState.CLOSED;
|
||||
|
||||
}
|
||||
if (wsFrame.isOpen()) {
|
||||
return SocketState.UPGRADED;
|
||||
} else {
|
||||
return SocketState.CLOSED;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void timeoutAsync(long now) {
|
||||
// NO-OP
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void pause() {
|
||||
// NO-OP
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void destroy() {
|
||||
if (connection != null) {
|
||||
try {
|
||||
connection.close();
|
||||
} catch (Exception e) {
|
||||
log.error(sm.getString("wsHttpUpgradeHandler.destroyFailed"), e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private void onError(Throwable throwable) {
|
||||
// Need to call onError using the web application's class loader
|
||||
Thread t = Thread.currentThread();
|
||||
ClassLoader cl = t.getContextClassLoader();
|
||||
t.setContextClassLoader(applicationClassLoader);
|
||||
try {
|
||||
ep.onError(wsSession, throwable);
|
||||
} finally {
|
||||
t.setContextClassLoader(cl);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private void close(CloseReason cr) {
|
||||
/*
|
||||
* Any call to this method is a result of a problem reading from the
|
||||
* client. At this point that state of the connection is unknown.
|
||||
* Attempt to send a close frame to the client and then close the socket
|
||||
* immediately. There is no point in waiting for a close frame from the
|
||||
* client because there is no guarantee that we can recover from
|
||||
* whatever messed up state the client put the connection into.
|
||||
*/
|
||||
wsSession.onClose(cr);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void setSslSupport(SSLSupport sslSupport) {
|
||||
// NO-OP. WebSocket has no requirement to access the TLS information
|
||||
// associated with the underlying connection.
|
||||
}
|
||||
}
|
||||
44
java/org/apache/tomcat/websocket/server/WsMappingResult.java
Normal file
44
java/org/apache/tomcat/websocket/server/WsMappingResult.java
Normal file
@@ -0,0 +1,44 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.apache.tomcat.websocket.server;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import javax.websocket.server.ServerEndpointConfig;
|
||||
|
||||
class WsMappingResult {
|
||||
|
||||
private final ServerEndpointConfig config;
|
||||
private final Map<String,String> pathParams;
|
||||
|
||||
|
||||
WsMappingResult(ServerEndpointConfig config,
|
||||
Map<String,String> pathParams) {
|
||||
this.config = config;
|
||||
this.pathParams = pathParams;
|
||||
}
|
||||
|
||||
|
||||
ServerEndpointConfig getConfig() {
|
||||
return config;
|
||||
}
|
||||
|
||||
|
||||
Map<String,String> getPathParams() {
|
||||
return pathParams;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.apache.tomcat.websocket.server;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
import javax.websocket.Decoder;
|
||||
import javax.websocket.Encoder;
|
||||
import javax.websocket.Extension;
|
||||
import javax.websocket.server.ServerEndpointConfig;
|
||||
|
||||
/**
|
||||
* Wraps the provided {@link ServerEndpointConfig} and provides a per session
|
||||
* view - the difference being that the map returned by {@link
|
||||
* #getUserProperties()} is unique to this instance rather than shared with the
|
||||
* wrapped {@link ServerEndpointConfig}.
|
||||
*/
|
||||
class WsPerSessionServerEndpointConfig implements ServerEndpointConfig {
|
||||
|
||||
private final ServerEndpointConfig perEndpointConfig;
|
||||
private final Map<String,Object> perSessionUserProperties =
|
||||
new ConcurrentHashMap<>();
|
||||
|
||||
WsPerSessionServerEndpointConfig(ServerEndpointConfig perEndpointConfig) {
|
||||
this.perEndpointConfig = perEndpointConfig;
|
||||
perSessionUserProperties.putAll(perEndpointConfig.getUserProperties());
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Class<? extends Encoder>> getEncoders() {
|
||||
return perEndpointConfig.getEncoders();
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Class<? extends Decoder>> getDecoders() {
|
||||
return perEndpointConfig.getDecoders();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String,Object> getUserProperties() {
|
||||
return perSessionUserProperties;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<?> getEndpointClass() {
|
||||
return perEndpointConfig.getEndpointClass();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getPath() {
|
||||
return perEndpointConfig.getPath();
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> getSubprotocols() {
|
||||
return perEndpointConfig.getSubprotocols();
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Extension> getExtensions() {
|
||||
return perEndpointConfig.getExtensions();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Configurator getConfigurator() {
|
||||
return perEndpointConfig.getConfigurator();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,265 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.apache.tomcat.websocket.server;
|
||||
|
||||
import java.io.EOFException;
|
||||
import java.io.IOException;
|
||||
import java.net.SocketTimeoutException;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.concurrent.Executor;
|
||||
|
||||
import javax.websocket.SendHandler;
|
||||
import javax.websocket.SendResult;
|
||||
|
||||
import org.apache.juli.logging.Log;
|
||||
import org.apache.juli.logging.LogFactory;
|
||||
import org.apache.tomcat.util.net.AbstractEndpoint;
|
||||
import org.apache.tomcat.util.net.SocketWrapperBase;
|
||||
import org.apache.tomcat.util.res.StringManager;
|
||||
import org.apache.tomcat.websocket.Transformation;
|
||||
import org.apache.tomcat.websocket.WsRemoteEndpointImplBase;
|
||||
|
||||
/**
|
||||
* This is the server side {@link javax.websocket.RemoteEndpoint} implementation
|
||||
* - i.e. what the server uses to send data to the client.
|
||||
*/
|
||||
public class WsRemoteEndpointImplServer extends WsRemoteEndpointImplBase {
|
||||
|
||||
private static final StringManager sm =
|
||||
StringManager.getManager(WsRemoteEndpointImplServer.class);
|
||||
private final Log log = LogFactory.getLog(WsRemoteEndpointImplServer.class); // must not be static
|
||||
|
||||
private final SocketWrapperBase<?> socketWrapper;
|
||||
private final WsWriteTimeout wsWriteTimeout;
|
||||
private volatile SendHandler handler = null;
|
||||
private volatile ByteBuffer[] buffers = null;
|
||||
|
||||
private volatile long timeoutExpiry = -1;
|
||||
|
||||
public WsRemoteEndpointImplServer(SocketWrapperBase<?> socketWrapper,
|
||||
WsServerContainer serverContainer) {
|
||||
this.socketWrapper = socketWrapper;
|
||||
this.wsWriteTimeout = serverContainer.getTimeout();
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
protected final boolean isMasked() {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
protected void doWrite(SendHandler handler, long blockingWriteTimeoutExpiry,
|
||||
ByteBuffer... buffers) {
|
||||
if (blockingWriteTimeoutExpiry == -1) {
|
||||
this.handler = handler;
|
||||
this.buffers = buffers;
|
||||
// This is definitely the same thread that triggered the write so a
|
||||
// dispatch will be required.
|
||||
onWritePossible(true);
|
||||
} else {
|
||||
// Blocking
|
||||
try {
|
||||
for (ByteBuffer buffer : buffers) {
|
||||
long timeout = blockingWriteTimeoutExpiry - System.currentTimeMillis();
|
||||
if (timeout <= 0) {
|
||||
SendResult sr = new SendResult(new SocketTimeoutException());
|
||||
handler.onResult(sr);
|
||||
return;
|
||||
}
|
||||
socketWrapper.setWriteTimeout(timeout);
|
||||
socketWrapper.write(true, buffer);
|
||||
}
|
||||
long timeout = blockingWriteTimeoutExpiry - System.currentTimeMillis();
|
||||
if (timeout <= 0) {
|
||||
SendResult sr = new SendResult(new SocketTimeoutException());
|
||||
handler.onResult(sr);
|
||||
return;
|
||||
}
|
||||
socketWrapper.setWriteTimeout(timeout);
|
||||
socketWrapper.flush(true);
|
||||
handler.onResult(SENDRESULT_OK);
|
||||
} catch (IOException e) {
|
||||
SendResult sr = new SendResult(e);
|
||||
handler.onResult(sr);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public void onWritePossible(boolean useDispatch) {
|
||||
ByteBuffer[] buffers = this.buffers;
|
||||
if (buffers == null) {
|
||||
// Servlet 3.1 will call the write listener once even if nothing
|
||||
// was written
|
||||
return;
|
||||
}
|
||||
boolean complete = false;
|
||||
try {
|
||||
socketWrapper.flush(false);
|
||||
// If this is false there will be a call back when it is true
|
||||
while (socketWrapper.isReadyForWrite()) {
|
||||
complete = true;
|
||||
for (ByteBuffer buffer : buffers) {
|
||||
if (buffer.hasRemaining()) {
|
||||
complete = false;
|
||||
socketWrapper.write(false, buffer);
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (complete) {
|
||||
socketWrapper.flush(false);
|
||||
complete = socketWrapper.isReadyForWrite();
|
||||
if (complete) {
|
||||
wsWriteTimeout.unregister(this);
|
||||
clearHandler(null, useDispatch);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
} catch (IOException | IllegalStateException e) {
|
||||
wsWriteTimeout.unregister(this);
|
||||
clearHandler(e, useDispatch);
|
||||
close();
|
||||
}
|
||||
|
||||
if (!complete) {
|
||||
// Async write is in progress
|
||||
long timeout = getSendTimeout();
|
||||
if (timeout > 0) {
|
||||
// Register with timeout thread
|
||||
timeoutExpiry = timeout + System.currentTimeMillis();
|
||||
wsWriteTimeout.register(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
protected void doClose() {
|
||||
if (handler != null) {
|
||||
// close() can be triggered by a wide range of scenarios. It is far
|
||||
// simpler just to always use a dispatch than it is to try and track
|
||||
// whether or not this method was called by the same thread that
|
||||
// triggered the write
|
||||
clearHandler(new EOFException(), true);
|
||||
}
|
||||
try {
|
||||
socketWrapper.close();
|
||||
} catch (Exception e) {
|
||||
if (log.isInfoEnabled()) {
|
||||
log.info(sm.getString("wsRemoteEndpointServer.closeFailed"), e);
|
||||
}
|
||||
}
|
||||
wsWriteTimeout.unregister(this);
|
||||
}
|
||||
|
||||
|
||||
protected long getTimeoutExpiry() {
|
||||
return timeoutExpiry;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Currently this is only called from the background thread so we could just
|
||||
* call clearHandler() with useDispatch == false but the method parameter
|
||||
* was added in case other callers started to use this method to make sure
|
||||
* that those callers think through what the correct value of useDispatch is
|
||||
* for them.
|
||||
*/
|
||||
protected void onTimeout(boolean useDispatch) {
|
||||
if (handler != null) {
|
||||
clearHandler(new SocketTimeoutException(), useDispatch);
|
||||
}
|
||||
close();
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
protected void setTransformation(Transformation transformation) {
|
||||
// Overridden purely so it is visible to other classes in this package
|
||||
super.setTransformation(transformation);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
*
|
||||
* @param t The throwable associated with any error that
|
||||
* occurred
|
||||
* @param useDispatch Should {@link SendHandler#onResult(SendResult)} be
|
||||
* called from a new thread, keeping in mind the
|
||||
* requirements of
|
||||
* {@link javax.websocket.RemoteEndpoint.Async}
|
||||
*/
|
||||
private void clearHandler(Throwable t, boolean useDispatch) {
|
||||
// Setting the result marks this (partial) message as
|
||||
// complete which means the next one may be sent which
|
||||
// could update the value of the handler. Therefore, keep a
|
||||
// local copy before signalling the end of the (partial)
|
||||
// message.
|
||||
SendHandler sh = handler;
|
||||
handler = null;
|
||||
buffers = null;
|
||||
if (sh != null) {
|
||||
if (useDispatch) {
|
||||
OnResultRunnable r = new OnResultRunnable(sh, t);
|
||||
AbstractEndpoint<?> endpoint = socketWrapper.getEndpoint();
|
||||
Executor containerExecutor = endpoint.getExecutor();
|
||||
if (endpoint.isRunning() && containerExecutor != null) {
|
||||
containerExecutor.execute(r);
|
||||
} else {
|
||||
// Can't use the executor so call the runnable directly.
|
||||
// This may not be strictly specification compliant in all
|
||||
// cases but during shutdown only close messages are going
|
||||
// to be sent so there should not be the issue of nested
|
||||
// calls leading to stack overflow as described in bug
|
||||
// 55715. The issues with nested calls was the reason for
|
||||
// the separate thread requirement in the specification.
|
||||
r.run();
|
||||
}
|
||||
} else {
|
||||
if (t == null) {
|
||||
sh.onResult(new SendResult());
|
||||
} else {
|
||||
sh.onResult(new SendResult(t));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private static class OnResultRunnable implements Runnable {
|
||||
|
||||
private final SendHandler sh;
|
||||
private final Throwable t;
|
||||
|
||||
private OnResultRunnable(SendHandler sh, Throwable t) {
|
||||
this.sh = sh;
|
||||
this.t = t;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
if (t == null) {
|
||||
sh.onResult(new SendResult());
|
||||
} else {
|
||||
sh.onResult(new SendResult(t));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
151
java/org/apache/tomcat/websocket/server/WsSci.java
Normal file
151
java/org/apache/tomcat/websocket/server/WsSci.java
Normal file
@@ -0,0 +1,151 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.apache.tomcat.websocket.server;
|
||||
|
||||
import java.lang.reflect.Modifier;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
import javax.servlet.ServletContainerInitializer;
|
||||
import javax.servlet.ServletContext;
|
||||
import javax.servlet.ServletException;
|
||||
import javax.servlet.annotation.HandlesTypes;
|
||||
import javax.websocket.ContainerProvider;
|
||||
import javax.websocket.DeploymentException;
|
||||
import javax.websocket.Endpoint;
|
||||
import javax.websocket.server.ServerApplicationConfig;
|
||||
import javax.websocket.server.ServerEndpoint;
|
||||
import javax.websocket.server.ServerEndpointConfig;
|
||||
|
||||
import org.apache.tomcat.util.compat.JreCompat;
|
||||
|
||||
/**
|
||||
* Registers an interest in any class that is annotated with
|
||||
* {@link ServerEndpoint} so that Endpoint can be published via the WebSocket
|
||||
* server.
|
||||
*/
|
||||
@HandlesTypes({ServerEndpoint.class, ServerApplicationConfig.class,
|
||||
Endpoint.class})
|
||||
public class WsSci implements ServletContainerInitializer {
|
||||
|
||||
@Override
|
||||
public void onStartup(Set<Class<?>> clazzes, ServletContext ctx)
|
||||
throws ServletException {
|
||||
|
||||
WsServerContainer sc = init(ctx, true);
|
||||
|
||||
if (clazzes == null || clazzes.size() == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Group the discovered classes by type
|
||||
Set<ServerApplicationConfig> serverApplicationConfigs = new HashSet<>();
|
||||
Set<Class<? extends Endpoint>> scannedEndpointClazzes = new HashSet<>();
|
||||
Set<Class<?>> scannedPojoEndpoints = new HashSet<>();
|
||||
|
||||
try {
|
||||
// wsPackage is "javax.websocket."
|
||||
String wsPackage = ContainerProvider.class.getName();
|
||||
wsPackage = wsPackage.substring(0, wsPackage.lastIndexOf('.') + 1);
|
||||
for (Class<?> clazz : clazzes) {
|
||||
JreCompat jreCompat = JreCompat.getInstance();
|
||||
int modifiers = clazz.getModifiers();
|
||||
if (!Modifier.isPublic(modifiers) ||
|
||||
Modifier.isAbstract(modifiers) ||
|
||||
Modifier.isInterface(modifiers) ||
|
||||
!jreCompat.isExported(clazz)) {
|
||||
// Non-public, abstract, interface or not in an exported
|
||||
// package (Java 9+) - skip it.
|
||||
continue;
|
||||
}
|
||||
// Protect against scanning the WebSocket API JARs
|
||||
if (clazz.getName().startsWith(wsPackage)) {
|
||||
continue;
|
||||
}
|
||||
if (ServerApplicationConfig.class.isAssignableFrom(clazz)) {
|
||||
serverApplicationConfigs.add(
|
||||
(ServerApplicationConfig) clazz.getConstructor().newInstance());
|
||||
}
|
||||
if (Endpoint.class.isAssignableFrom(clazz)) {
|
||||
@SuppressWarnings("unchecked")
|
||||
Class<? extends Endpoint> endpoint =
|
||||
(Class<? extends Endpoint>) clazz;
|
||||
scannedEndpointClazzes.add(endpoint);
|
||||
}
|
||||
if (clazz.isAnnotationPresent(ServerEndpoint.class)) {
|
||||
scannedPojoEndpoints.add(clazz);
|
||||
}
|
||||
}
|
||||
} catch (ReflectiveOperationException e) {
|
||||
throw new ServletException(e);
|
||||
}
|
||||
|
||||
// Filter the results
|
||||
Set<ServerEndpointConfig> filteredEndpointConfigs = new HashSet<>();
|
||||
Set<Class<?>> filteredPojoEndpoints = new HashSet<>();
|
||||
|
||||
if (serverApplicationConfigs.isEmpty()) {
|
||||
filteredPojoEndpoints.addAll(scannedPojoEndpoints);
|
||||
} else {
|
||||
for (ServerApplicationConfig config : serverApplicationConfigs) {
|
||||
Set<ServerEndpointConfig> configFilteredEndpoints =
|
||||
config.getEndpointConfigs(scannedEndpointClazzes);
|
||||
if (configFilteredEndpoints != null) {
|
||||
filteredEndpointConfigs.addAll(configFilteredEndpoints);
|
||||
}
|
||||
Set<Class<?>> configFilteredPojos =
|
||||
config.getAnnotatedEndpointClasses(
|
||||
scannedPojoEndpoints);
|
||||
if (configFilteredPojos != null) {
|
||||
filteredPojoEndpoints.addAll(configFilteredPojos);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
// Deploy endpoints
|
||||
for (ServerEndpointConfig config : filteredEndpointConfigs) {
|
||||
sc.addEndpoint(config);
|
||||
}
|
||||
// Deploy POJOs
|
||||
for (Class<?> clazz : filteredPojoEndpoints) {
|
||||
sc.addEndpoint(clazz, true);
|
||||
}
|
||||
} catch (DeploymentException e) {
|
||||
throw new ServletException(e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static WsServerContainer init(ServletContext servletContext,
|
||||
boolean initBySciMechanism) {
|
||||
|
||||
WsServerContainer sc = new WsServerContainer(servletContext);
|
||||
|
||||
servletContext.setAttribute(
|
||||
Constants.SERVER_CONTAINER_SERVLET_CONTEXT_ATTRIBUTE, sc);
|
||||
|
||||
servletContext.addListener(new WsSessionListener(sc));
|
||||
// Can't register the ContextListener again if the ContextListener is
|
||||
// calling this method
|
||||
if (initBySciMechanism) {
|
||||
servletContext.addListener(new WsContextListener());
|
||||
}
|
||||
|
||||
return sc;
|
||||
}
|
||||
}
|
||||
534
java/org/apache/tomcat/websocket/server/WsServerContainer.java
Normal file
534
java/org/apache/tomcat/websocket/server/WsServerContainer.java
Normal file
@@ -0,0 +1,534 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.apache.tomcat.websocket.server;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.EnumSet;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.ConcurrentMap;
|
||||
import java.util.concurrent.ConcurrentSkipListMap;
|
||||
|
||||
import javax.servlet.DispatcherType;
|
||||
import javax.servlet.FilterRegistration;
|
||||
import javax.servlet.ServletContext;
|
||||
import javax.servlet.ServletException;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import javax.websocket.CloseReason;
|
||||
import javax.websocket.CloseReason.CloseCodes;
|
||||
import javax.websocket.DeploymentException;
|
||||
import javax.websocket.Encoder;
|
||||
import javax.websocket.server.ServerContainer;
|
||||
import javax.websocket.server.ServerEndpoint;
|
||||
import javax.websocket.server.ServerEndpointConfig;
|
||||
import javax.websocket.server.ServerEndpointConfig.Configurator;
|
||||
|
||||
import org.apache.tomcat.InstanceManager;
|
||||
import org.apache.tomcat.util.res.StringManager;
|
||||
import org.apache.tomcat.websocket.WsSession;
|
||||
import org.apache.tomcat.websocket.WsWebSocketContainer;
|
||||
import org.apache.tomcat.websocket.pojo.PojoMethodMapping;
|
||||
|
||||
/**
|
||||
* Provides a per class loader (i.e. per web application) instance of a
|
||||
* ServerContainer. Web application wide defaults may be configured by setting
|
||||
* the following servlet context initialisation parameters to the desired
|
||||
* values.
|
||||
* <ul>
|
||||
* <li>{@link Constants#BINARY_BUFFER_SIZE_SERVLET_CONTEXT_INIT_PARAM}</li>
|
||||
* <li>{@link Constants#TEXT_BUFFER_SIZE_SERVLET_CONTEXT_INIT_PARAM}</li>
|
||||
* </ul>
|
||||
*/
|
||||
public class WsServerContainer extends WsWebSocketContainer
|
||||
implements ServerContainer {
|
||||
|
||||
private static final StringManager sm = StringManager.getManager(WsServerContainer.class);
|
||||
|
||||
private static final CloseReason AUTHENTICATED_HTTP_SESSION_CLOSED =
|
||||
new CloseReason(CloseCodes.VIOLATED_POLICY,
|
||||
"This connection was established under an authenticated " +
|
||||
"HTTP session that has ended.");
|
||||
|
||||
private final WsWriteTimeout wsWriteTimeout = new WsWriteTimeout();
|
||||
|
||||
private final ServletContext servletContext;
|
||||
private final Map<String,ExactPathMatch> configExactMatchMap = new ConcurrentHashMap<>();
|
||||
private final ConcurrentMap<Integer,ConcurrentSkipListMap<String,TemplatePathMatch>> configTemplateMatchMap =
|
||||
new ConcurrentHashMap<>();
|
||||
private volatile boolean enforceNoAddAfterHandshake =
|
||||
org.apache.tomcat.websocket.Constants.STRICT_SPEC_COMPLIANCE;
|
||||
private volatile boolean addAllowed = true;
|
||||
private final ConcurrentMap<String,Set<WsSession>> authenticatedSessions =
|
||||
new ConcurrentHashMap<>();
|
||||
private volatile boolean endpointsRegistered = false;
|
||||
private volatile boolean deploymentFailed = false;
|
||||
|
||||
WsServerContainer(ServletContext servletContext) {
|
||||
|
||||
this.servletContext = servletContext;
|
||||
setInstanceManager((InstanceManager) servletContext.getAttribute(InstanceManager.class.getName()));
|
||||
|
||||
// Configure servlet context wide defaults
|
||||
String value = servletContext.getInitParameter(
|
||||
Constants.BINARY_BUFFER_SIZE_SERVLET_CONTEXT_INIT_PARAM);
|
||||
if (value != null) {
|
||||
setDefaultMaxBinaryMessageBufferSize(Integer.parseInt(value));
|
||||
}
|
||||
|
||||
value = servletContext.getInitParameter(
|
||||
Constants.TEXT_BUFFER_SIZE_SERVLET_CONTEXT_INIT_PARAM);
|
||||
if (value != null) {
|
||||
setDefaultMaxTextMessageBufferSize(Integer.parseInt(value));
|
||||
}
|
||||
|
||||
value = servletContext.getInitParameter(
|
||||
Constants.ENFORCE_NO_ADD_AFTER_HANDSHAKE_CONTEXT_INIT_PARAM);
|
||||
if (value != null) {
|
||||
setEnforceNoAddAfterHandshake(Boolean.parseBoolean(value));
|
||||
}
|
||||
|
||||
FilterRegistration.Dynamic fr = servletContext.addFilter(
|
||||
"Tomcat WebSocket (JSR356) Filter", new WsFilter());
|
||||
fr.setAsyncSupported(true);
|
||||
|
||||
EnumSet<DispatcherType> types = EnumSet.of(DispatcherType.REQUEST,
|
||||
DispatcherType.FORWARD);
|
||||
|
||||
fr.addMappingForUrlPatterns(types, true, "/*");
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Published the provided endpoint implementation at the specified path with
|
||||
* the specified configuration. {@link #WsServerContainer(ServletContext)}
|
||||
* must be called before calling this method.
|
||||
*
|
||||
* @param sec The configuration to use when creating endpoint instances
|
||||
* @throws DeploymentException if the endpoint cannot be published as
|
||||
* requested
|
||||
*/
|
||||
@Override
|
||||
public void addEndpoint(ServerEndpointConfig sec) throws DeploymentException {
|
||||
addEndpoint(sec, false);
|
||||
}
|
||||
|
||||
|
||||
void addEndpoint(ServerEndpointConfig sec, boolean fromAnnotatedPojo) throws DeploymentException {
|
||||
|
||||
if (enforceNoAddAfterHandshake && !addAllowed) {
|
||||
throw new DeploymentException(
|
||||
sm.getString("serverContainer.addNotAllowed"));
|
||||
}
|
||||
|
||||
if (servletContext == null) {
|
||||
throw new DeploymentException(
|
||||
sm.getString("serverContainer.servletContextMissing"));
|
||||
}
|
||||
|
||||
if (deploymentFailed) {
|
||||
throw new DeploymentException(sm.getString("serverContainer.failedDeployment",
|
||||
servletContext.getContextPath(), servletContext.getVirtualServerName()));
|
||||
}
|
||||
|
||||
try {
|
||||
String path = sec.getPath();
|
||||
|
||||
// Add method mapping to user properties
|
||||
PojoMethodMapping methodMapping = new PojoMethodMapping(sec.getEndpointClass(),
|
||||
sec.getDecoders(), path);
|
||||
if (methodMapping.getOnClose() != null || methodMapping.getOnOpen() != null
|
||||
|| methodMapping.getOnError() != null || methodMapping.hasMessageHandlers()) {
|
||||
sec.getUserProperties().put(org.apache.tomcat.websocket.pojo.Constants.POJO_METHOD_MAPPING_KEY,
|
||||
methodMapping);
|
||||
}
|
||||
|
||||
UriTemplate uriTemplate = new UriTemplate(path);
|
||||
if (uriTemplate.hasParameters()) {
|
||||
Integer key = Integer.valueOf(uriTemplate.getSegmentCount());
|
||||
ConcurrentSkipListMap<String,TemplatePathMatch> templateMatches =
|
||||
configTemplateMatchMap.get(key);
|
||||
if (templateMatches == null) {
|
||||
// Ensure that if concurrent threads execute this block they
|
||||
// all end up using the same ConcurrentSkipListMap instance
|
||||
templateMatches = new ConcurrentSkipListMap<>();
|
||||
configTemplateMatchMap.putIfAbsent(key, templateMatches);
|
||||
templateMatches = configTemplateMatchMap.get(key);
|
||||
}
|
||||
TemplatePathMatch newMatch = new TemplatePathMatch(sec, uriTemplate, fromAnnotatedPojo);
|
||||
TemplatePathMatch oldMatch = templateMatches.putIfAbsent(uriTemplate.getNormalizedPath(), newMatch);
|
||||
if (oldMatch != null) {
|
||||
// Note: This depends on Endpoint instances being added
|
||||
// before POJOs in WsSci#onStartup()
|
||||
if (oldMatch.isFromAnnotatedPojo() && !newMatch.isFromAnnotatedPojo() &&
|
||||
oldMatch.getConfig().getEndpointClass() == newMatch.getConfig().getEndpointClass()) {
|
||||
// The WebSocket spec says to ignore the new match in this case
|
||||
templateMatches.put(path, oldMatch);
|
||||
} else {
|
||||
// Duplicate uriTemplate;
|
||||
throw new DeploymentException(
|
||||
sm.getString("serverContainer.duplicatePaths", path,
|
||||
sec.getEndpointClass(),
|
||||
sec.getEndpointClass()));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Exact match
|
||||
ExactPathMatch newMatch = new ExactPathMatch(sec, fromAnnotatedPojo);
|
||||
ExactPathMatch oldMatch = configExactMatchMap.put(path, newMatch);
|
||||
if (oldMatch != null) {
|
||||
// Note: This depends on Endpoint instances being added
|
||||
// before POJOs in WsSci#onStartup()
|
||||
if (oldMatch.isFromAnnotatedPojo() && !newMatch.isFromAnnotatedPojo() &&
|
||||
oldMatch.getConfig().getEndpointClass() == newMatch.getConfig().getEndpointClass()) {
|
||||
// The WebSocket spec says to ignore the new match in this case
|
||||
configExactMatchMap.put(path, oldMatch);
|
||||
} else {
|
||||
// Duplicate path mappings
|
||||
throw new DeploymentException(
|
||||
sm.getString("serverContainer.duplicatePaths", path,
|
||||
oldMatch.getConfig().getEndpointClass(),
|
||||
sec.getEndpointClass()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
endpointsRegistered = true;
|
||||
} catch (DeploymentException de) {
|
||||
failDeployment();
|
||||
throw de;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Provides the equivalent of {@link #addEndpoint(ServerEndpointConfig)}
|
||||
* for publishing plain old java objects (POJOs) that have been annotated as
|
||||
* WebSocket endpoints.
|
||||
*
|
||||
* @param pojo The annotated POJO
|
||||
*/
|
||||
@Override
|
||||
public void addEndpoint(Class<?> pojo) throws DeploymentException {
|
||||
addEndpoint(pojo, false);
|
||||
}
|
||||
|
||||
|
||||
void addEndpoint(Class<?> pojo, boolean fromAnnotatedPojo) throws DeploymentException {
|
||||
|
||||
if (deploymentFailed) {
|
||||
throw new DeploymentException(sm.getString("serverContainer.failedDeployment",
|
||||
servletContext.getContextPath(), servletContext.getVirtualServerName()));
|
||||
}
|
||||
|
||||
ServerEndpointConfig sec;
|
||||
|
||||
try {
|
||||
ServerEndpoint annotation = pojo.getAnnotation(ServerEndpoint.class);
|
||||
if (annotation == null) {
|
||||
throw new DeploymentException(
|
||||
sm.getString("serverContainer.missingAnnotation",
|
||||
pojo.getName()));
|
||||
}
|
||||
String path = annotation.value();
|
||||
|
||||
// Validate encoders
|
||||
validateEncoders(annotation.encoders());
|
||||
|
||||
// ServerEndpointConfig
|
||||
Class<? extends Configurator> configuratorClazz =
|
||||
annotation.configurator();
|
||||
Configurator configurator = null;
|
||||
if (!configuratorClazz.equals(Configurator.class)) {
|
||||
try {
|
||||
configurator = annotation.configurator().getConstructor().newInstance();
|
||||
} catch (ReflectiveOperationException e) {
|
||||
throw new DeploymentException(sm.getString(
|
||||
"serverContainer.configuratorFail",
|
||||
annotation.configurator().getName(),
|
||||
pojo.getClass().getName()), e);
|
||||
}
|
||||
}
|
||||
sec = ServerEndpointConfig.Builder.create(pojo, path).
|
||||
decoders(Arrays.asList(annotation.decoders())).
|
||||
encoders(Arrays.asList(annotation.encoders())).
|
||||
subprotocols(Arrays.asList(annotation.subprotocols())).
|
||||
configurator(configurator).
|
||||
build();
|
||||
} catch (DeploymentException de) {
|
||||
failDeployment();
|
||||
throw de;
|
||||
}
|
||||
|
||||
addEndpoint(sec, fromAnnotatedPojo);
|
||||
}
|
||||
|
||||
|
||||
void failDeployment() {
|
||||
deploymentFailed = true;
|
||||
|
||||
// Clear all existing deployments
|
||||
endpointsRegistered = false;
|
||||
configExactMatchMap.clear();
|
||||
configTemplateMatchMap.clear();
|
||||
}
|
||||
|
||||
|
||||
boolean areEndpointsRegistered() {
|
||||
return endpointsRegistered;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Until the WebSocket specification provides such a mechanism, this Tomcat
|
||||
* proprietary method is provided to enable applications to programmatically
|
||||
* determine whether or not to upgrade an individual request to WebSocket.
|
||||
* <p>
|
||||
* Note: This method is not used by Tomcat but is used directly by
|
||||
* third-party code and must not be removed.
|
||||
*
|
||||
* @param request The request object to be upgraded
|
||||
* @param response The response object to be populated with the result of
|
||||
* the upgrade
|
||||
* @param sec The server endpoint to use to process the upgrade request
|
||||
* @param pathParams The path parameters associated with the upgrade request
|
||||
*
|
||||
* @throws ServletException If a configuration error prevents the upgrade
|
||||
* from taking place
|
||||
* @throws IOException If an I/O error occurs during the upgrade process
|
||||
*/
|
||||
public void doUpgrade(HttpServletRequest request,
|
||||
HttpServletResponse response, ServerEndpointConfig sec,
|
||||
Map<String,String> pathParams)
|
||||
throws ServletException, IOException {
|
||||
UpgradeUtil.doUpgrade(this, request, response, sec, pathParams);
|
||||
}
|
||||
|
||||
|
||||
public WsMappingResult findMapping(String path) {
|
||||
|
||||
// Prevent registering additional endpoints once the first attempt has
|
||||
// been made to use one
|
||||
if (addAllowed) {
|
||||
addAllowed = false;
|
||||
}
|
||||
|
||||
// Check an exact match. Simple case as there are no templates.
|
||||
ExactPathMatch match = configExactMatchMap.get(path);
|
||||
if (match != null) {
|
||||
return new WsMappingResult(match.getConfig(), Collections.<String, String>emptyMap());
|
||||
}
|
||||
|
||||
// No exact match. Need to look for template matches.
|
||||
UriTemplate pathUriTemplate = null;
|
||||
try {
|
||||
pathUriTemplate = new UriTemplate(path);
|
||||
} catch (DeploymentException e) {
|
||||
// Path is not valid so can't be matched to a WebSocketEndpoint
|
||||
return null;
|
||||
}
|
||||
|
||||
// Number of segments has to match
|
||||
Integer key = Integer.valueOf(pathUriTemplate.getSegmentCount());
|
||||
ConcurrentSkipListMap<String,TemplatePathMatch> templateMatches = configTemplateMatchMap.get(key);
|
||||
|
||||
if (templateMatches == null) {
|
||||
// No templates with an equal number of segments so there will be
|
||||
// no matches
|
||||
return null;
|
||||
}
|
||||
|
||||
// List is in alphabetical order of normalised templates.
|
||||
// Correct match is the first one that matches.
|
||||
ServerEndpointConfig sec = null;
|
||||
Map<String,String> pathParams = null;
|
||||
for (TemplatePathMatch templateMatch : templateMatches.values()) {
|
||||
pathParams = templateMatch.getUriTemplate().match(pathUriTemplate);
|
||||
if (pathParams != null) {
|
||||
sec = templateMatch.getConfig();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (sec == null) {
|
||||
// No match
|
||||
return null;
|
||||
}
|
||||
|
||||
return new WsMappingResult(sec, pathParams);
|
||||
}
|
||||
|
||||
|
||||
|
||||
public boolean isEnforceNoAddAfterHandshake() {
|
||||
return enforceNoAddAfterHandshake;
|
||||
}
|
||||
|
||||
|
||||
public void setEnforceNoAddAfterHandshake(
|
||||
boolean enforceNoAddAfterHandshake) {
|
||||
this.enforceNoAddAfterHandshake = enforceNoAddAfterHandshake;
|
||||
}
|
||||
|
||||
|
||||
protected WsWriteTimeout getTimeout() {
|
||||
return wsWriteTimeout;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*
|
||||
* Overridden to make it visible to other classes in this package.
|
||||
*/
|
||||
@Override
|
||||
protected void registerSession(Object key, WsSession wsSession) {
|
||||
super.registerSession(key, wsSession);
|
||||
if (wsSession.isOpen() &&
|
||||
wsSession.getUserPrincipal() != null &&
|
||||
wsSession.getHttpSessionId() != null) {
|
||||
registerAuthenticatedSession(wsSession,
|
||||
wsSession.getHttpSessionId());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*
|
||||
* Overridden to make it visible to other classes in this package.
|
||||
*/
|
||||
@Override
|
||||
protected void unregisterSession(Object key, WsSession wsSession) {
|
||||
if (wsSession.getUserPrincipal() != null &&
|
||||
wsSession.getHttpSessionId() != null) {
|
||||
unregisterAuthenticatedSession(wsSession,
|
||||
wsSession.getHttpSessionId());
|
||||
}
|
||||
super.unregisterSession(key, wsSession);
|
||||
}
|
||||
|
||||
|
||||
private void registerAuthenticatedSession(WsSession wsSession,
|
||||
String httpSessionId) {
|
||||
Set<WsSession> wsSessions = authenticatedSessions.get(httpSessionId);
|
||||
if (wsSessions == null) {
|
||||
wsSessions = Collections.newSetFromMap(
|
||||
new ConcurrentHashMap<WsSession,Boolean>());
|
||||
authenticatedSessions.putIfAbsent(httpSessionId, wsSessions);
|
||||
wsSessions = authenticatedSessions.get(httpSessionId);
|
||||
}
|
||||
wsSessions.add(wsSession);
|
||||
}
|
||||
|
||||
|
||||
private void unregisterAuthenticatedSession(WsSession wsSession,
|
||||
String httpSessionId) {
|
||||
Set<WsSession> wsSessions = authenticatedSessions.get(httpSessionId);
|
||||
// wsSessions will be null if the HTTP session has ended
|
||||
if (wsSessions != null) {
|
||||
wsSessions.remove(wsSession);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public void closeAuthenticatedSession(String httpSessionId) {
|
||||
Set<WsSession> wsSessions = authenticatedSessions.remove(httpSessionId);
|
||||
|
||||
if (wsSessions != null && !wsSessions.isEmpty()) {
|
||||
for (WsSession wsSession : wsSessions) {
|
||||
try {
|
||||
wsSession.close(AUTHENTICATED_HTTP_SESSION_CLOSED);
|
||||
} catch (IOException e) {
|
||||
// Any IOExceptions during close will have been caught and the
|
||||
// onError method called.
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private static void validateEncoders(Class<? extends Encoder>[] encoders)
|
||||
throws DeploymentException {
|
||||
|
||||
for (Class<? extends Encoder> encoder : encoders) {
|
||||
// Need to instantiate decoder to ensure it is valid and that
|
||||
// deployment can be failed if it is not
|
||||
@SuppressWarnings("unused")
|
||||
Encoder instance;
|
||||
try {
|
||||
encoder.getConstructor().newInstance();
|
||||
} catch(ReflectiveOperationException e) {
|
||||
throw new DeploymentException(sm.getString(
|
||||
"serverContainer.encoderFail", encoder.getName()), e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private static class TemplatePathMatch {
|
||||
private final ServerEndpointConfig config;
|
||||
private final UriTemplate uriTemplate;
|
||||
private final boolean fromAnnotatedPojo;
|
||||
|
||||
public TemplatePathMatch(ServerEndpointConfig config, UriTemplate uriTemplate,
|
||||
boolean fromAnnotatedPojo) {
|
||||
this.config = config;
|
||||
this.uriTemplate = uriTemplate;
|
||||
this.fromAnnotatedPojo = fromAnnotatedPojo;
|
||||
}
|
||||
|
||||
|
||||
public ServerEndpointConfig getConfig() {
|
||||
return config;
|
||||
}
|
||||
|
||||
|
||||
public UriTemplate getUriTemplate() {
|
||||
return uriTemplate;
|
||||
}
|
||||
|
||||
|
||||
public boolean isFromAnnotatedPojo() {
|
||||
return fromAnnotatedPojo;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private static class ExactPathMatch {
|
||||
private final ServerEndpointConfig config;
|
||||
private final boolean fromAnnotatedPojo;
|
||||
|
||||
public ExactPathMatch(ServerEndpointConfig config, boolean fromAnnotatedPojo) {
|
||||
this.config = config;
|
||||
this.fromAnnotatedPojo = fromAnnotatedPojo;
|
||||
}
|
||||
|
||||
|
||||
public ServerEndpointConfig getConfig() {
|
||||
return config;
|
||||
}
|
||||
|
||||
|
||||
public boolean isFromAnnotatedPojo() {
|
||||
return fromAnnotatedPojo;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.apache.tomcat.websocket.server;
|
||||
|
||||
import javax.servlet.http.HttpSessionEvent;
|
||||
import javax.servlet.http.HttpSessionListener;
|
||||
|
||||
public class WsSessionListener implements HttpSessionListener{
|
||||
|
||||
private final WsServerContainer wsServerContainer;
|
||||
|
||||
|
||||
public WsSessionListener(WsServerContainer wsServerContainer) {
|
||||
this.wsServerContainer = wsServerContainer;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void sessionCreated(HttpSessionEvent se) {
|
||||
// NO-OP
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void sessionDestroyed(HttpSessionEvent se) {
|
||||
wsServerContainer.closeAuthenticatedSession(se.getSession().getId());
|
||||
}
|
||||
}
|
||||
128
java/org/apache/tomcat/websocket/server/WsWriteTimeout.java
Normal file
128
java/org/apache/tomcat/websocket/server/WsWriteTimeout.java
Normal file
@@ -0,0 +1,128 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.apache.tomcat.websocket.server;
|
||||
|
||||
import java.util.Comparator;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ConcurrentSkipListSet;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
import org.apache.tomcat.websocket.BackgroundProcess;
|
||||
import org.apache.tomcat.websocket.BackgroundProcessManager;
|
||||
|
||||
/**
|
||||
* Provides timeouts for asynchronous web socket writes. On the server side we
|
||||
* only have access to {@link javax.servlet.ServletOutputStream} and
|
||||
* {@link javax.servlet.ServletInputStream} so there is no way to set a timeout
|
||||
* for writes to the client.
|
||||
*/
|
||||
public class WsWriteTimeout implements BackgroundProcess {
|
||||
|
||||
private final Set<WsRemoteEndpointImplServer> endpoints =
|
||||
new ConcurrentSkipListSet<>(new EndpointComparator());
|
||||
private final AtomicInteger count = new AtomicInteger(0);
|
||||
private int backgroundProcessCount = 0;
|
||||
private volatile int processPeriod = 1;
|
||||
|
||||
@Override
|
||||
public void backgroundProcess() {
|
||||
// This method gets called once a second.
|
||||
backgroundProcessCount ++;
|
||||
|
||||
if (backgroundProcessCount >= processPeriod) {
|
||||
backgroundProcessCount = 0;
|
||||
|
||||
long now = System.currentTimeMillis();
|
||||
for (WsRemoteEndpointImplServer endpoint : endpoints) {
|
||||
if (endpoint.getTimeoutExpiry() < now) {
|
||||
// Background thread, not the thread that triggered the
|
||||
// write so no need to use a dispatch
|
||||
endpoint.onTimeout(false);
|
||||
} else {
|
||||
// Endpoints are ordered by timeout expiry so if this point
|
||||
// is reached there is no need to check the remaining
|
||||
// endpoints
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void setProcessPeriod(int period) {
|
||||
this.processPeriod = period;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*
|
||||
* The default value is 1 which means asynchronous write timeouts are
|
||||
* processed every 1 second.
|
||||
*/
|
||||
@Override
|
||||
public int getProcessPeriod() {
|
||||
return processPeriod;
|
||||
}
|
||||
|
||||
|
||||
public void register(WsRemoteEndpointImplServer endpoint) {
|
||||
boolean result = endpoints.add(endpoint);
|
||||
if (result) {
|
||||
int newCount = count.incrementAndGet();
|
||||
if (newCount == 1) {
|
||||
BackgroundProcessManager.getInstance().register(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public void unregister(WsRemoteEndpointImplServer endpoint) {
|
||||
boolean result = endpoints.remove(endpoint);
|
||||
if (result) {
|
||||
int newCount = count.decrementAndGet();
|
||||
if (newCount == 0) {
|
||||
BackgroundProcessManager.getInstance().unregister(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Note: this comparator imposes orderings that are inconsistent with equals
|
||||
*/
|
||||
private static class EndpointComparator implements
|
||||
Comparator<WsRemoteEndpointImplServer> {
|
||||
|
||||
@Override
|
||||
public int compare(WsRemoteEndpointImplServer o1,
|
||||
WsRemoteEndpointImplServer o2) {
|
||||
|
||||
long t1 = o1.getTimeoutExpiry();
|
||||
long t2 = o2.getTimeoutExpiry();
|
||||
|
||||
if (t1 < t2) {
|
||||
return -1;
|
||||
} else if (t1 == t2) {
|
||||
return 0;
|
||||
} else {
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
21
java/org/apache/tomcat/websocket/server/package-info.java
Normal file
21
java/org/apache/tomcat/websocket/server/package-info.java
Normal file
@@ -0,0 +1,21 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
/**
|
||||
* Server-side specific implementation classes. These are in a separate package
|
||||
* to make packaging a pure client JAR simpler.
|
||||
*/
|
||||
package org.apache.tomcat.websocket.server;
|
||||
Reference in New Issue
Block a user