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.
com.google.firebase.database.connection.Connection Maven / Gradle / Ivy
/*
* Copyright 2017 Google Inc.
*
* Licensed 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 com.google.firebase.database.connection;
import com.google.common.annotations.VisibleForTesting;
import java.util.HashMap;
import java.util.Map;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
class Connection implements WebsocketConnection.Delegate {
private static final String REQUEST_TYPE = "t";
private static final String REQUEST_TYPE_DATA = "d";
private static final String REQUEST_PAYLOAD = "d";
private static final String SERVER_ENVELOPE_TYPE = "t";
private static final String SERVER_DATA_MESSAGE = "d";
private static final String SERVER_CONTROL_MESSAGE = "c";
private static final String SERVER_ENVELOPE_DATA = "d";
private static final String SERVER_CONTROL_MESSAGE_TYPE = "t";
private static final String SERVER_CONTROL_MESSAGE_SHUTDOWN = "s";
private static final String SERVER_CONTROL_MESSAGE_RESET = "r";
private static final String SERVER_CONTROL_MESSAGE_HELLO = "h";
private static final String SERVER_CONTROL_MESSAGE_DATA = "d";
private static final String SERVER_HELLO_TIMESTAMP = "ts";
private static final String SERVER_HELLO_HOST = "h";
private static final String SERVER_HELLO_SESSION_ID = "s";
private static final Logger logger = LoggerFactory.getLogger(Connection.class);
private static long connectionIds = 0;
private final HostInfo hostInfo;
private final Delegate delegate;
private final String label;
private WebsocketConnection conn;
private State state;
Connection(
ConnectionContext context,
HostInfo hostInfo,
String cachedHost,
Delegate delegate,
String optLastSessionId) {
this(hostInfo, delegate,
new DefaultWebsocketConnectionFactory(context, hostInfo, cachedHost, optLastSessionId));
}
@VisibleForTesting
Connection(
HostInfo hostInfo,
Delegate delegate,
WebsocketConnectionFactory connFactory) {
long connId = connectionIds++;
this.hostInfo = hostInfo;
this.delegate = delegate;
this.label = "[conn_" + connId + "]";
this.state = State.REALTIME_CONNECTING;
this.conn = connFactory.newConnection(this);
}
public void open() {
logger.debug("{} Opening a connection", label);
conn.open();
}
public void close(DisconnectReason reason) {
if (state != State.REALTIME_DISCONNECTED) {
logger.debug("{} Closing realtime connection", label);
state = State.REALTIME_DISCONNECTED;
if (conn != null) {
conn.close();
conn = null;
}
delegate.onDisconnect(reason);
}
}
public void close() {
close(DisconnectReason.OTHER);
}
public void sendRequest(Map message, boolean isSensitive) {
// This came from the persistent connection. Wrap it in an envelope and send it
Map request = new HashMap<>();
request.put(REQUEST_TYPE, REQUEST_TYPE_DATA);
request.put(REQUEST_PAYLOAD, message);
sendData(request, isSensitive);
}
@Override
public void onMessage(Map message) {
try {
String messageType = (String) message.get(SERVER_ENVELOPE_TYPE);
if (messageType != null) {
if (messageType.equals(SERVER_DATA_MESSAGE)) {
@SuppressWarnings("unchecked")
Map data = (Map) message.get(SERVER_ENVELOPE_DATA);
onDataMessage(data);
} else if (messageType.equals(SERVER_CONTROL_MESSAGE)) {
@SuppressWarnings("unchecked")
Map data = (Map) message.get(SERVER_ENVELOPE_DATA);
onControlMessage(data);
} else {
logger.debug("{} Ignoring unknown server message type: {}", label, messageType);
}
} else {
logger.debug("{} Failed to parse server message: missing message type: {}", label, message);
close();
}
} catch (ClassCastException e) {
logger.debug("{} Failed to parse server message", label, e);
close();
}
}
@Override
public void onDisconnect(boolean wasEverConnected) {
conn = null;
if (!wasEverConnected && state == State.REALTIME_CONNECTING) {
logger.debug("{} Realtime connection failed", label);
} else {
logger.debug("{} Realtime connection lost", label);
}
close();
}
private void onDataMessage(Map data) {
logger.debug("{} Received data message: {}", label, data);
// We don't do anything with data messages, just kick them up a level
delegate.onDataMessage(data);
}
private void onControlMessage(Map data) {
logger.debug("{} Got control message: {}", label, data);
try {
String messageType = (String) data.get(SERVER_CONTROL_MESSAGE_TYPE);
if (messageType != null) {
if (messageType.equals(SERVER_CONTROL_MESSAGE_SHUTDOWN)) {
String reason = (String) data.get(SERVER_CONTROL_MESSAGE_DATA);
onConnectionShutdown(reason);
} else if (messageType.equals(SERVER_CONTROL_MESSAGE_RESET)) {
String host = (String) data.get(SERVER_CONTROL_MESSAGE_DATA);
onReset(host);
} else if (messageType.equals(SERVER_CONTROL_MESSAGE_HELLO)) {
@SuppressWarnings("unchecked")
Map handshakeData =
(Map) data.get(SERVER_CONTROL_MESSAGE_DATA);
onHandshake(handshakeData);
} else {
logger.debug("{} Ignoring unknown control message: {}", label, messageType);
}
} else {
logger.debug("{} Got invalid control message: {}", label, data);
close();
}
} catch (ClassCastException e) {
logger.debug("{} Failed to parse control message", label, e);
close();
}
}
private void onConnectionShutdown(String reason) {
logger.debug("{} Connection shutdown command received. Shutting down...", label);
delegate.onKill(reason);
close();
}
private void onHandshake(Map handshake) {
long timestamp = (Long) handshake.get(SERVER_HELLO_TIMESTAMP);
String host = (String) handshake.get(SERVER_HELLO_HOST);
delegate.onCacheHost(host);
String sessionId = (String) handshake.get(SERVER_HELLO_SESSION_ID);
if (state == State.REALTIME_CONNECTING) {
conn.start();
onConnectionReady(timestamp, sessionId);
}
}
private void onConnectionReady(long timestamp, String sessionId) {
logger.debug("{} Realtime connection established", label);
state = State.REALTIME_CONNECTED;
delegate.onReady(timestamp, sessionId);
}
private void onReset(String host) {
logger.debug(
"{} Got a reset; killing connection to {}; Updating internalHost to {}",
label, hostInfo.getHost(), host);
delegate.onCacheHost(host);
// Explicitly close the connection with SERVER_RESET so calling code knows to reconnect
// immediately.
close(DisconnectReason.SERVER_RESET);
}
private void sendData(Map data, boolean isSensitive) {
if (state != State.REALTIME_CONNECTED) {
logger.debug("{} Tried to send on an unconnected connection", label);
} else {
if (isSensitive) {
logger.debug("{} Sending data (contents hidden)", label);
} else {
logger.debug("{} Sending data: {}", label, data);
}
conn.send(data);
}
}
public enum DisconnectReason {
SERVER_RESET,
OTHER
}
private enum State {
REALTIME_CONNECTING,
REALTIME_CONNECTED,
REALTIME_DISCONNECTED
}
interface WebsocketConnectionFactory {
WebsocketConnection newConnection(WebsocketConnection.Delegate delegate);
}
private static class DefaultWebsocketConnectionFactory implements WebsocketConnectionFactory {
final ConnectionContext context;
final HostInfo hostInfo;
final String cachedHost;
final String optLastSessionId;
DefaultWebsocketConnectionFactory(
ConnectionContext context,
HostInfo hostInfo,
String cachedHost,
String optLastSessionId) {
this.context = context;
this.hostInfo = hostInfo;
this.cachedHost = cachedHost;
this.optLastSessionId = optLastSessionId;
}
@Override
public WebsocketConnection newConnection(WebsocketConnection.Delegate delegate) {
return new WebsocketConnection(context, hostInfo, cachedHost, delegate, optLastSessionId);
}
}
public interface Delegate {
void onCacheHost(String host);
void onReady(long timestamp, String sessionId);
void onDataMessage(Map message);
void onDisconnect(DisconnectReason reason);
void onKill(String reason);
}
}