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
*
* Copyright (c) 2015-2020 aoju.org All rights reserved.
*
* 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.BufferSink;
import org.aoju.bus.core.io.BufferSource;
import org.aoju.bus.core.io.ByteString;
import org.aoju.bus.core.lang.Header;
import org.aoju.bus.core.lang.Http;
import org.aoju.bus.core.lang.Symbol;
import org.aoju.bus.core.utils.IoUtils;
import org.aoju.bus.http.*;
import org.aoju.bus.http.accord.StreamAllocation;
import org.aoju.bus.http.metric.EventListener;
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
* @version 5.6.5
* @since JDK 1.8+
*/
public final 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 = 16 * 1024 * 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[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 = Builder.instance.newWebSocketCall(client, request);
call.timeout().clearTimeout();
call.enqueue(new Callback() {
@Override
public void onResponse(NewCall call, Response response) {
try {
checkResponse(response);
} catch (ProtocolException e) {
failWebSocket(e, response);
IoUtils.close(response);
return;
}
// 将HTTP流提升为web套接字流.
StreamAllocation streamAllocation = Builder.instance.streamAllocation(call);
streamAllocation.noNewStreams(); // Prevent connection pooling!
Streams streams = streamAllocation.connection().newWebSocketStreams(streamAllocation);
// 处理所有web套接字消息.
try {
listener.onOpen(RealWebSocket.this, response);
String name = "Httpd WebSocket " + request.url().redact();
initReaderAndWriter(name, streams);
streamAllocation.connection().socket().setSoTimeout(0);
loopReader();
} catch (Exception e) {
failWebSocket(e, null);
}
}
@Override
public void onFailure(NewCall call, IOException e) {
failWebSocket(e, null);
}
});
}
void checkResponse(Response response) throws ProtocolException {
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("Connection");
if (!"Upgrade".equalsIgnoreCase(headerConnection)) {
throw new ProtocolException("Expected 'Connection' header value 'Upgrade' but was '"
+ headerConnection + Symbol.SINGLE_QUOTE);
}
String headerUpgrade = response.header("Upgrade");
if (!"websocket".equalsIgnoreCase(headerUpgrade)) {
throw new ProtocolException(
"Expected 'Upgrade' header value 'websocket' but was '" + headerUpgrade + Symbol.SINGLE_QUOTE);
}
String headerAccept = response.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 + Symbol.SINGLE_QUOTE);
}
}
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();
}
}
reader = new WebSocketReader(streams.client, streams.source, this);
}
public void loopReader() throws IOException {
while (receivedCloseCode == -1) {
reader.processNextFrame();
}
}
boolean processNextFrame() {
try {
reader.processNextFrame();
return receivedCloseCode == -1;
} catch (Exception e) {
failWebSocket(e, null);
return false;
}
}
void awaitTermination(int timeout, TimeUnit timeUnit) throws InterruptedException {
executor.awaitTermination(timeout, timeUnit);
}
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) {
if (failed || (enqueuedClose && messageAndCloseQueue.isEmpty())) return;
pongQueue.add(payload);
runWriter();
receivedPingCount++;
}
@Override
public synchronized void onReadPong(ByteString buffer) {
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 (cancelFuture != null) cancelFuture.cancel(false);
this.executor.shutdown();
}
}
try {
listener.onClosing(this, code, reason);
if (toClose != null) {
listener.onClosed(this, code, reason);
}
} finally {
IoUtils.close(toClose);
}
}
@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) {
// 不要发送新的帧后,我们已经失败或排队关闭的帧.
if (failed || enqueuedClose) return false;
// 如果此帧溢出缓冲区,则拒绝它并关闭web套接字.
if (queueSize + data.size() > MAX_QUEUE_SIZE) {
close(WebSocketProtocol.CLOSE_CLIENT_GOING_AWAY, null);
return false;
}
// 对消息帧进行排队.
queueSize += data.size();
messageAndCloseQueue.add(new Message(formatOpcode, data));
runWriter();
return true;
}
synchronized boolean pong(ByteString payload) {
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 (reason != null) {
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;
enqueuedClose = true;
messageAndCloseQueue.add(new Close(code, reasonBytes, cancelAfterCloseMillis));
runWriter();
return true;
}
private void runWriter() {
assert (Thread.holdsLock(this));
if (executor != null) {
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 (pong == null) {
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 (messageOrClose == null) {
// 队列已满
return false;
}
}
}
try {
if (pong != null) {
writer.writePong(pong);
} else if (messageOrClose instanceof Message) {
ByteString data = ((Message) messageOrClose).data;
BufferSink sink = IoUtils.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 (streamsToClose != null) {
listener.onClosed(this, receivedCloseCode, receivedCloseReason);
}
} else {
throw new AssertionError();
}
return true;
} finally {
IoUtils.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 (cancelFuture != null) cancelFuture.cancel(false);
if (executor != null) executor.shutdown();
}
try {
listener.onFailure(this, e, response);
} finally {
IoUtils.close(streamsToClose);
}
}
static final class Message {
final int formatOpcode;
final ByteString data;
Message(int formatOpcode, ByteString data) {
this.formatOpcode = formatOpcode;
this.data = data;
}
}
static final 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 final class PingRunnable implements Runnable {
PingRunnable() {
}
@Override
public void run() {
writePingFrame();
}
}
final class CancelRunnable implements Runnable {
@Override
public void run() {
cancel();
}
}
}