Moved project package from org.eclipse.moquette to io.moquette
This commit is contained in:
@@ -0,0 +1,41 @@
|
||||
/*
|
||||
* Copyright (c) 2012-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.moquette.server;
|
||||
|
||||
/**
|
||||
* Value object to maintain the information of single connection, like ClientID, IoSession,
|
||||
* and other clean session fla.
|
||||
*
|
||||
*
|
||||
* @author andrea
|
||||
*/
|
||||
public class ConnectionDescriptor {
|
||||
|
||||
public final String clientID;
|
||||
public final ServerChannel session;
|
||||
public final boolean cleanSession;
|
||||
|
||||
public ConnectionDescriptor(String clientID, ServerChannel session, boolean cleanSession) {
|
||||
this.clientID = clientID;
|
||||
this.session = session;
|
||||
this.cleanSession = cleanSession;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "ConnectionDescriptor{" + "clientID=" + clientID + ", cleanSession=" + cleanSession + '}';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
/*
|
||||
* Copyright (c) 2012-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.moquette.server;
|
||||
|
||||
/**
|
||||
* Server constants keeper
|
||||
*/
|
||||
public class Constants {
|
||||
public static final String ATTR_CLIENTID = "ClientID";
|
||||
public static final String CLEAN_SESSION = "cleanSession";
|
||||
public static final String KEEP_ALIVE = "keepAlive";
|
||||
}
|
||||
@@ -0,0 +1,107 @@
|
||||
/*
|
||||
* Copyright (c) 2012-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.moquette.server;
|
||||
|
||||
import io.moquette.server.config.MemoryConfig;
|
||||
import io.moquette.spi.impl.SimpleMessaging;
|
||||
import io.moquette.server.config.FilesystemConfig;
|
||||
import io.moquette.server.config.IConfig;
|
||||
import io.moquette.server.netty.NettyAcceptor;
|
||||
import io.moquette.spi.impl.ProtocolProcessor;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.Properties;
|
||||
|
||||
/**
|
||||
* Launch a configured version of the server.
|
||||
* @author andrea
|
||||
*/
|
||||
public class Server {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(Server.class);
|
||||
|
||||
private ServerAcceptor m_acceptor;
|
||||
|
||||
public static void main(String[] args) throws IOException {
|
||||
final Server server = new Server();
|
||||
server.startServer();
|
||||
System.out.println("Server started, version 0.8-SNAPSHOT");
|
||||
//Bind a shutdown hook
|
||||
Runtime.getRuntime().addShutdownHook(new Thread() {
|
||||
@Override
|
||||
public void run() {
|
||||
server.stopServer();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts Moquette bringing the configuration from the file
|
||||
* located at m_config/moquette.conf
|
||||
*/
|
||||
public void startServer() throws IOException {
|
||||
final IConfig config = new FilesystemConfig();
|
||||
startServer(config);
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts Moquette bringing the configuration from the given file
|
||||
*/
|
||||
public void startServer(File configFile) throws IOException {
|
||||
LOG.info("Using m_config file: " + configFile.getAbsolutePath());
|
||||
final IConfig config = new FilesystemConfig(configFile);
|
||||
startServer(config);
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts the server with the given properties.
|
||||
*
|
||||
* Its suggested to at least have the following properties:
|
||||
* <ul>
|
||||
* <li>port</li>
|
||||
* <li>password_file</li>
|
||||
* </ul>
|
||||
*/
|
||||
public void startServer(Properties configProps) throws IOException {
|
||||
final IConfig config = new MemoryConfig(configProps);
|
||||
startServer(config);
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts Moquette bringing the configuration files from the given Config implementation.
|
||||
*/
|
||||
public void startServer(IConfig config) throws IOException {
|
||||
final String handlerProp = System.getProperty("intercept.handler");
|
||||
if (handlerProp != null) {
|
||||
config.setProperty("intercept.handler", handlerProp);
|
||||
}
|
||||
LOG.info("Persistent store file: " + config.getProperty(io.moquette.commons.Constants.PERSISTENT_STORE_PROPERTY_NAME));
|
||||
final ProtocolProcessor processor = SimpleMessaging.getInstance().init(config);
|
||||
|
||||
m_acceptor = new NettyAcceptor();
|
||||
m_acceptor.initialize(processor, config);
|
||||
}
|
||||
|
||||
public void stopServer() {
|
||||
LOG.info("Server stopping...");
|
||||
m_acceptor.close();
|
||||
SimpleMessaging.getInstance().shutdown();
|
||||
LOG.info("Server stopped");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
/*
|
||||
* Copyright (c) 2012-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.moquette.server;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import io.moquette.server.config.IConfig;
|
||||
import io.moquette.spi.impl.ProtocolProcessor;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author andrea
|
||||
*/
|
||||
public interface ServerAcceptor {
|
||||
|
||||
void initialize(ProtocolProcessor processor, IConfig props) throws IOException;
|
||||
|
||||
void close();
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
/*
|
||||
* Copyright (c) 2012-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.moquette.server;
|
||||
|
||||
import io.netty.util.AttributeKey;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author andrea
|
||||
*/
|
||||
public interface ServerChannel {
|
||||
|
||||
Object getAttribute(AttributeKey<Object> key);
|
||||
|
||||
void setAttribute(AttributeKey<Object> key, Object value);
|
||||
|
||||
void setIdleTime(int idleTime);
|
||||
|
||||
void close(boolean immediately);
|
||||
|
||||
void write(Object value);
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
/*
|
||||
* Copyright (c) 2012-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.moquette.server.config;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.Reader;
|
||||
import java.text.ParseException;
|
||||
import java.util.Properties;
|
||||
|
||||
/**
|
||||
* Configuration that loads file from the classpath
|
||||
*
|
||||
* @author andrea
|
||||
*/
|
||||
public class ClasspathConfig implements IConfig {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(ClasspathConfig.class);
|
||||
|
||||
private final Properties m_properties;
|
||||
|
||||
public ClasspathConfig() {
|
||||
ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
|
||||
InputStream is = classLoader.getResourceAsStream("config/moquette.conf");
|
||||
if (is == null) {
|
||||
throw new RuntimeException("Can't locate the resourse \"config/moquette.conf\"");
|
||||
}
|
||||
Reader configReader = new InputStreamReader(is);
|
||||
ConfigurationParser confParser = new ConfigurationParser();
|
||||
try {
|
||||
confParser.parse(configReader);
|
||||
} catch (ParseException pex) {
|
||||
LOG.warn("An error occurred in parsing configuration, fallback on default configuration", pex);
|
||||
}
|
||||
m_properties = confParser.getProperties();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setProperty(String name, String value) {
|
||||
m_properties.setProperty(name, value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getProperty(String name) {
|
||||
return m_properties.getProperty(name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getProperty(String name, String defaultValue) {
|
||||
return m_properties.getProperty(name, defaultValue);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,123 @@
|
||||
/*
|
||||
* Copyright (c) 2012-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.moquette.server.config;
|
||||
|
||||
import io.moquette.commons.Constants;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.io.*;
|
||||
import java.text.ParseException;
|
||||
import java.util.Properties;
|
||||
|
||||
/**
|
||||
* Mosquitto configuration parser.
|
||||
*
|
||||
* A line that at the very first has # is a comment
|
||||
* Each line has key value format, where the separator used it the space.
|
||||
*
|
||||
* @author andrea
|
||||
*/
|
||||
class ConfigurationParser {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(ConfigurationParser.class);
|
||||
|
||||
private Properties m_properties = new Properties();
|
||||
|
||||
ConfigurationParser() {
|
||||
createDefaults();
|
||||
}
|
||||
|
||||
private void createDefaults() {
|
||||
m_properties.put(Constants.PORT_PROPERTY_NAME, Integer.toString(Constants.PORT));
|
||||
m_properties.put(Constants.HOST_PROPERTY_NAME, Constants.HOST);
|
||||
m_properties.put(Constants.WEB_SOCKET_PORT_PROPERTY_NAME, Integer.toString(Constants.WEBSOCKET_PORT));
|
||||
m_properties.put(Constants.PASSWORD_FILE_PROPERTY_NAME, "");
|
||||
m_properties.put(Constants.PERSISTENT_STORE_PROPERTY_NAME, Constants.DEFAULT_PERSISTENT_PATH);
|
||||
m_properties.put(Constants.ALLOW_ANONYMOUS_PROPERTY_NAME, true);
|
||||
m_properties.put(Constants.AUTHENTICATOR_CLASS_NAME, "");
|
||||
m_properties.put(Constants.AUTHORIZATOR_CLASS_NAME, "");
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse the configuration from file.
|
||||
*/
|
||||
void parse(File file) throws ParseException {
|
||||
if (file == null) {
|
||||
LOG.warn("parsing NULL file, so fallback on default configuration!");
|
||||
return;
|
||||
}
|
||||
if (!file.exists()) {
|
||||
LOG.warn(String.format("parsing not existing file %s, so fallback on default configuration!", file.getAbsolutePath()));
|
||||
return;
|
||||
}
|
||||
try {
|
||||
FileReader reader = new FileReader(file);
|
||||
parse(reader);
|
||||
} catch (FileNotFoundException fex) {
|
||||
LOG.warn(String.format("parsing not existing file %s, so fallback on default configuration!", file.getAbsolutePath()), fex);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse the configuration
|
||||
*
|
||||
* @throws ParseException if the format is not compliant.
|
||||
*/
|
||||
void parse(Reader reader) throws ParseException {
|
||||
if (reader == null) {
|
||||
//just log and return default properties
|
||||
LOG.warn("parsing NULL reader, so fallback on default configuration!");
|
||||
return;
|
||||
}
|
||||
|
||||
BufferedReader br = new BufferedReader(reader);
|
||||
String line;
|
||||
try {
|
||||
while ((line = br.readLine()) != null) {
|
||||
int commentMarker = line.indexOf('#');
|
||||
if (commentMarker != -1) {
|
||||
if (commentMarker == 0) {
|
||||
//skip its a comment
|
||||
continue;
|
||||
} else {
|
||||
//it's a malformed comment
|
||||
throw new ParseException(line, commentMarker);
|
||||
}
|
||||
} else {
|
||||
if (line.isEmpty() || line.matches("^\\s*$")) {
|
||||
//skip it's a black line
|
||||
continue;
|
||||
}
|
||||
|
||||
//split till the first space
|
||||
int delimiterIdx = line.indexOf(' ');
|
||||
String key = line.substring(0, delimiterIdx).trim();
|
||||
String value = line.substring(delimiterIdx).trim();
|
||||
|
||||
m_properties.put(key, value);
|
||||
}
|
||||
}
|
||||
} catch (IOException ex) {
|
||||
throw new ParseException("Failed to read", 1);
|
||||
}
|
||||
}
|
||||
|
||||
Properties getProperties() {
|
||||
return m_properties;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
/*
|
||||
* Copyright (c) 2012-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.moquette.server.config;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.io.File;
|
||||
import java.text.ParseException;
|
||||
import java.util.Properties;
|
||||
|
||||
/**
|
||||
* Configuration that loads file from the file system
|
||||
*
|
||||
* @author andrea
|
||||
*/
|
||||
public class FilesystemConfig implements IConfig {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(FilesystemConfig.class);
|
||||
|
||||
private final Properties m_properties;
|
||||
|
||||
public FilesystemConfig(File file) {
|
||||
ConfigurationParser confParser = new ConfigurationParser();
|
||||
try {
|
||||
confParser.parse(file);
|
||||
} catch (ParseException pex) {
|
||||
LOG.warn("An error occurred in parsing configuration, fallback on default configuration", pex);
|
||||
}
|
||||
m_properties = confParser.getProperties();
|
||||
}
|
||||
|
||||
public FilesystemConfig() {
|
||||
this(defaultConfigFile());
|
||||
}
|
||||
|
||||
private static File defaultConfigFile() {
|
||||
String configPath = System.getProperty("moquette.path", null);
|
||||
return new File(configPath, "config/moquette.conf");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setProperty(String name, String value) {
|
||||
m_properties.setProperty(name, value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getProperty(String name) {
|
||||
return m_properties.getProperty(name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getProperty(String name, String defaultValue) {
|
||||
return m_properties.getProperty(name, defaultValue);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
/*
|
||||
* Copyright (c) 2012-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.moquette.server.config;
|
||||
|
||||
/**
|
||||
* Base interface for all configuration implementations (filesystem, memory or classpath)
|
||||
*
|
||||
* @author andrea
|
||||
*/
|
||||
public interface IConfig {
|
||||
void setProperty(String name, String value);
|
||||
|
||||
String getProperty(String name);
|
||||
|
||||
String getProperty(String name, String defaultValue);
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
/*
|
||||
* Copyright (c) 2012-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.moquette.server.config;
|
||||
|
||||
import io.moquette.commons.Constants;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.Properties;
|
||||
|
||||
/**
|
||||
* Configuration backed by memory.
|
||||
*
|
||||
* @author andrea
|
||||
*/
|
||||
public class MemoryConfig implements IConfig {
|
||||
|
||||
private final Properties m_properties = new Properties();
|
||||
|
||||
public MemoryConfig(Properties properties) {
|
||||
createDefaults();
|
||||
for (Map.Entry<Object, Object> entrySet : properties.entrySet()) {
|
||||
m_properties.put(entrySet.getKey(), entrySet.getValue());
|
||||
}
|
||||
}
|
||||
|
||||
private void createDefaults() {
|
||||
m_properties.put(Constants.PORT_PROPERTY_NAME, Integer.toString(Constants.PORT));
|
||||
m_properties.put(Constants.HOST_PROPERTY_NAME, Constants.HOST);
|
||||
m_properties.put(Constants.WEB_SOCKET_PORT_PROPERTY_NAME, Integer.toString(Constants.WEBSOCKET_PORT));
|
||||
m_properties.put(Constants.PASSWORD_FILE_PROPERTY_NAME, "");
|
||||
m_properties.put(Constants.PERSISTENT_STORE_PROPERTY_NAME, Constants.DEFAULT_PERSISTENT_PATH);
|
||||
m_properties.put(Constants.ALLOW_ANONYMOUS_PROPERTY_NAME, true);
|
||||
m_properties.put(Constants.AUTHENTICATOR_CLASS_NAME, "");
|
||||
m_properties.put(Constants.AUTHORIZATOR_CLASS_NAME, "");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setProperty(String name, String value) {
|
||||
m_properties.setProperty(name, value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getProperty(String name) {
|
||||
return m_properties.getProperty(name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getProperty(String name, String defaultValue) {
|
||||
return m_properties.getProperty(name, defaultValue);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
/*
|
||||
* Copyright (c) 2012-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.moquette.server.netty;
|
||||
|
||||
import io.netty.channel.ChannelDuplexHandler;
|
||||
import io.netty.channel.ChannelHandler.Sharable;
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
import io.netty.handler.timeout.IdleState;
|
||||
import io.netty.handler.timeout.IdleStateEvent;
|
||||
|
||||
@Sharable
|
||||
class MoquetteIdleTimoutHandler extends ChannelDuplexHandler {
|
||||
@Override
|
||||
public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
|
||||
if (evt instanceof IdleStateEvent) {
|
||||
IdleState e = ((IdleStateEvent) evt).state();
|
||||
if (e == IdleState.ALL_IDLE) {
|
||||
//fire a channelInactive to trigger publish of Will
|
||||
ctx.fireChannelInactive();
|
||||
ctx.close();
|
||||
}
|
||||
} else {
|
||||
super.userEventTriggered(ctx, evt);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,366 @@
|
||||
/*
|
||||
* Copyright (c) 2012-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.moquette.server.netty;
|
||||
|
||||
import io.moquette.commons.Constants;
|
||||
import io.moquette.parser.netty.MQTTDecoder;
|
||||
import io.moquette.server.config.IConfig;
|
||||
import io.moquette.server.netty.metrics.*;
|
||||
import io.netty.bootstrap.ServerBootstrap;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.channel.*;
|
||||
import io.netty.channel.nio.NioEventLoopGroup;
|
||||
import io.netty.channel.socket.SocketChannel;
|
||||
import io.netty.channel.socket.nio.NioServerSocketChannel;
|
||||
import io.netty.handler.codec.MessageToMessageDecoder;
|
||||
import io.netty.handler.codec.MessageToMessageEncoder;
|
||||
import io.netty.handler.codec.http.HttpObjectAggregator;
|
||||
import io.netty.handler.codec.http.HttpRequestDecoder;
|
||||
import io.netty.handler.codec.http.HttpResponseEncoder;
|
||||
import io.netty.handler.codec.http.websocketx.BinaryWebSocketFrame;
|
||||
import io.netty.handler.codec.http.websocketx.WebSocketServerProtocolHandler;
|
||||
import io.netty.handler.ssl.SslHandler;
|
||||
import io.netty.handler.timeout.IdleStateHandler;
|
||||
import io.netty.util.concurrent.Future;
|
||||
import io.moquette.parser.netty.MQTTEncoder;
|
||||
import io.moquette.server.ServerAcceptor;
|
||||
import io.moquette.spi.impl.ProtocolProcessor;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import javax.net.ssl.KeyManagerFactory;
|
||||
import javax.net.ssl.SSLContext;
|
||||
import javax.net.ssl.SSLEngine;
|
||||
import java.io.*;
|
||||
import java.net.URL;
|
||||
import java.security.*;
|
||||
import java.security.cert.CertificateException;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author andrea
|
||||
*/
|
||||
public class NettyAcceptor implements ServerAcceptor {
|
||||
|
||||
static class WebSocketFrameToByteBufDecoder extends MessageToMessageDecoder<BinaryWebSocketFrame> {
|
||||
|
||||
@Override
|
||||
protected void decode(ChannelHandlerContext chc, BinaryWebSocketFrame frame, List<Object> out) throws Exception {
|
||||
//convert the frame to a ByteBuf
|
||||
ByteBuf bb = frame.content();
|
||||
//System.out.println("WebSocketFrameToByteBufDecoder decode - " + ByteBufUtil.hexDump(bb));
|
||||
bb.retain();
|
||||
out.add(bb);
|
||||
}
|
||||
}
|
||||
|
||||
static class ByteBufToWebSocketFrameEncoder extends MessageToMessageEncoder<ByteBuf> {
|
||||
|
||||
@Override
|
||||
protected void encode(ChannelHandlerContext chc, ByteBuf bb, List<Object> out) throws Exception {
|
||||
//convert the ByteBuf to a WebSocketFrame
|
||||
BinaryWebSocketFrame result = new BinaryWebSocketFrame();
|
||||
//System.out.println("ByteBufToWebSocketFrameEncoder encode - " + ByteBufUtil.hexDump(bb));
|
||||
result.content().writeBytes(bb);
|
||||
out.add(result);
|
||||
}
|
||||
}
|
||||
|
||||
abstract class PipelineInitializer {
|
||||
|
||||
abstract void init(ChannelPipeline pipeline) throws Exception;
|
||||
}
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(NettyAcceptor.class);
|
||||
|
||||
EventLoopGroup m_bossGroup;
|
||||
EventLoopGroup m_workerGroup;
|
||||
BytesMetricsCollector m_bytesMetricsCollector = new BytesMetricsCollector();
|
||||
MessageMetricsCollector m_metricsCollector = new MessageMetricsCollector();
|
||||
|
||||
@Override
|
||||
public void initialize(ProtocolProcessor processor, IConfig props) throws IOException {
|
||||
m_bossGroup = new NioEventLoopGroup();
|
||||
m_workerGroup = new NioEventLoopGroup();
|
||||
final NettyMQTTHandler handler = new NettyMQTTHandler(processor);
|
||||
|
||||
initializePlainTCPTransport(handler, props);
|
||||
initializeWebSocketTransport(handler, props);
|
||||
String sslTcpPortProp = props.getProperty(Constants.SSL_PORT_PROPERTY_NAME);
|
||||
String wssPortProp = props.getProperty(Constants.WSS_PORT_PROPERTY_NAME);
|
||||
if (sslTcpPortProp != null || wssPortProp != null) {
|
||||
SslHandlerFactory sslHandlerFactory = initSSLHandlerFactory(props);
|
||||
if (!sslHandlerFactory.canCreate()) {
|
||||
LOG.error("Can't initialize SSLHandler layer! Exiting, check your configuration of jks");
|
||||
return;
|
||||
}
|
||||
initializeSSLTCPTransport(handler, props, sslHandlerFactory);
|
||||
initializeWSSTransport(handler, props, sslHandlerFactory);
|
||||
}
|
||||
}
|
||||
|
||||
private void initFactory(String host, int port, final PipelineInitializer pipeliner) {
|
||||
ServerBootstrap b = new ServerBootstrap();
|
||||
b.group(m_bossGroup, m_workerGroup)
|
||||
.channel(NioServerSocketChannel.class)
|
||||
.childHandler(new ChannelInitializer<SocketChannel>() {
|
||||
@Override
|
||||
public void initChannel(SocketChannel ch) throws Exception {
|
||||
ChannelPipeline pipeline = ch.pipeline();
|
||||
try {
|
||||
pipeliner.init(pipeline);
|
||||
} catch (Throwable th) {
|
||||
LOG.error("Severe error during pipeline creation", th);
|
||||
throw th;
|
||||
}
|
||||
}
|
||||
})
|
||||
.option(ChannelOption.SO_BACKLOG, 128)
|
||||
.option(ChannelOption.SO_REUSEADDR, true)
|
||||
.option(ChannelOption.TCP_NODELAY, true)
|
||||
.childOption(ChannelOption.SO_KEEPALIVE, true);
|
||||
try {
|
||||
// Bind and start to accept incoming connections.
|
||||
ChannelFuture f = b.bind(host, port);
|
||||
LOG.info("Server binded host: {}, port: {}", host, port);
|
||||
f.sync();
|
||||
} catch (InterruptedException ex) {
|
||||
LOG.error(null, ex);
|
||||
}
|
||||
}
|
||||
|
||||
private void initializePlainTCPTransport(final NettyMQTTHandler handler, IConfig props) throws IOException {
|
||||
final MoquetteIdleTimoutHandler timeoutHandler = new MoquetteIdleTimoutHandler();
|
||||
String host = props.getProperty(Constants.HOST_PROPERTY_NAME);
|
||||
int port = Integer.parseInt(props.getProperty(Constants.PORT_PROPERTY_NAME));
|
||||
initFactory(host, port, new PipelineInitializer() {
|
||||
@Override
|
||||
void init(ChannelPipeline pipeline) {
|
||||
pipeline.addFirst("idleStateHandler", new IdleStateHandler(0, 0, Constants.DEFAULT_CONNECT_TIMEOUT));
|
||||
pipeline.addAfter("idleStateHandler", "idleEventHandler", timeoutHandler);
|
||||
//pipeline.addLast("logger", new LoggingHandler("Netty", LogLevel.ERROR));
|
||||
pipeline.addFirst("bytemetrics", new BytesMetricsHandler(m_bytesMetricsCollector));
|
||||
pipeline.addLast("decoder", new MQTTDecoder());
|
||||
pipeline.addLast("encoder", new MQTTEncoder());
|
||||
pipeline.addLast("metrics", new MessageMetricsHandler(m_metricsCollector));
|
||||
pipeline.addLast("handler", handler);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void initializeWebSocketTransport(final NettyMQTTHandler handler, IConfig props) throws IOException {
|
||||
String webSocketPortProp = props.getProperty(Constants.WEB_SOCKET_PORT_PROPERTY_NAME);
|
||||
if (webSocketPortProp == null) {
|
||||
//Do nothing no WebSocket configured
|
||||
LOG.info("WebSocket is disabled");
|
||||
return;
|
||||
}
|
||||
int port = Integer.parseInt(webSocketPortProp);
|
||||
|
||||
final MoquetteIdleTimoutHandler timeoutHandler = new MoquetteIdleTimoutHandler();
|
||||
|
||||
String host = props.getProperty(Constants.HOST_PROPERTY_NAME);
|
||||
initFactory(host, port, new PipelineInitializer() {
|
||||
@Override
|
||||
void init(ChannelPipeline pipeline) {
|
||||
pipeline.addLast("httpEncoder", new HttpResponseEncoder());
|
||||
pipeline.addLast("httpDecoder", new HttpRequestDecoder());
|
||||
pipeline.addLast("aggregator", new HttpObjectAggregator(65536));
|
||||
pipeline.addLast("webSocketHandler", new WebSocketServerProtocolHandler("/mqtt", "mqtt, mqttv3.1, mqttv3.1.1"));
|
||||
pipeline.addLast("ws2bytebufDecoder", new WebSocketFrameToByteBufDecoder());
|
||||
pipeline.addLast("bytebuf2wsEncoder", new ByteBufToWebSocketFrameEncoder());
|
||||
pipeline.addFirst("idleStateHandler", new IdleStateHandler(0, 0, Constants.DEFAULT_CONNECT_TIMEOUT));
|
||||
pipeline.addAfter("idleStateHandler", "idleEventHandler", timeoutHandler);
|
||||
pipeline.addFirst("bytemetrics", new BytesMetricsHandler(m_bytesMetricsCollector));
|
||||
pipeline.addLast("decoder", new MQTTDecoder());
|
||||
pipeline.addLast("encoder", new MQTTEncoder());
|
||||
pipeline.addLast("metrics", new MessageMetricsHandler(m_metricsCollector));
|
||||
pipeline.addLast("handler", handler);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void initializeSSLTCPTransport(final NettyMQTTHandler handler, IConfig props, final SslHandlerFactory sslHandlerFactory) throws IOException {
|
||||
String sslPortProp = props.getProperty(Constants.SSL_PORT_PROPERTY_NAME);
|
||||
if (sslPortProp == null) {
|
||||
//Do nothing no SSL configured
|
||||
LOG.info("SSL is disabled");
|
||||
return;
|
||||
}
|
||||
|
||||
int sslPort = Integer.parseInt(sslPortProp);
|
||||
LOG.info("Starting SSL on port {}", sslPort);
|
||||
|
||||
final MoquetteIdleTimoutHandler timeoutHandler = new MoquetteIdleTimoutHandler();
|
||||
String host = props.getProperty(Constants.HOST_PROPERTY_NAME);
|
||||
initFactory(host, sslPort, new PipelineInitializer() {
|
||||
@Override
|
||||
void init(ChannelPipeline pipeline) throws Exception {
|
||||
pipeline.addLast("ssl", sslHandlerFactory.create());
|
||||
pipeline.addFirst("idleStateHandler", new IdleStateHandler(0, 0, Constants.DEFAULT_CONNECT_TIMEOUT));
|
||||
pipeline.addAfter("idleStateHandler", "idleEventHandler", timeoutHandler);
|
||||
//pipeline.addLast("logger", new LoggingHandler("Netty", LogLevel.ERROR));
|
||||
pipeline.addFirst("bytemetrics", new BytesMetricsHandler(m_bytesMetricsCollector));
|
||||
pipeline.addLast("decoder", new MQTTDecoder());
|
||||
pipeline.addLast("encoder", new MQTTEncoder());
|
||||
pipeline.addLast("metrics", new MessageMetricsHandler(m_metricsCollector));
|
||||
pipeline.addLast("handler", handler);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void initializeWSSTransport(final NettyMQTTHandler handler, IConfig props, final SslHandlerFactory sslHandlerFactory) throws IOException {
|
||||
String sslPortProp = props.getProperty(Constants.WSS_PORT_PROPERTY_NAME);
|
||||
if (sslPortProp == null) {
|
||||
//Do nothing no SSL configured
|
||||
LOG.info("SSL is disabled");
|
||||
return;
|
||||
}
|
||||
int sslPort = Integer.parseInt(sslPortProp);
|
||||
final MoquetteIdleTimoutHandler timeoutHandler = new MoquetteIdleTimoutHandler();
|
||||
String host = props.getProperty(Constants.HOST_PROPERTY_NAME);
|
||||
initFactory(host, sslPort, new PipelineInitializer() {
|
||||
@Override
|
||||
void init(ChannelPipeline pipeline) throws Exception {
|
||||
pipeline.addLast("ssl", sslHandlerFactory.create());
|
||||
pipeline.addLast("httpEncoder", new HttpResponseEncoder());
|
||||
pipeline.addLast("httpDecoder", new HttpRequestDecoder());
|
||||
pipeline.addLast("aggregator", new HttpObjectAggregator(65536));
|
||||
pipeline.addLast("webSocketHandler", new WebSocketServerProtocolHandler("/mqtt", "mqtt mqttv3.1, mqttv3.1.1"));
|
||||
pipeline.addLast("ws2bytebufDecoder", new WebSocketFrameToByteBufDecoder());
|
||||
pipeline.addLast("bytebuf2wsEncoder", new ByteBufToWebSocketFrameEncoder());
|
||||
pipeline.addFirst("idleStateHandler", new IdleStateHandler(0, 0, Constants.DEFAULT_CONNECT_TIMEOUT));
|
||||
pipeline.addAfter("idleStateHandler", "idleEventHandler", timeoutHandler);
|
||||
pipeline.addFirst("bytemetrics", new BytesMetricsHandler(m_bytesMetricsCollector));
|
||||
pipeline.addLast("decoder", new MQTTDecoder());
|
||||
pipeline.addLast("encoder", new MQTTEncoder());
|
||||
pipeline.addLast("metrics", new MessageMetricsHandler(m_metricsCollector));
|
||||
pipeline.addLast("handler", handler);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public void close() {
|
||||
if (m_workerGroup == null) {
|
||||
throw new IllegalStateException("Invoked close on an Acceptor that wasn't initialized");
|
||||
}
|
||||
if (m_bossGroup == null) {
|
||||
throw new IllegalStateException("Invoked close on an Acceptor that wasn't initialized");
|
||||
}
|
||||
Future workerWaiter = m_workerGroup.shutdownGracefully();
|
||||
Future bossWaiter = m_bossGroup.shutdownGracefully();
|
||||
|
||||
try {
|
||||
workerWaiter.await(100);
|
||||
} catch (InterruptedException iex) {
|
||||
throw new IllegalStateException(iex);
|
||||
}
|
||||
|
||||
try {
|
||||
bossWaiter.await(100);
|
||||
} catch (InterruptedException iex) {
|
||||
throw new IllegalStateException(iex);
|
||||
}
|
||||
|
||||
MessageMetrics metrics = m_metricsCollector.computeMetrics();
|
||||
LOG.info("Msg read: {}, msg wrote: {}", metrics.messagesRead(), metrics.messagesWrote());
|
||||
|
||||
BytesMetrics bytesMetrics = m_bytesMetricsCollector.computeMetrics();
|
||||
LOG.info(String.format("Bytes read: %d, bytes wrote: %d", bytesMetrics.readBytes(), bytesMetrics.wroteBytes()));
|
||||
}
|
||||
|
||||
|
||||
private SslHandlerFactory initSSLHandlerFactory(IConfig props) {
|
||||
SslHandlerFactory factory = new SslHandlerFactory(props);
|
||||
return factory.canCreate() ? factory : null;
|
||||
}
|
||||
|
||||
private static class SslHandlerFactory {
|
||||
|
||||
private SSLContext sslContext;
|
||||
|
||||
public SslHandlerFactory(IConfig props) {
|
||||
this.sslContext = initSSLContext(props);
|
||||
}
|
||||
|
||||
public boolean canCreate() {
|
||||
return this.sslContext != null;
|
||||
}
|
||||
private SSLContext initSSLContext(IConfig props) {
|
||||
final String jksPath = props.getProperty(Constants.JKS_PATH_PROPERTY_NAME);
|
||||
LOG.info("Starting SSL using keystore at {}", jksPath);
|
||||
if (jksPath == null || jksPath.isEmpty()) {
|
||||
//key_store_password or key_manager_password are empty
|
||||
LOG.warn("You have configured the SSL port but not the jks_path, SSL not started");
|
||||
return null;
|
||||
}
|
||||
|
||||
//if we have the port also the jks then keyStorePassword and keyManagerPassword
|
||||
//has to be defined
|
||||
final String keyStorePassword = props.getProperty(Constants.KEY_STORE_PASSWORD_PROPERTY_NAME);
|
||||
final String keyManagerPassword = props.getProperty(Constants.KEY_MANAGER_PASSWORD_PROPERTY_NAME);
|
||||
if (keyStorePassword == null || keyStorePassword.isEmpty()) {
|
||||
//key_store_password or key_manager_password are empty
|
||||
LOG.warn("You have configured the SSL port but not the key_store_password, SSL not started");
|
||||
return null;
|
||||
}
|
||||
if (keyManagerPassword == null || keyManagerPassword.isEmpty()) {
|
||||
//key_manager_password or key_manager_password are empty
|
||||
LOG.warn("You have configured the SSL port but not the key_manager_password, SSL not started");
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
InputStream jksInputStream = jksDatastore(jksPath);
|
||||
SSLContext serverContext = SSLContext.getInstance("TLS");
|
||||
final KeyStore ks = KeyStore.getInstance("JKS");
|
||||
ks.load(jksInputStream, keyStorePassword.toCharArray());
|
||||
final KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
|
||||
kmf.init(ks, keyManagerPassword.toCharArray());
|
||||
serverContext.init(kmf.getKeyManagers(), null, null);
|
||||
return serverContext;
|
||||
} catch (NoSuchAlgorithmException | UnrecoverableKeyException | CertificateException | KeyStoreException
|
||||
| KeyManagementException | IOException ex) {
|
||||
LOG.error("Can't start SSL layer!", ex);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private InputStream jksDatastore(String jksPath) throws FileNotFoundException {
|
||||
URL jksUrl = getClass().getClassLoader().getResource(jksPath);
|
||||
if (jksUrl != null) {
|
||||
LOG.info("Starting with jks at {}, jks normal {}", jksUrl.toExternalForm(), jksUrl);
|
||||
return getClass().getClassLoader().getResourceAsStream(jksPath);
|
||||
}
|
||||
LOG.info("jks not found in bundled resources, try on the filesystem");
|
||||
File jksFile = new File(jksPath);
|
||||
if (jksFile.exists()) {
|
||||
LOG.info("Using {} ", jksFile.getAbsolutePath());
|
||||
return new FileInputStream(jksFile);
|
||||
}
|
||||
LOG.warn("File {} doesn't exists", jksFile.getAbsolutePath());
|
||||
return null;
|
||||
}
|
||||
|
||||
public ChannelHandler create() {
|
||||
SSLEngine sslEngine = sslContext.createSSLEngine();
|
||||
sslEngine.setUseClientMode(false);
|
||||
return new SslHandler(sslEngine);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
/*
|
||||
* Copyright (c) 2012-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.moquette.server.netty;
|
||||
|
||||
import io.moquette.server.Constants;
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
import io.netty.handler.timeout.IdleStateHandler;
|
||||
import io.netty.util.Attribute;
|
||||
import io.netty.util.AttributeKey;
|
||||
import io.moquette.server.ServerChannel;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author andrea
|
||||
*/
|
||||
public class NettyChannel implements ServerChannel {
|
||||
|
||||
private ChannelHandlerContext m_channel;
|
||||
|
||||
public static final String ATTR_USERNAME = "username";
|
||||
public static final String ATTR_SESSION_STOLEN = "sessionStolen";
|
||||
|
||||
public static final AttributeKey<Object> ATTR_KEY_KEEPALIVE = AttributeKey.valueOf(Constants.KEEP_ALIVE);
|
||||
public static final AttributeKey<Object> ATTR_KEY_CLEANSESSION = AttributeKey.valueOf(Constants.CLEAN_SESSION);
|
||||
public static final AttributeKey<Object> ATTR_KEY_CLIENTID = AttributeKey.valueOf(Constants.ATTR_CLIENTID);
|
||||
public static final AttributeKey<Object> ATTR_KEY_USERNAME = AttributeKey.valueOf(ATTR_USERNAME);
|
||||
public static final AttributeKey<Object> ATTR_KEY_SESSION_STOLEN = AttributeKey.valueOf(ATTR_SESSION_STOLEN);
|
||||
|
||||
NettyChannel(ChannelHandlerContext ctx) {
|
||||
m_channel = ctx;
|
||||
}
|
||||
|
||||
public Object getAttribute(AttributeKey<Object> key) {
|
||||
Attribute<Object> attr = m_channel.attr(key);
|
||||
return attr.get();
|
||||
}
|
||||
|
||||
public void setAttribute(AttributeKey<Object> key, Object value) {
|
||||
Attribute<Object> attr = m_channel.attr(key);
|
||||
attr.set(value);
|
||||
}
|
||||
|
||||
public void setIdleTime(int idleTime) {
|
||||
if (m_channel.pipeline().names().contains("idleStateHandler")) {
|
||||
m_channel.pipeline().remove("idleStateHandler");
|
||||
}
|
||||
m_channel.pipeline().addFirst("idleStateHandler", new IdleStateHandler(0, 0, idleTime));
|
||||
}
|
||||
|
||||
public void close(boolean immediately) {
|
||||
m_channel.close();
|
||||
}
|
||||
|
||||
public void write(Object value) {
|
||||
m_channel.writeAndFlush(value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
String clientID = (String) getAttribute(ATTR_KEY_CLIENTID);
|
||||
return "session [clientID: "+ clientID +"]";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,114 @@
|
||||
/*
|
||||
* Copyright (c) 2012-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.moquette.server.netty;
|
||||
|
||||
import io.moquette.proto.Utils;
|
||||
import io.moquette.proto.messages.*;
|
||||
import io.moquette.spi.impl.ProtocolProcessor;
|
||||
import static io.moquette.proto.messages.AbstractMessage.*;
|
||||
import io.netty.channel.ChannelHandler.Sharable;
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
import io.netty.channel.ChannelInboundHandlerAdapter;
|
||||
|
||||
import io.netty.handler.codec.CorruptedFrameException;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author andrea
|
||||
*/
|
||||
@Sharable
|
||||
public class NettyMQTTHandler extends ChannelInboundHandlerAdapter {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(NettyMQTTHandler.class);
|
||||
private final ProtocolProcessor m_processor;
|
||||
|
||||
public NettyMQTTHandler(ProtocolProcessor processor) {
|
||||
m_processor = processor;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void channelRead(ChannelHandlerContext ctx, Object message) {
|
||||
AbstractMessage msg = (AbstractMessage) message;
|
||||
LOG.info("Received a message of type {}", Utils.msgType2String(msg.getMessageType()));
|
||||
try {
|
||||
switch (msg.getMessageType()) {
|
||||
case CONNECT:
|
||||
m_processor.processConnect(new NettyChannel(ctx), (ConnectMessage) msg);
|
||||
break;
|
||||
case SUBSCRIBE:
|
||||
m_processor.processSubscribe(new NettyChannel(ctx), (SubscribeMessage) msg);
|
||||
break;
|
||||
case UNSUBSCRIBE:
|
||||
m_processor.processUnsubscribe(new NettyChannel(ctx), (UnsubscribeMessage) msg);
|
||||
break;
|
||||
case PUBLISH:
|
||||
m_processor.processPublish(new NettyChannel(ctx), (PublishMessage) msg);
|
||||
break;
|
||||
case PUBREC:
|
||||
m_processor.processPubRec(new NettyChannel(ctx), (PubRecMessage) msg);
|
||||
break;
|
||||
case PUBCOMP:
|
||||
m_processor.processPubComp(new NettyChannel(ctx), (PubCompMessage) msg);
|
||||
break;
|
||||
case PUBREL:
|
||||
m_processor.processPubRel(new NettyChannel(ctx), (PubRelMessage) msg);
|
||||
break;
|
||||
case DISCONNECT:
|
||||
m_processor.processDisconnect(new NettyChannel(ctx));
|
||||
break;
|
||||
case PUBACK:
|
||||
m_processor.processPubAck(new NettyChannel(ctx), (PubAckMessage) msg);
|
||||
break;
|
||||
case PINGREQ:
|
||||
PingRespMessage pingResp = new PingRespMessage();
|
||||
ctx.writeAndFlush(pingResp);
|
||||
break;
|
||||
}
|
||||
} catch (Exception ex) {
|
||||
LOG.error("Bad error in processing the message", ex);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void channelInactive(ChannelHandlerContext ctx) throws Exception {
|
||||
String clientID = (String) NettyUtils.getAttribute(ctx, NettyChannel.ATTR_KEY_CLIENTID);
|
||||
if (clientID != null && !clientID.isEmpty()) {
|
||||
//if the channel was of a correctly connected client, inform messaging
|
||||
//else it was of a not completed CONNECT message or sessionStolen
|
||||
boolean stolen = false;
|
||||
Boolean stolenAttr = (Boolean) NettyUtils.getAttribute(ctx, NettyChannel.ATTR_KEY_SESSION_STOLEN);
|
||||
if (stolenAttr != null && stolenAttr == Boolean.TRUE) {
|
||||
stolen = stolenAttr;
|
||||
}
|
||||
m_processor.processConnectionLost(clientID, stolen);
|
||||
}
|
||||
ctx.close(/*false*/);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
|
||||
if (cause instanceof CorruptedFrameException) {
|
||||
//something goes bad with decoding
|
||||
LOG.warn("Error decoding a packet, probably a bad formatted packet, message: " + cause.getMessage());
|
||||
} else {
|
||||
LOG.error("Ugly error on networking", cause);
|
||||
}
|
||||
ctx.close();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
/*
|
||||
* Copyright (c) 2012-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.moquette.server.netty;
|
||||
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
import io.netty.handler.timeout.IdleStateHandler;
|
||||
import io.netty.util.Attribute;
|
||||
import io.netty.util.AttributeKey;
|
||||
|
||||
/**
|
||||
* Some Netty's channels utilities.
|
||||
*
|
||||
* @author andrea
|
||||
*/
|
||||
public class NettyUtils {
|
||||
|
||||
public static Object getAttribute(ChannelHandlerContext channel, AttributeKey<Object> key) {
|
||||
Attribute<Object> attr = channel.attr(key);
|
||||
return attr.get();
|
||||
}
|
||||
|
||||
public static void setAttribute(ChannelHandlerContext channel, AttributeKey<Object> key, Object value) {
|
||||
Attribute<Object> attr = channel.attr(key);
|
||||
attr.set(value);
|
||||
}
|
||||
|
||||
public static void setIdleTime(ChannelHandlerContext channel, int idleTime) {
|
||||
if (channel.pipeline().names().contains("idleStateHandler")) {
|
||||
channel.pipeline().remove("idleStateHandler");
|
||||
}
|
||||
if (channel.pipeline().names().contains("idleEventHandler")) {
|
||||
channel.pipeline().remove("idleEventHandler");
|
||||
}
|
||||
channel.pipeline().addFirst("idleStateHandler", new IdleStateHandler(0, 0, idleTime));
|
||||
channel.pipeline().addAfter("idleStateHandler", "idleEventHandler", new MoquetteIdleTimoutHandler());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
/*
|
||||
* Copyright (c) 2012-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.moquette.server.netty.metrics;
|
||||
|
||||
public class BytesMetrics {
|
||||
private long m_readBytes = 0;
|
||||
private long m_wroteBytes = 0;
|
||||
|
||||
void incrementRead(long numBytes) {
|
||||
m_readBytes += numBytes;
|
||||
}
|
||||
|
||||
void incrementWrote(long numBytes) {
|
||||
m_wroteBytes += numBytes;
|
||||
}
|
||||
|
||||
public long readBytes() {
|
||||
return m_readBytes;
|
||||
}
|
||||
|
||||
public long wroteBytes() {
|
||||
return m_wroteBytes;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
/*
|
||||
* Copyright (c) 2012-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.moquette.server.netty.metrics;
|
||||
|
||||
import java.util.concurrent.atomic.AtomicLong;
|
||||
|
||||
/**
|
||||
* Collects all the metrics from the various pipeline.
|
||||
*/
|
||||
public class BytesMetricsCollector {
|
||||
|
||||
private AtomicLong readBytes = new AtomicLong();
|
||||
private AtomicLong wroteBytes = new AtomicLong();
|
||||
|
||||
public BytesMetrics computeMetrics() {
|
||||
BytesMetrics allMetrics = new BytesMetrics();
|
||||
allMetrics.incrementRead(readBytes.get());
|
||||
allMetrics.incrementWrote(wroteBytes.get());
|
||||
return allMetrics;
|
||||
}
|
||||
|
||||
public void sumReadBytes(long count) {
|
||||
readBytes.getAndAdd(count);
|
||||
}
|
||||
|
||||
public void sumWroteBytes(long count) {
|
||||
wroteBytes.getAndAdd(count);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
/*
|
||||
* Copyright (c) 2012-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.moquette.server.netty.metrics;
|
||||
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.channel.ChannelDuplexHandler;
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
import io.netty.channel.ChannelPromise;
|
||||
import io.netty.util.Attribute;
|
||||
import io.netty.util.AttributeKey;
|
||||
|
||||
public class BytesMetricsHandler extends ChannelDuplexHandler {
|
||||
|
||||
private static final AttributeKey<BytesMetrics> ATTR_KEY_METRICS = AttributeKey.valueOf("BytesMetrics");
|
||||
|
||||
private BytesMetricsCollector m_collector;
|
||||
|
||||
public BytesMetricsHandler(BytesMetricsCollector collector) {
|
||||
m_collector = collector;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void channelActive(ChannelHandlerContext ctx) throws Exception {
|
||||
Attribute<BytesMetrics> attr = ctx.attr(ATTR_KEY_METRICS);
|
||||
attr.set(new BytesMetrics());
|
||||
|
||||
super.channelActive(ctx);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
|
||||
BytesMetrics metrics = ctx.attr(ATTR_KEY_METRICS).get();
|
||||
metrics.incrementRead(((ByteBuf)msg).readableBytes());
|
||||
ctx.fireChannelRead(msg);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception {
|
||||
BytesMetrics metrics = ctx.attr(ATTR_KEY_METRICS).get();
|
||||
metrics.incrementWrote(((ByteBuf)msg).writableBytes());
|
||||
ctx.write(msg, promise);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void close(ChannelHandlerContext ctx,
|
||||
ChannelPromise promise) throws Exception {
|
||||
BytesMetrics metrics = ctx.attr(ATTR_KEY_METRICS).get();
|
||||
m_collector.sumReadBytes(metrics.readBytes());
|
||||
m_collector.sumWroteBytes(metrics.wroteBytes());
|
||||
super.close(ctx, promise);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
/*
|
||||
* Copyright (c) 2012-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.moquette.server.netty.metrics;
|
||||
|
||||
public class MessageMetrics {
|
||||
private long m_messagesRead = 0;
|
||||
private long m_messageWrote = 0;
|
||||
|
||||
void incrementRead(long numMessages) {
|
||||
m_messagesRead += numMessages;
|
||||
}
|
||||
|
||||
void incrementWrote(long numMessages) {
|
||||
m_messageWrote += numMessages;
|
||||
}
|
||||
|
||||
public long messagesRead() {
|
||||
return m_messagesRead;
|
||||
}
|
||||
|
||||
public long messagesWrote() {
|
||||
return m_messageWrote;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
/*
|
||||
* Copyright (c) 2012-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.moquette.server.netty.metrics;
|
||||
|
||||
import java.util.concurrent.atomic.AtomicLong;
|
||||
|
||||
/**
|
||||
* Collects all the metrics from the various pipeline.
|
||||
*/
|
||||
public class MessageMetricsCollector {
|
||||
private AtomicLong readMsgs = new AtomicLong();
|
||||
private AtomicLong wroteMsgs = new AtomicLong();
|
||||
|
||||
public MessageMetrics computeMetrics() {
|
||||
MessageMetrics allMetrics = new MessageMetrics();
|
||||
allMetrics.incrementRead(readMsgs.get());
|
||||
allMetrics.incrementWrote(wroteMsgs.get());
|
||||
return allMetrics;
|
||||
}
|
||||
|
||||
public void sumReadMessages(long count) {
|
||||
readMsgs.getAndAdd(count);
|
||||
}
|
||||
|
||||
public void sumWroteMessages(long count) {
|
||||
wroteMsgs.getAndAdd(count);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
/*
|
||||
* Copyright (c) 2012-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.moquette.server.netty.metrics;
|
||||
|
||||
import io.netty.channel.ChannelDuplexHandler;
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
import io.netty.channel.ChannelPromise;
|
||||
import io.netty.util.Attribute;
|
||||
import io.netty.util.AttributeKey;
|
||||
|
||||
public class MessageMetricsHandler extends ChannelDuplexHandler {
|
||||
|
||||
private static final AttributeKey<MessageMetrics> ATTR_KEY_METRICS = AttributeKey.valueOf("MessageMetrics");
|
||||
|
||||
private MessageMetricsCollector m_collector;
|
||||
|
||||
public MessageMetricsHandler(MessageMetricsCollector collector) {
|
||||
m_collector = collector;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void channelActive(ChannelHandlerContext ctx) throws Exception {
|
||||
Attribute<MessageMetrics> attr = ctx.attr(ATTR_KEY_METRICS);
|
||||
attr.set(new MessageMetrics());
|
||||
|
||||
super.channelActive(ctx);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
|
||||
MessageMetrics metrics = ctx.attr(ATTR_KEY_METRICS).get();
|
||||
metrics.incrementRead(1);
|
||||
ctx.fireChannelRead(msg);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception {
|
||||
MessageMetrics metrics = ctx.attr(ATTR_KEY_METRICS).get();
|
||||
metrics.incrementWrote(1);
|
||||
ctx.write(msg, promise);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void close(ChannelHandlerContext ctx,
|
||||
ChannelPromise promise) throws Exception {
|
||||
MessageMetrics metrics = ctx.attr(ATTR_KEY_METRICS).get();
|
||||
m_collector.sumReadMessages(metrics.messagesRead());
|
||||
m_collector.sumWroteMessages(metrics.messagesWrote());
|
||||
super.close(ctx, promise);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user