Please wait. This can take some minutes ...
Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance.
Project price only 1 $
You can buy this project and download/modify it how often you want.
org.aoju.bus.http.socket.RealWebSocket Maven / Gradle / Ivy
/*********************************************************************************
* *
* The MIT License (MIT) *
* *
* Copyright (c) 2015-2022 aoju.org and other contributors. *
* *
* Permission is hereby granted, free of charge, to any person obtaining a copy *
* of this software and associated documentation files (the "Software"), to deal *
* in the Software without restriction, including without limitation the rights *
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell *
* copies of the Software, and to permit persons to whom the Software is *
* furnished to do so, subject to the following conditions: *
* *
* The above copyright notice and this permission notice shall be included in *
* all copies or substantial portions of the Software. *
* *
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR *
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, *
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE *
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER *
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, *
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN *
* THE SOFTWARE. *
* *
********************************************************************************/
package org.aoju.bus.http.socket;
import org.aoju.bus.core.io.ByteString;
import org.aoju.bus.core.io.sink.BufferSink;
import org.aoju.bus.core.io.source.BufferSource;
import org.aoju.bus.core.lang.Header;
import org.aoju.bus.core.lang.Http;
import org.aoju.bus.core.lang.Normal;
import org.aoju.bus.core.lang.Symbol;
import org.aoju.bus.core.toolkit.IoKit;
import org.aoju.bus.http.*;
import org.aoju.bus.http.accord.Exchange;
import org.aoju.bus.http.metric.EventListener;
import org.aoju.bus.http.metric.Internal;
import java.io.Closeable;
import java.io.IOException;
import java.net.ProtocolException;
import java.net.SocketTimeoutException;
import java.util.ArrayDeque;
import java.util.Collections;
import java.util.List;
import java.util.Random;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
/**
* @author Kimi Liu
* @since Java 17+
*/
public class RealWebSocket implements WebSocket, WebSocketReader.FrameCallback {
private static final List ONLY_HTTP1 = Collections.singletonList(Protocol.HTTP_1_1);
/**
* 要加入队列的最大字节数。而不是排队超过这个限制,我们拆掉web套接字!有可能我们写得比别人读得快
* 16 MiB
*/
private static final long MAX_QUEUE_SIZE = Normal._16 * Normal._1024 * Normal._1024;
/**
* 客户端调用{@link #close}以等待适当关闭的最大时间量。如果服务器没有响应,websocket将被取消
*/
private static final long CANCEL_AFTER_CLOSE_MILLIS = 60 * 1000;
final WebSocketListener listener;
/**
* 应用程序的原始请求未受web套接字头的影响
*/
private final Request originalRequest;
private final Random random;
private final long pingIntervalMillis;
private final String key;
/**
* 这个runnable处理传出队列。在进入队列后调用{@link #runWriter()}.
*/
private final Runnable writerRunnable;
/**
* 发出的ping信号的顺序应该是写出来的
*/
private final ArrayDeque pongQueue = new ArrayDeque<>();
/**
* 发送消息和关闭帧的顺序应该是它们被写入的顺序
*/
private final ArrayDeque messageAndCloseQueue = new ArrayDeque<>();
/**
* 客户端web套接字是非空的。这些可以被取消.
*/
private NewCall call;
/**
* 在连接此web套接字之前为空。仅由读线程访问
*/
private WebSocketReader reader;
/**
* 在连接此web套接字之前为空。注意,消息可能在此之前排队
*/
private WebSocketWriter writer;
/**
* 在连接此web套接字之前为空。用于写、ping和关闭超时
*/
private ScheduledExecutorService executor;
/**
* 此web套接字持有的流。在读取所有传入消息和写入所有传出消息之前,这是非空的
* 当读者和作者都精疲力尽,或者出现任何失败时,它就关闭了
*/
private Streams streams;
/**
* 排队但尚未传输的消息的总大小(以字节为单位)
*/
private long queueSize;
/**
* 如果我们加入了一个闭帧,则为真。不再有消息帧进入队列
*/
private boolean enqueuedClose;
/**
* 执行时将取消此websocket。如果不必要的话,应该取消这个future本身,因为web套接字已经关闭或取消了
*/
private ScheduledFuture> cancelFuture;
/**
* 来自对等端的关闭代码,如果此web套接字尚未读取关闭帧,则为-1
*/
private int receivedCloseCode = -1;
/**
* 来自对等方的关闭原因,如果此web套接字尚未读取关闭帧,则为null
*/
private String receivedCloseReason;
/**
* 如果此web套接字失败且侦听器已被通知,则为
*/
private boolean failed;
/**
* 此web套接字发送的ping的总数
*/
private int sentPingCount;
/**
* 此web套接字接收的ping的总数
*/
private int receivedPingCount;
/**
* 此web套接字接收的ping总数
*/
private int receivedPongCount;
/**
* 如果我们发送了一个仍在等待回复的ping,则为真
*/
private boolean awaitingPong;
public RealWebSocket(Request request, WebSocketListener listener, Random random,
long pingIntervalMillis) {
if (!Http.GET.equals(request.method())) {
throw new IllegalArgumentException("Request must be GET: " + request.method());
}
this.originalRequest = request;
this.listener = listener;
this.random = random;
this.pingIntervalMillis = pingIntervalMillis;
byte[] nonce = new byte[Normal._16];
random.nextBytes(nonce);
this.key = ByteString.of(nonce).base64();
this.writerRunnable = () -> {
try {
while (writeOneFrame()) {
}
} catch (IOException e) {
failWebSocket(e, null);
}
};
}
@Override
public Request request() {
return originalRequest;
}
@Override
public synchronized long queueSize() {
return queueSize;
}
@Override
public void cancel() {
call.cancel();
}
public void connect(Httpd client) {
client = client.newBuilder()
.eventListener(EventListener.NONE)
.protocols(ONLY_HTTP1)
.build();
final Request request = originalRequest.newBuilder()
.header(Header.UPGRADE, "websocket")
.header(Header.CONNECTION, Header.UPGRADE)
.header(Header.SEC_WEBSOCKET_KEY, key)
.header(Header.SEC_WEBSOCKET_VERSION, "13")
.build();
call = Internal.instance.newWebSocketCall(client, request);
call.enqueue(new Callback() {
@Override
public void onResponse(NewCall call, Response response) {
Exchange exchange = Internal.instance.exchange(response);
Streams streams;
try {
checkUpgradeSuccess(response, exchange);
streams = exchange.newWebSocketStreams();
} catch (IOException e) {
if (exchange != null) exchange.webSocketUpgradeFailed();
failWebSocket(e, response);
IoKit.close(response);
return;
}
// Process all web socket messages.
try {
String name = "WebSocket " + request.url().redact();
initReaderAndWriter(name, streams);
listener.onOpen(RealWebSocket.this, response);
loopReader();
} catch (Exception e) {
failWebSocket(e, null);
}
}
@Override
public void onFailure(NewCall call, IOException e) {
failWebSocket(e, null);
}
});
}
void checkUpgradeSuccess(Response response, Exchange exchange) throws IOException {
if (response.code() != 101) {
throw new ProtocolException("Expected HTTP 101 response but was '"
+ response.code() + Symbol.SPACE + response.message() + Symbol.SINGLE_QUOTE);
}
String headerConnection = response.header(Header.CONNECTION);
if (!Header.UPGRADE.equalsIgnoreCase(headerConnection)) {
throw new ProtocolException("Expected 'Connection' header value 'Upgrade' but was '"
+ headerConnection + Symbol.SINGLE_QUOTE);
}
String headerUpgrade = response.header(Header.UPGRADE);
if (!"websocket".equalsIgnoreCase(headerUpgrade)) {
throw new ProtocolException(
"Expected 'Upgrade' header value 'websocket' but was '" + headerUpgrade + Symbol.SINGLE_QUOTE);
}
String headerAccept = response.header(Header.SEC_WEBSOCKET_ACCEPT);
String acceptExpected = ByteString.encodeUtf8(key + WebSocketProtocol.ACCEPT_MAGIC)
.sha1().base64();
if (!acceptExpected.equals(headerAccept)) {
throw new ProtocolException("Expected 'Sec-WebSocket-Accept' header value '"
+ acceptExpected + "' but was '" + headerAccept + "'");
}
if (exchange == null) {
throw new ProtocolException("Web Socket exchange missing: bad interceptor?");
}
}
public void initReaderAndWriter(String name, Streams streams) {
synchronized (this) {
this.streams = streams;
this.writer = new WebSocketWriter(streams.client, streams.sink, random);
this.executor = new ScheduledThreadPoolExecutor(1, Builder.threadFactory(name, false));
if (pingIntervalMillis != 0) {
executor.scheduleAtFixedRate(
new PingRunnable(), pingIntervalMillis, pingIntervalMillis, TimeUnit.MILLISECONDS);
}
if (!messageAndCloseQueue.isEmpty()) {
runWriter(); // Send messages that were enqueued before we were connected.
}
}
reader = new WebSocketReader(streams.client, streams.source, this);
}
/**
* Receive frames until there are no more. Invoked only by the reader thread.
*/
public void loopReader() throws IOException {
while (receivedCloseCode == -1) {
// This method call results in one or more onRead* methods being called on this thread.
reader.processNextFrame();
}
}
/**
* For testing: receive a single frame and return true if there are more frames to read. Invoked
* only by the reader thread.
*/
boolean processNextFrame() {
try {
reader.processNextFrame();
return receivedCloseCode == -1;
} catch (Exception e) {
failWebSocket(e, null);
return false;
}
}
/**
* For testing: wait until the web socket's executor has terminated.
*/
void awaitTermination(int timeout, TimeUnit timeUnit) throws InterruptedException {
executor.awaitTermination(timeout, timeUnit);
}
/**
* For testing: force this web socket to release its threads.
*/
void tearDown() throws InterruptedException {
if (cancelFuture != null) {
cancelFuture.cancel(false);
}
executor.shutdown();
executor.awaitTermination(10, TimeUnit.SECONDS);
}
synchronized int sentPingCount() {
return sentPingCount;
}
synchronized int receivedPingCount() {
return receivedPingCount;
}
synchronized int receivedPongCount() {
return receivedPongCount;
}
@Override
public void onReadMessage(String text) {
listener.onMessage(this, text);
}
@Override
public void onReadMessage(ByteString bytes) {
listener.onMessage(this, bytes);
}
@Override
public synchronized void onReadPing(ByteString payload) {
// Don't respond to pings after we've failed or sent the close frame.
if (failed || (enqueuedClose && messageAndCloseQueue.isEmpty())) return;
pongQueue.add(payload);
runWriter();
receivedPingCount++;
}
@Override
public synchronized void onReadPong(ByteString buffer) {
// This API doesn't expose pings.
receivedPongCount++;
awaitingPong = false;
}
@Override
public void onReadClose(int code, String reason) {
if (code == -1) throw new IllegalArgumentException();
Streams toClose = null;
synchronized (this) {
if (receivedCloseCode != -1) throw new IllegalStateException("already closed");
receivedCloseCode = code;
receivedCloseReason = reason;
if (enqueuedClose && messageAndCloseQueue.isEmpty()) {
toClose = this.streams;
this.streams = null;
if (null != cancelFuture) cancelFuture.cancel(false);
this.executor.shutdown();
}
}
try {
listener.onClosing(this, code, reason);
if (null != toClose) {
listener.onClosed(this, code, reason);
}
} finally {
IoKit.close(toClose);
}
}
// Writer methods to enqueue frames. They'll be sent asynchronously by the writer thread.
@Override
public boolean send(String text) {
if (text == null) throw new NullPointerException("text == null");
return send(ByteString.encodeUtf8(text), WebSocketProtocol.OPCODE_TEXT);
}
@Override
public boolean send(ByteString bytes) {
if (bytes == null) throw new NullPointerException("bytes == null");
return send(bytes, WebSocketProtocol.OPCODE_BINARY);
}
private synchronized boolean send(ByteString data, int formatOpcode) {
// Don't send new frames after we've failed or enqueued a close frame.
if (failed || enqueuedClose) return false;
// If this frame overflows the buffer, reject it and close the web socket.
if (queueSize + data.size() > MAX_QUEUE_SIZE) {
close(WebSocketProtocol.CLOSE_CLIENT_GOING_AWAY, null);
return false;
}
// Enqueue the message frame.
queueSize += data.size();
messageAndCloseQueue.add(new Message(formatOpcode, data));
runWriter();
return true;
}
synchronized boolean pong(ByteString payload) {
// Don't send pongs after we've failed or sent the close frame.
if (failed || (enqueuedClose && messageAndCloseQueue.isEmpty())) return false;
pongQueue.add(payload);
runWriter();
return true;
}
@Override
public boolean close(int code, String reason) {
return close(code, reason, CANCEL_AFTER_CLOSE_MILLIS);
}
synchronized boolean close(int code, String reason, long cancelAfterCloseMillis) {
WebSocketProtocol.validateCloseCode(code);
ByteString reasonBytes = null;
if (null != reason) {
reasonBytes = ByteString.encodeUtf8(reason);
if (reasonBytes.size() > WebSocketProtocol.CLOSE_MESSAGE_MAX) {
throw new IllegalArgumentException("reason.size() > " + WebSocketProtocol.CLOSE_MESSAGE_MAX + ": " + reason);
}
}
if (failed || enqueuedClose) return false;
// Immediately prevent further frames from being enqueued.
enqueuedClose = true;
// Enqueue the close frame.
messageAndCloseQueue.add(new Close(code, reasonBytes, cancelAfterCloseMillis));
runWriter();
return true;
}
private void runWriter() {
assert (Thread.holdsLock(this));
if (null != executor) {
executor.execute(writerRunnable);
}
}
/**
* 尝试从队列中删除单个帧并发送它。这种写法更倾向于在不太紧急的信息和较短的框架前
* 例如,可能调用者将对后面跟着ping的消息进行排队,但这会发送后面跟着消息的ping
* 如果无法发送帧因为没有任何帧进入队列,或者因为web套接字没有连接不执行任何操作并返回false。
* 否则,该方法将返回true,调用者应立即再次调用该方法,直到它返回false为止.
* 此方法只能由写线程调用。一次可能只有一个线程调用这个方法
*
* @return the true/false
* @throws IOException 异常信息
*/
boolean writeOneFrame() throws IOException {
WebSocketWriter writer;
ByteString pong;
Object messageOrClose = null;
int receivedCloseCode = -1;
String receivedCloseReason = null;
Streams streamsToClose = null;
synchronized (RealWebSocket.this) {
if (failed) {
return false;
}
writer = this.writer;
pong = pongQueue.poll();
if (null == pong) {
messageOrClose = messageAndCloseQueue.poll();
if (messageOrClose instanceof Close) {
receivedCloseCode = this.receivedCloseCode;
receivedCloseReason = this.receivedCloseReason;
if (receivedCloseCode != -1) {
streamsToClose = this.streams;
this.streams = null;
this.executor.shutdown();
} else {
// 当我们请求一个优雅的关闭,也计划取消websocket.
cancelFuture = executor.schedule(new CancelRunnable(),
((Close) messageOrClose).cancelAfterCloseMillis, TimeUnit.MILLISECONDS);
}
} else if (null == messageOrClose) {
// 队列已满
return false;
}
}
}
try {
if (null != pong) {
writer.writePong(pong);
} else if (messageOrClose instanceof Message) {
ByteString data = ((Message) messageOrClose).data;
BufferSink sink = IoKit.buffer(writer.newMessageSink(
((Message) messageOrClose).formatOpcode, data.size()));
sink.write(data);
sink.close();
synchronized (this) {
queueSize -= data.size();
}
} else if (messageOrClose instanceof Close) {
Close close = (Close) messageOrClose;
writer.writeClose(close.code, close.reason);
// 我们关闭了writer:现在reader和writer都关闭了.
if (null != streamsToClose) {
listener.onClosed(this, receivedCloseCode, receivedCloseReason);
}
} else {
throw new AssertionError();
}
return true;
} finally {
IoKit.close(streamsToClose);
}
}
void writePingFrame() {
WebSocketWriter writer;
int failedPing;
synchronized (this) {
if (failed) return;
writer = this.writer;
failedPing = awaitingPong ? sentPingCount : -1;
sentPingCount++;
awaitingPong = true;
}
if (failedPing != -1) {
failWebSocket(new SocketTimeoutException("sent ping but didn't receive pong within "
+ pingIntervalMillis + "ms (after " + (failedPing - 1) + " successful ping/pongs)"),
null);
return;
}
try {
writer.writePing(ByteString.EMPTY);
} catch (IOException e) {
failWebSocket(e, null);
}
}
public void failWebSocket(Exception e, Response response) {
Streams streamsToClose;
synchronized (this) {
if (failed) return; // Already failed.
failed = true;
streamsToClose = this.streams;
this.streams = null;
if (null != cancelFuture) cancelFuture.cancel(false);
if (null != executor) executor.shutdown();
}
try {
listener.onFailure(this, e, response);
} finally {
IoKit.close(streamsToClose);
}
}
static class Message {
final int formatOpcode;
final ByteString data;
Message(int formatOpcode, ByteString data) {
this.formatOpcode = formatOpcode;
this.data = data;
}
}
static class Close {
final int code;
final ByteString reason;
final long cancelAfterCloseMillis;
Close(int code, ByteString reason, long cancelAfterCloseMillis) {
this.code = code;
this.reason = reason;
this.cancelAfterCloseMillis = cancelAfterCloseMillis;
}
}
public abstract static class Streams implements Closeable {
public final boolean client;
public final BufferSource source;
public final BufferSink sink;
public Streams(boolean client, BufferSource source, BufferSink sink) {
this.client = client;
this.source = source;
this.sink = sink;
}
}
private class PingRunnable implements Runnable {
PingRunnable() {
}
@Override
public void run() {
writePingFrame();
}
}
class CancelRunnable implements Runnable {
@Override
public void run() {
cancel();
}
}
}