All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.atmosphere.stomp.interceptor.ConnectInterceptor Maven / Gradle / Ivy

/*
 * Copyright 2014 Jeanfrancois Arcand
 *
 * 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.atmosphere.stomp.interceptor;

import org.atmosphere.cpr.Action;
import org.atmosphere.cpr.AtmosphereConfig;
import org.atmosphere.cpr.AtmosphereFramework;
import org.atmosphere.cpr.AtmosphereResourceEvent;
import org.atmosphere.cpr.AtmosphereResourceEventListenerAdapter;
import org.atmosphere.cpr.AtmosphereResourceImpl;
import org.atmosphere.cpr.HeartbeatAtmosphereResourceEvent;
import org.atmosphere.cpr.packages.StompEndpointProcessor;
import org.atmosphere.interceptor.HeartbeatInterceptor;
import org.atmosphere.stomp.StompInterceptor;
import org.atmosphere.stomp.protocol.Frame;
import org.atmosphere.stomp.protocol.Header;
import org.atmosphere.util.Version;

import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;

/**
 * 

* Evaluates the {@link org.atmosphere.stomp.protocol.Action#CONNECT connection} frame. *

*

*

* Key features are the heartbeat negotiation and the user authentication when associated headers are specified in the * frame. *

* * @author Guillaume DROUET * @version 1.0 * @since 0.2 */ public class ConnectInterceptor extends HeartbeatInterceptor implements StompInterceptor { /** * The padding data for STOMP heartbeat. */ public static final byte[] STOMP_HEARTBEAT_DATA = new byte[]{0x0A,}; /** * The default supported version. */ public static final float DEFAULT_VERSION = 1.0f; /** * The highest version we currently support. */ public static final float HIGHEST_VERSION = 1.1f; /** * Supported versions. */ public static final String VERSIONS = String.format("%f,%f", DEFAULT_VERSION, HIGHEST_VERSION); /** * Server name sent to client. */ private static final String SERVER = "Atmosphere/" + Version.getDotedVersion(); /** * Heartbeat desired by client. */ private ThreadLocal desiredHeartbeat = new ThreadLocal(); /** * {@inheritDoc} */ @Override public void configure(final AtmosphereConfig config) { super.configure(config); // Atmosphere.js does not like empty strings, works fine with "hb". paddingText(/*STOMP_HEARTBEAT_DATA*/"hb".getBytes()); } /** * {@inheritDoc} */ @Override protected int extractHeartbeatInterval(final AtmosphereResourceImpl resource) { return desiredHeartbeat.get(); } /** * {@inheritDoc} */ @Override public Action inspect(final AtmosphereFramework framework, final Frame frame, final FrameInterceptor.StompAtmosphereResource r) { try { // Hack: we suspect a heartbeat here if (org.atmosphere.stomp.protocol.Action.NULL.equals(frame.getAction())) { // Dispatch an event to notify that a heartbeat has been intercepted // TODO: see https://github.com/Atmosphere/atmosphere/issues/1561 final AtmosphereResourceEvent event = new HeartbeatAtmosphereResourceEvent(AtmosphereResourceImpl.class.cast(r.getResource())); r.getResource().addEventListener(new AtmosphereResourceEventListenerAdapter.OnHeartbeat() { @Override public void onHeartbeat(AtmosphereResourceEvent event) { StompEndpointProcessor.invokeOnHeartbeat(event); } }); // Fire event r.getResource().notifyListeners(event); desiredHeartbeat.set(0); return inspect(r.getResource()); } // Send headers response to client final Map headers = new HashMap(); // Protocol negotiation final float version = parseVersion(frame.getHeaders().get(Header.ACCEPT_VERSION)); // No version in common between server and client if (version == -1) { headers.put(Header.VERSION, VERSIONS); r.write(org.atmosphere.stomp.protocol.Action.ERROR, headers, "Supported protocol versions are " + VERSIONS); return Action.CANCELLED; } else { // Extracts heartbeat then clock final Integer[] intervals = parseHeartBeat(frame.getHeaders().get(Header.HEART_BEAT)); // Extract the desired heartbeat interval // Won't be applied if lower than config value int serverInterval = intervals[1]; if (serverInterval != 0) { serverInterval = Math.max((int) TimeUnit.SECONDS.convert(serverInterval, TimeUnit.MILLISECONDS), heartbeatFrequencyInSeconds()); } else { serverInterval = 0; } desiredHeartbeat.set(serverInterval); final Action retval = inspect(r.getResource()); headers.put(Header.VERSION, String.valueOf(version)); headers.put(Header.SESSION, r.getResource().uuid()); headers.put(Header.SERVER, SERVER); headers.put(Header.HEART_BEAT, TimeUnit.MILLISECONDS.convert(serverInterval, TimeUnit.SECONDS) + "," + intervals[0]); r.write(org.atmosphere.stomp.protocol.Action.CONNECTED, headers); return retval; } } finally { desiredHeartbeat.remove(); } } /** *

* Parse the given header value to extract the most appropriate version sent by client. If value is {@code null}, * then {@link #DEFAULT_VERSION} is returned. Otherwise, the method looks for the highest version supported by * both client and server. If no version are in common, -1 is returned and an error should be sent *

* * @param acceptVersion the header value * @return the extracted version */ private float parseVersion(final String acceptVersion) { float retval; if (acceptVersion != null) { retval = -1; final String[] versions = acceptVersion.split(","); for (final String version : versions) { final float clientVersion = Float.parseFloat(version); if (clientVersion == DEFAULT_VERSION || clientVersion == HIGHEST_VERSION) { retval = Math.max(retval, clientVersion); } } } else { retval = DEFAULT_VERSION; } return retval; } /** *

* Extracts the heartbeat from the given header value. The value contains two integers. The first one is the * heartbeat interval in milliseconds the sender of the header can assume. The second one is what he wants to * receive. *

*

*

* If the value is {@code null} then 0 will be returned for each direction do disable heartbeat. *

* * @param heartbeat the header value * @return the parsed array */ private Integer[] parseHeartBeat(final String heartbeat) { if (heartbeat == null) { return new Integer[]{0, 0,}; } else { final String[] arr = heartbeat.split(","); return new Integer[]{Integer.parseInt(arr[0]), Integer.parseInt(arr[1]),}; } } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy