
io.vertx.ext.stomp.impl.StompServerImpl Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of vertx-stomp Show documentation
Show all versions of vertx-stomp Show documentation
Stomp support for Vert.x 3
/*
* Copyright (c) 2011-2015 The original author or authors
* ------------------------------------------------------
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* and Apache License v2.0 which accompanies this distribution.
*
* The Eclipse Public License is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* The Apache License v2.0 is available at
* http://www.opensource.org/licenses/apache2.0.php
*
* You may elect to redistribute this code under either of these licenses.
*/
package io.vertx.ext.stomp.impl;
import io.vertx.core.*;
import io.vertx.core.http.ServerWebSocket;
import io.vertx.core.http.ServerWebSocketHandshake;
import io.vertx.core.internal.logging.Logger;
import io.vertx.core.internal.logging.LoggerFactory;
import io.vertx.core.net.NetServer;
import io.vertx.ext.stomp.*;
import java.util.Collections;
import java.util.Objects;
import java.util.concurrent.atomic.AtomicBoolean;
/**
* Default implementation of the {@link StompServer}.
*
* @author Clement Escoffier
*/
public class StompServerImpl implements StompServer {
private static final Logger LOGGER = LoggerFactory.getLogger(StompServerImpl.class);
private final Vertx vertx;
private final StompServerOptions options;
private final NetServer server;
private StompServerHandler handler;
private volatile boolean listening;
private Handler writingFrameHandler;
/**
* Creates a new instance of {@link StompServerImpl}.
* @param vertx the vert.x instance
* @param net the net server, may be {@code null}
* @param options the options
*/
public StompServerImpl(Vertx vertx, NetServer net, StompServerOptions options) {
Objects.requireNonNull(vertx);
Objects.requireNonNull(options);
this.options = options;
this.vertx = vertx;
if (net == null) {
server = vertx.createNetServer(options);
} else {
server = net;
}
}
@Override
public synchronized StompServer handler(StompServerHandler handler) {
Objects.requireNonNull(handler);
this.handler = handler;
return this;
}
@Override
public Future listen() {
Promise promise = Promise.promise();
listen(promise);
return promise.future();
}
public StompServer listen(Completable handler) {
return listen(options.getPort(), options.getHost(), handler);
}
@Override
public Future listen(int port) {
return listen(port, StompServerOptions.DEFAULT_STOMP_HOST);
}
@Override
public Future listen(int port, String host) {
Promise promise = Promise.promise();
listen(port, host, promise);
return promise.future();
}
public StompServer listen(int port, String host, Completable handler) {
if (port == -1) {
handler.fail("TCP server disabled. The port is set to '-1'.");
return this;
}
StompServerHandler stomp;
synchronized (this) {
stomp = this.handler;
}
Objects.requireNonNull(stomp, "Cannot open STOMP server - no StompServerConnectionHandler attached to the " +
"server.");
server
.connectHandler(socket -> {
AtomicBoolean connected = new AtomicBoolean();
AtomicBoolean firstFrame = new AtomicBoolean();
StompServerConnection connection = new StompServerTCPConnectionImpl(socket, this, frame -> {
if (frame.frame().getCommand() == Command.CONNECTED) {
connected.set(true);
}
Handler h = writingFrameHandler;
if (h != null) {
h.handle(frame);
}
});
FrameParser parser = new FrameParser(options);
socket.exceptionHandler((exception) -> {
LOGGER.error("The STOMP server caught a TCP socket error - closing connection", exception);
connection.close();
});
socket.endHandler(v -> connection.close());
parser
.errorHandler((exception) -> {
connection.write(
Frames.createInvalidFrameErrorFrame(exception));
connection.close();
}
)
.handler(frame -> {
if (frame.getCommand() == Command.CONNECT || frame.getCommand() == Command.STOMP) {
if (firstFrame.compareAndSet(false, true)) {
stomp.handle(new ServerFrameImpl(frame, connection));
} else {
connection.write(Frames.createErrorFrame("Already connected", Collections.emptyMap(), ""));
connection.close();
}
} else if (connected.get()) {
stomp.handle(new ServerFrameImpl(frame, connection));
} else {
connection.write(Frames.createErrorFrame("Not connected", Collections.emptyMap(), ""));
connection.close();
}
});
socket.handler(parser);
})
.listen(port, host).onComplete(ar -> {
if (ar.failed()) {
if (handler != null) {
vertx.runOnContext(v -> handler.fail(ar.cause()));
} else {
LOGGER.error(ar.cause());
}
} else {
listening = true;
LOGGER.info("STOMP server listening on " + ar.result().actualPort());
if (handler != null) {
vertx.runOnContext(v -> handler.succeed(this));
}
}
});
return this;
}
@Override
public Future close() {
Promise promise = Promise.promise();
close(promise);
return promise.future();
}
@Override
public boolean isListening() {
return listening;
}
@Override
public int actualPort() {
return server.actualPort();
}
@Override
public StompServerOptions options() {
return options;
}
@Override
public Vertx vertx() {
return vertx;
}
@Override
public synchronized StompServerHandler stompHandler() {
return handler;
}
public void close(Completable done) {
if (!listening) {
if (done != null) {
vertx.runOnContext((v) -> done.succeed());
}
return;
}
Completable listener = (res, err) -> {
if (err == null) {
LOGGER.info("STOMP Server stopped");
} else {
LOGGER.info("STOMP Server failed to stop", err);
}
listening = false;
if (done != null) {
done.complete(res, err);
}
};
server.close().onComplete(listener);
}
@Override
public Handler webSocketHandshakeHandler() {
if (!options.isWebsocketBridge()) {
return null;
}
return handshake -> {
if (!handshake.path().equals(options.getWebsocketPath())) {
LOGGER.error("Receiving a web socket connection on an invalid path (" + handshake.path() + "), the path is " +
"configured to " + options.getWebsocketPath() + ". Rejecting connection");
handshake.reject();
} else {
handshake.accept();
}
};
}
@Override
public Handler webSocketHandler() {
if (!options.isWebsocketBridge()) {
return null;
}
StompServerHandler stomp;
synchronized (this) {
stomp = this.handler;
}
return socket -> {
AtomicBoolean connected = new AtomicBoolean();
AtomicBoolean firstFrame = new AtomicBoolean();
StompServerConnection connection = new StompServerWebSocketConnectionImpl(socket, this, frame -> {
if (frame.frame().getCommand() == Command.CONNECTED || frame.frame().getCommand() == Command.STOMP) {
connected.set(true);
}
Handler h = writingFrameHandler;
if (h != null) {
h.handle(frame);
}
});
FrameParser parser = new FrameParser(options);
socket.exceptionHandler((exception) -> {
LOGGER.error("The STOMP server caught a WebSocket error - closing connection", exception);
connection.close();
});
socket.endHandler(v -> connection.close());
parser
.errorHandler((exception) -> {
connection.write(
Frames.createInvalidFrameErrorFrame(exception));
connection.close();
}
)
.handler(frame -> {
if (frame.getCommand() == Command.CONNECT) {
if (firstFrame.compareAndSet(false, true)) {
stomp.handle(new ServerFrameImpl(frame, connection));
} else {
connection.write(Frames.createErrorFrame("Already connected", Collections.emptyMap(), ""));
connection.close();
}
} else if (connected.get()) {
stomp.handle(new ServerFrameImpl(frame, connection));
} else {
connection.write(Frames.createErrorFrame("Not connected", Collections.emptyMap(), ""));
connection.close();
}
});
socket.handler(parser);
};
}
@Override
public StompServer writingFrameHandler(Handler handler) {
synchronized (this) {
this.writingFrameHandler = handler;
}
return this;
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy