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());
            }
        }
    }
}

 

posted @ 2022-08-10 13:31  意犹未尽  阅读(62)  评论(0编辑  收藏  举报