This commit is contained in:
2024-11-30 19:03:49 +08:00
commit 1e6763c160
3806 changed files with 737676 additions and 0 deletions

View 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
}
}
}
}

View 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;
}

View 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.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;
}
}
}

View 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;
}
}
}

View File

@@ -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);
}
}

View 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;
}
}

View 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;
}
}

View 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();
}

View 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;
}
}
}

View 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;
}
}

View 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
}
}

View 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;
}
}

View 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;
}
}

View 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;
}
}

View 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

View 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.

View 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

View 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é

View 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=書き込み操作を完了しようとすると、書き込みが進行中であることを示すフラグがfalsetrueであったはずですであることが判明しました。
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}]がレスポンスで返されました。

View 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}]이(가) 응답에서 반환되었습니다.

View 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}]

View File

@@ -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连接

View 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;
}
}

View File

@@ -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
}

View 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;
}
}

View 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();
}
}

View File

@@ -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;
}
}

View 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();
}

View 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));
}
}
}

View 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
}

View 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;
}
}
}

View 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();
}

View 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();
}
}

View 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;
}
}

View 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;
}
}

File diff suppressed because it is too large Load Diff

View 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);
}
}
}
}

View 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;
}
}

View 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;
}
}

View 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;
}
}

View 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);
}
}

View 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);
}
}

View 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);
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -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();
}
}

View 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();
}
}

File diff suppressed because it is too large Load Diff

View 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
}
}

View 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.
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.

View File

@@ -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

View File

@@ -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

View 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.
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 dappeler 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

View 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.
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プリミティブ、またはそれらのボックス版のみです。

View 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.
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된 파라미터들은 오직 문자열들, 또는 자바 원시타입들 또는 그것들의 박싱된 버전들이어야 합니다.

View File

@@ -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}]

View File

@@ -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原语或盒装版本。

View 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;
}
}

View 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.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);
}
}

View File

@@ -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);
}
}

View File

@@ -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);
}
}
}

View File

@@ -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);
}
}

View 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.
*/
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);
}
}

View File

@@ -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);
}
}

View File

@@ -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();
}

View 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.
*/
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();
}
}
}

View File

@@ -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
}
}

View File

@@ -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();
}
}
}

View 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
}
}

View 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()));
}
}

View 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;

View 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
}
}

View File

@@ -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
}
}

View 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.
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

View 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.
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.

View 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.
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

View 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.
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

View 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.
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コネクションを正常に閉じることができませんでした

View 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.
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의 연결을 깨끗하게 닫지 못했습니다.

View File

@@ -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 连接

View 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);
}
}

View 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;
}
}
}

View 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.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();
}
}
}

View 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
}
}

View 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;
}
}

View 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);
}
}
}

View File

@@ -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.
}
}

View 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;
}
}

View File

@@ -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();
}
}

View File

@@ -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));
}
}
}
}

View 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;
}
}

View 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;
}
}
}

View 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.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());
}
}

View 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;
}
}
}
}

View 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;