init
This commit is contained in:
@@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user