websocket-cometd源码阅读-transport(五)
说明
transport就是我们的处理器器,比如websoket通信,http长轮询,支持jsonp方式的长轮询
如我们的初始化配置的代码
@Bean(initMethod = "start", destroyMethod = "stop") public BayeuxServer bayeuxServer() { BayeuxServerImpl bean = new BayeuxServerImpl(); bean.setTransports(new WebSocketTransport(bean), new JSONTransport(bean), new JSONPTransport(bean)); servletContext.setAttribute(BayeuxServer.ATTRIBUTE, bean); bean.setOption(ServletContext.class.getName(), servletContext); bean.setOption("ws.cometdURLMapping", "/cometd/*"); return bean; }
类图
接口
Transport定义
定义一些获取transport名字 以及配置的的接口
public interface Transport { /** * @return The well known name of this transport, used in transport negotiations * @see Bayeux#getAllowedTransports() * 获得协议名称,用于请求的时候根据参数名称找到对应的处理器 */ String getName(); /** * @param name the configuration option name * @return the configuration option with the given {@code qualifiedName} * @see #getOptionNames() * 获取配置相关参数 */ Object getOption(String name); /** * @return the set of configuration options * @see #getOption(String) * 获取配置相关参数的names */ Set<String> getOptionNames(); /** * Specifies an option prefix made of string segments separated by the "." * character, used to override more generic configuration entries. * * @return the option prefix for this transport. */ String getOptionPrefix(); }
ServerTransport
服务性质的transport,比如会定义一些获取超时配置
public interface ServerTransport extends Transport { /** * @return the timeout (in milliseconds) of this transport * 传输超时时间 */ public long getTimeout(); /** * @return the interval of time (in milliseconds) of this transport * 传输间隔,比如轮训/meta/connect */ public long getInterval(); /** * @return the maximum interval of time (in milliseconds) before the server consider the client lost * 最大间隔时间,超过这个时间服务端认为断开连接 比如多久没调用/meta/connect */ public long getMaxInterval(); /** * @return the maximum time (in milliseconds) before dispatching lazy messages * 调度延迟消息之前的最长时间(毫秒) */ public long getMaxLazyTimeout(); /** * @return whether the messages are delivered to clients exclusively via the "/meta/connect" channel * 消息是否仅通过“/meta/connect”通道传递给客户端 */ public boolean isMetaConnectDeliveryOnly(); }
AbstractTransport
仅仅定义了封装transport名字以及参数,以及获得相关option参数方法的实现
public class AbstractTransport implements Transport { private final String _name; private final Map<String, Object> _options; private String[] _prefix = new String[0]; private String _optionPrefix = ""; protected AbstractTransport(String name, Map<String, Object> options) { _name = name; _options = options == null ? new HashMap<>(1) : options; } @Override public String getName() { return _name; } /** * Returns an option value for the given option name, searching the option name tree. * The option map is searched for the option name with the most specific prefix. * If this transport was initialized with a call to: * <pre> * setOptionPrefix("long-polling.jsonp"); * </pre> * then a call to getOption("foobar") will look for the * most specific value with names: * <pre> * long-polling.json.foobar * long-polling.foobar * foobar * </pre> * * @param name the option name to return the value for. */ @Override public Object getOption(String name) { Object value = _options.get(name); String prefix = null; for (String segment : _prefix) { prefix = prefix == null ? segment : (prefix + "." + segment); String key = prefix + "." + name; if (_options.containsKey(key)) { value = _options.get(key); } } return value; } /** * Sets the option value with the given name. * The option name is inspected to see whether it starts with the {@link #getOptionPrefix() option prefix}; * if it does not, the option prefix is prepended to the given name. * * @param name the option name to set the value for. * @param value the value of the option. */ public void setOption(String name, Object value) { String prefix = getOptionPrefix(); if (prefix != null && prefix.length() > 0 && !name.startsWith(prefix)) { name = prefix + "." + name; } _options.put(name, value); } @Override public String getOptionPrefix() { return _optionPrefix; } /** * Set the option name prefix segment. * <p> Normally this is called by the super class constructors to establish * a naming hierarchy for options and iteracts with the {@link #setOption(String, Object)} * method to create a naming hierarchy for options. * For example the following sequence of calls:<pre> * setOption("foo","x"); * setOption("bar","y"); * setOptionPrefix("long-polling"); * setOption("foo","z"); * setOption("whiz","p"); * setOptionPrefix("long-polling.jsonp"); * setOption("bang","q"); * setOption("bar","r"); * </pre> * will establish the following option names and values:<pre> * foo: x * bar: y * long-polling.foo: z * long-polling.whiz: p * long-polling.jsonp.bang: q * long-polling.jsonp.bar: r * </pre> * The various {@link #getOption(String)} methods will search this * name tree for the most specific match. * * @param prefix the prefix name * @throws IllegalArgumentException if the new prefix is not prefixed by the old prefix. */ public void setOptionPrefix(String prefix) { if (!prefix.startsWith(_optionPrefix)) { throw new IllegalArgumentException(_optionPrefix + " not prefix of " + prefix); } _optionPrefix = prefix; _prefix = prefix.split("\\."); } @Override public Set<String> getOptionNames() { Set<String> names = new HashSet<>(); for (String name : _options.keySet()) { int lastDot = name.lastIndexOf('.'); if (lastDot >= 0) { name = name.substring(lastDot + 1); } names.add(name); } return names; } /** * Get option or default value. * * @param option The option name. * @param dftValue The default value. * @return option or default value * @see #getOption(String) */ public String getOption(String option, String dftValue) { Object value = getOption(option); return (value == null) ? dftValue : value.toString(); } /** * Get option or default value. * * @param option The option name. * @param dftValue The default value. * @return option or default value * @see #getOption(String) */ public long getOption(String option, long dftValue) { Object value = getOption(option); if (value == null) { return dftValue; } if (value instanceof Number) { return ((Number)value).longValue(); } return Long.parseLong(value.toString()); } /** * Get option or default value. * * @param option The option name. * @param dftValue The default value. * @return option or default value * @see #getOption(String) */ public int getOption(String option, int dftValue) { Object value = getOption(option); if (value == null) { return dftValue; } if (value instanceof Number) { return ((Number)value).intValue(); } return Integer.parseInt(value.toString()); } /** * Get option or default value. * * @param option The option name. * @param dftValue The default value. * @return option or default value * @see #getOption(String) */ public boolean getOption(String option, boolean dftValue) { Object value = getOption(option); if (value == null) { return dftValue; } if (value instanceof Boolean) { return (Boolean)value; } return Boolean.parseBoolean(value.toString()); } }
AbstractServerTransport
/* * Copyright (c) 2008-2022 the original author or authors. * * 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 org.cometd.server; import java.io.BufferedReader; import java.io.IOException; import java.text.ParseException; import java.util.concurrent.atomic.AtomicLong; import org.cometd.bayeux.Promise; import org.cometd.bayeux.server.ServerMessage; import org.cometd.bayeux.server.ServerTransport; import org.cometd.common.AbstractTransport; import org.eclipse.jetty.util.component.Dumpable; /** * <p>The base class of all server transports.</p> * <p>Each derived Transport class should declare all options that it supports * by calling {@link #setOption(String, Object)} for each option. * Then during the call the {@link #init()}, each transport should * call the variants of {@link #getOption(String)} to obtained the configured * value for the option.</p> */ public abstract class AbstractServerTransport extends AbstractTransport implements ServerTransport, Dumpable { public static final String TIMEOUT_OPTION = "timeout"; public static final String INTERVAL_OPTION = "interval"; public static final String MAX_INTERVAL_OPTION = "maxInterval"; public static final String MAX_PROCESSING_OPTION = "maxProcessing"; public static final String MAX_LAZY_TIMEOUT_OPTION = "maxLazyTimeout"; public static final String META_CONNECT_DELIVERY_OPTION = "metaConnectDeliverOnly"; public static final String MAX_QUEUE_OPTION = "maxQueue"; public static final String JSON_CONTEXT_OPTION = "jsonContext"; public static final String HANDSHAKE_RECONNECT_OPTION = "handshakeReconnect"; public static final String ALLOW_MESSAGE_DELIVERY_DURING_HANDSHAKE = "allowMessageDeliveryDuringHandshake"; public static final String MAX_MESSAGE_SIZE_OPTION = "maxMessageSize"; // /meta/connect cycles must be shared because a transport may fallback to another. private static final AtomicLong META_CONNECT_CYCLES = new AtomicLong(); private final BayeuxServerImpl _bayeux; private long _interval = 0; private long _maxInterval = 10000; private long _timeout = 30000; private long _maxLazyTimeout = 5000; private boolean _metaConnectDeliveryOnly = false; private JSONContextServer _jsonContext; private boolean _handshakeReconnect; private boolean _allowHandshakeDelivery; private int _maxMessageSize; /** * <p>The constructor is passed the {@link BayeuxServerImpl} instance for * the transport. The {@link BayeuxServerImpl#getOptions()} map is * populated with the default options known by this transport. The options * are then inspected again when {@link #init()} is called, to set the * actual values used. The options are arranged into a naming hierarchy * by derived classes adding prefix by calling add {@link #setOptionPrefix(String)}. * Calls to {@link #getOption(String)} will use the list of prefixes * to search for the most specific option set.</p> * * @param bayeux the BayeuxServer implementation * @param name the name of the transport */ protected AbstractServerTransport(BayeuxServerImpl bayeux, String name) { super(name, bayeux.getOptions()); _bayeux = bayeux; } public long newMetaConnectCycle() { return META_CONNECT_CYCLES.incrementAndGet(); } /** * @return the interval in milliseconds */ @Override public long getInterval() { return _interval; } /** * @return the maxInterval in milliseconds */ @Override public long getMaxInterval() { return _maxInterval; } /** * @return the max lazy timeout in milliseconds before flushing lazy messages */ @Override public long getMaxLazyTimeout() { return _maxLazyTimeout; } /** * @return the timeout in milliseconds */ @Override public long getTimeout() { return _timeout; } @Override public boolean isMetaConnectDeliveryOnly() { return _metaConnectDeliveryOnly; } public void setMetaConnectDeliveryOnly(boolean meta) { _metaConnectDeliveryOnly = meta; } public boolean isHandshakeReconnect() { return _handshakeReconnect; } public void setHandshakeReconnect(boolean handshakeReconnect) { _handshakeReconnect = handshakeReconnect; } public boolean isAllowMessageDeliveryDuringHandshake() { return _allowHandshakeDelivery; } public void setAllowMessageDeliveryDuringHandshake(boolean allow) { _allowHandshakeDelivery = allow; } public int getMaxMessageSize() { return _maxMessageSize; } public void setMaxMessageSize(int maxMessageSize) { _maxMessageSize = maxMessageSize; } /** * Initializes the transport, resolving default and direct options. *初始化传输,解析默认和直接选项。 */ public void init() { _interval = getOption(INTERVAL_OPTION, _interval); _maxInterval = getOption(MAX_INTERVAL_OPTION, _maxInterval); _timeout = getOption(TIMEOUT_OPTION, _timeout); _maxLazyTimeout = getOption(MAX_LAZY_TIMEOUT_OPTION, _maxLazyTimeout); _metaConnectDeliveryOnly = getOption(META_CONNECT_DELIVERY_OPTION, _metaConnectDeliveryOnly); //从option获取消息解析器 _jsonContext = (JSONContextServer)getOption(JSON_CONTEXT_OPTION); _handshakeReconnect = getOption(HANDSHAKE_RECONNECT_OPTION, false); _allowHandshakeDelivery = getOption(ALLOW_MESSAGE_DELIVERY_DURING_HANDSHAKE, false); _maxMessageSize = getOption(MAX_MESSAGE_SIZE_OPTION, -1); } public void destroy() { } protected JSONContextServer getJSONContextServer() { return _jsonContext; } /** * 读取message * @param reader * @param jsonDebug * @return * @throws ParseException * @throws IOException */ protected ServerMessage.Mutable[] parseMessages(BufferedReader reader, boolean jsonDebug) throws ParseException, IOException { if (jsonDebug || getMaxMessageSize() > 0) { return parseMessages(read(reader)); } else { return _jsonContext.parse(reader); } } /** * 读取message * @param json * @return * @throws ParseException */ public ServerMessage.Mutable[] parseMessages(String json) throws ParseException { //委托个_jsonContext return _jsonContext.parse(json); } /** * 从buffer读取消息 * @param reader * @return * @throws IOException */ private String read(BufferedReader reader) throws IOException { int maxMessageSize = getMaxMessageSize(); StringBuilder builder = new StringBuilder(); int total = 0; char[] buffer = new char[1024]; while (true) { int read = reader.read(buffer); if (read < 0) { break; } else { if (maxMessageSize > 0) { total += read; if (total > maxMessageSize) { throw new IOException("Max message size " + maxMessageSize + " exceeded"); } } builder.append(buffer, 0, read); } } return builder.toString(); } /** * @return the BayeuxServer object */ public BayeuxServerImpl getBayeux() { return _bayeux; } /** * @param interval the interval in milliseconds */ public void setInterval(long interval) { _interval = interval; } /** * @param maxInterval the maxInterval in milliseconds */ public void setMaxInterval(long maxInterval) { _maxInterval = maxInterval; } /** * @param timeout the timeout in milliseconds */ public void setTimeout(long timeout) { _timeout = timeout; } /** * @param maxLazyTimeout the maxLazyTimeout in milliseconds */ public void setMaxLazyTimeout(long maxLazyTimeout) { _maxLazyTimeout = maxLazyTimeout; } /** * Housekeeping sweep, called a regular intervals */ protected void sweep() { } public void processReply(ServerSessionImpl session, ServerMessage.Mutable reply, Promise<ServerMessage.Mutable> promise) { getBayeux().extendReply(session, session, reply, promise); } protected String toJSON(ServerMessage msg) { return toJSON((ServerMessageImpl)(msg instanceof ServerMessageImpl ? msg : _bayeux.newMessage(msg))); } private String toJSON(ServerMessageImpl message) { String json = message.getJSON(); if (json == null) { json = _jsonContext.generate(message); } return json; } public boolean allowMessageDeliveryDuringHandshake(ServerSessionImpl session) { return session != null && session.isAllowMessageDeliveryDuringHandshake(); } /** * 更新session的过期时间 * @param session * @param metaConnectCycle */ public void scheduleExpiration(ServerSessionImpl session, long metaConnectCycle) { if (session != null) { session.scheduleExpiration(getInterval(), getMaxInterval(), metaConnectCycle); } } @Override public void dump(Appendable out, String indent) throws IOException { Dumpable.dumpObject(out, this); } @Override public String toString() { return String.format("%s@%x[%s]", getClass().getSimpleName(), hashCode(), getName()); } /** * <p>Performs server-to-client transport operations when a {@code /meta/connect} * message is held and a server-side message is published.</p> * <p>HTTP transports can only perform server-to-client * sends if there is an outstanding {@code /meta/connect}, * or if they are processing incoming messages.</p> * <p>WebSocket transports, on the other hand, can perform * server-to-client sends even if there is no outstanding * {@code /meta/connect}.</p> */ public interface Scheduler { /** * @return the cycle number for suspended {@code /meta/connect}s. */ public default long getMetaConnectCycle() { return 0; } /** * Invoked when the transport wants to send queued * messages, and possibly a /meta/connect reply. */ public default void schedule() { } /** * Invoked when the transport wants to cancel scheduled operations * that will trigger when the /meta/connect timeout fires. */ public default void cancel() { } /** * Invoked when the transport wants to abort communication. */ public default void destroy() { } /** * <p>A scheduler that does not perform any operation * but remembers the {@code /meta/connect} cycle.</p> */ public static class None implements Scheduler { private final long metaConnectCycle; public None(long metaConnectCycle) { this.metaConnectCycle = metaConnectCycle; } @Override public long getMetaConnectCycle() { return metaConnectCycle; } @Override public String toString() { return String.format("%s@%x[cycle=%d]", getClass().getSimpleName(), hashCode(), getMetaConnectCycle()); } } } }