Moved packages from old io.moquette.server to new io.moquette.broker
This commit is contained in:
@@ -1,31 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2012-2018 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 final class Constants {
|
||||
|
||||
public static final String ATTR_CLIENTID = "ClientID";
|
||||
public static final String CLEAN_SESSION = "removeTemporaryQoS2";
|
||||
public static final String KEEP_ALIVE = "keepAlive";
|
||||
public static final int MAX_MESSAGE_QUEUE = 1024; // number of messages
|
||||
|
||||
private Constants() {
|
||||
}
|
||||
}
|
||||
@@ -1,187 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2012-2018 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.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.net.URL;
|
||||
import java.security.GeneralSecurityException;
|
||||
import java.security.KeyManagementException;
|
||||
import java.security.KeyStore;
|
||||
import java.security.KeyStoreException;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.PrivateKey;
|
||||
import java.security.cert.Certificate;
|
||||
import java.security.cert.X509Certificate;
|
||||
import java.util.Collections;
|
||||
import java.util.Objects;
|
||||
|
||||
import io.moquette.BrokerConstants;
|
||||
import io.moquette.server.config.IConfig;
|
||||
import io.moquette.spi.security.ISslContextCreator;
|
||||
import io.netty.handler.ssl.ClientAuth;
|
||||
import io.netty.handler.ssl.SslContext;
|
||||
import io.netty.handler.ssl.SslContextBuilder;
|
||||
import io.netty.handler.ssl.SslProvider;
|
||||
import javax.net.ssl.KeyManagerFactory;
|
||||
import javax.net.ssl.TrustManagerFactory;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* Moquette server implementation to load SSL certificate from local filesystem path configured in
|
||||
* config file.
|
||||
*/
|
||||
public class DefaultMoquetteSslContextCreator implements ISslContextCreator {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(DefaultMoquetteSslContextCreator.class);
|
||||
|
||||
private final IConfig props;
|
||||
|
||||
public DefaultMoquetteSslContextCreator(IConfig props) {
|
||||
this.props = Objects.requireNonNull(props);
|
||||
}
|
||||
|
||||
@Override
|
||||
public SslContext initSSLContext() {
|
||||
LOG.info("Checking SSL configuration properties...");
|
||||
|
||||
final String keyPassword = props.getProperty(BrokerConstants.KEY_MANAGER_PASSWORD_PROPERTY_NAME);
|
||||
if (keyPassword == null || keyPassword.isEmpty()) {
|
||||
LOG.warn("The key manager password is null or empty. The SSL context won't be initialized.");
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
SslProvider sslProvider = getSSLProvider();
|
||||
KeyStore ks = loadKeyStore();
|
||||
SslContextBuilder contextBuilder;
|
||||
switch (sslProvider) {
|
||||
case JDK:
|
||||
contextBuilder = builderWithJdkProvider(ks, keyPassword);
|
||||
break;
|
||||
case OPENSSL:
|
||||
case OPENSSL_REFCNT:
|
||||
contextBuilder = builderWithOpenSSLProvider(ks, keyPassword);
|
||||
break;
|
||||
default:
|
||||
LOG.error("unsupported SSL provider {}", sslProvider);
|
||||
return null;
|
||||
}
|
||||
// if client authentification is enabled a trustmanager needs to be added to the ServerContext
|
||||
String sNeedsClientAuth = props.getProperty(BrokerConstants.NEED_CLIENT_AUTH, "false");
|
||||
if (Boolean.valueOf(sNeedsClientAuth)) {
|
||||
addClientAuthentication(ks, contextBuilder);
|
||||
}
|
||||
contextBuilder.sslProvider(sslProvider);
|
||||
SslContext sslContext = contextBuilder.build();
|
||||
LOG.info("The SSL context has been initialized successfully.");
|
||||
return sslContext;
|
||||
} catch (GeneralSecurityException | IOException ex) {
|
||||
LOG.error("Unable to initialize SSL context.", ex);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private KeyStore loadKeyStore() throws IOException, GeneralSecurityException {
|
||||
final String jksPath = props.getProperty(BrokerConstants.JKS_PATH_PROPERTY_NAME);
|
||||
LOG.info("Initializing SSL context. KeystorePath = {}.", jksPath);
|
||||
if (jksPath == null || jksPath.isEmpty()) {
|
||||
LOG.warn("The keystore path is null or empty. The SSL context won't be initialized.");
|
||||
return null;
|
||||
}
|
||||
final String keyStorePassword = props.getProperty(BrokerConstants.KEY_STORE_PASSWORD_PROPERTY_NAME);
|
||||
if (keyStorePassword == null || keyStorePassword.isEmpty()) {
|
||||
LOG.warn("The keystore password is null or empty. The SSL context won't be initialized.");
|
||||
return null;
|
||||
}
|
||||
String ksType = props.getProperty(BrokerConstants.KEY_STORE_TYPE, "jks");
|
||||
final KeyStore keyStore = KeyStore.getInstance(ksType);
|
||||
LOG.info("Loading keystore. KeystorePath = {}.", jksPath);
|
||||
try (InputStream jksInputStream = jksDatastore(jksPath)) {
|
||||
keyStore.load(jksInputStream, keyStorePassword.toCharArray());
|
||||
}
|
||||
return keyStore;
|
||||
}
|
||||
|
||||
private static SslContextBuilder builderWithJdkProvider(KeyStore ks, String keyPassword)
|
||||
throws GeneralSecurityException {
|
||||
LOG.info("Initializing key manager...");
|
||||
final KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
|
||||
kmf.init(ks, keyPassword.toCharArray());
|
||||
LOG.info("Initializing SSL context...");
|
||||
return SslContextBuilder.forServer(kmf);
|
||||
}
|
||||
|
||||
/**
|
||||
* The OpenSSL provider does not support the {@link KeyManagerFactory}, so we have to lookup the server
|
||||
* certificate and key in order to provide it to OpenSSL.
|
||||
* <p>
|
||||
* TODO: SNI is currently not supported, we use only the first found private key.
|
||||
*/
|
||||
private static SslContextBuilder builderWithOpenSSLProvider(KeyStore ks, String keyPassword)
|
||||
throws GeneralSecurityException {
|
||||
for (String alias : Collections.list(ks.aliases())) {
|
||||
if (ks.entryInstanceOf(alias, KeyStore.PrivateKeyEntry.class)) {
|
||||
PrivateKey key = (PrivateKey) ks.getKey(alias, keyPassword.toCharArray());
|
||||
Certificate[] chain = ks.getCertificateChain(alias);
|
||||
X509Certificate[] certChain = new X509Certificate[chain.length];
|
||||
System.arraycopy(chain, 0, certChain, 0, chain.length);
|
||||
return SslContextBuilder.forServer(key, certChain);
|
||||
}
|
||||
}
|
||||
throw new KeyManagementException("the SSL key-store does not contain a private key");
|
||||
}
|
||||
|
||||
private static void addClientAuthentication(KeyStore ks, SslContextBuilder contextBuilder)
|
||||
throws NoSuchAlgorithmException, KeyStoreException {
|
||||
LOG.warn("Client authentication is enabled. The keystore will be used as a truststore.");
|
||||
// use keystore as truststore, as server needs to trust certificates signed by the server certificates
|
||||
TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
|
||||
tmf.init(ks);
|
||||
contextBuilder.clientAuth(ClientAuth.REQUIRE);
|
||||
contextBuilder.trustManager(tmf);
|
||||
}
|
||||
|
||||
private SslProvider getSSLProvider() {
|
||||
String providerName = props.getProperty(BrokerConstants.SSL_PROVIDER, SslProvider.JDK.name());
|
||||
try {
|
||||
return SslProvider.valueOf(providerName);
|
||||
} catch (IllegalArgumentException e) {
|
||||
LOG.warn("unknown SSL Provider {}, falling back on JDK provider", providerName);
|
||||
return SslProvider.JDK;
|
||||
}
|
||||
}
|
||||
|
||||
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.warn("No keystore has been found in the bundled resources. Scanning filesystem...");
|
||||
File jksFile = new File(jksPath);
|
||||
if (jksFile.exists()) {
|
||||
LOG.info("Loading external keystore. Url = {}.", jksFile.getAbsolutePath());
|
||||
return new FileInputStream(jksFile);
|
||||
}
|
||||
throw new FileNotFoundException("The keystore file does not exist. Url = " + jksFile.getAbsolutePath());
|
||||
}
|
||||
}
|
||||
@@ -1,64 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2012-2018 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 java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.Reader;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
public class ClasspathResourceLoader implements IResourceLoader {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(ClasspathResourceLoader.class);
|
||||
|
||||
private final String defaultResource;
|
||||
private final ClassLoader classLoader;
|
||||
|
||||
public ClasspathResourceLoader() {
|
||||
this(IConfig.DEFAULT_CONFIG);
|
||||
}
|
||||
|
||||
public ClasspathResourceLoader(String defaultResource) {
|
||||
this(defaultResource, Thread.currentThread().getContextClassLoader());
|
||||
}
|
||||
|
||||
public ClasspathResourceLoader(String defaultResource, ClassLoader classLoader) {
|
||||
this.defaultResource = defaultResource;
|
||||
this.classLoader = classLoader;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Reader loadDefaultResource() {
|
||||
return loadResource(defaultResource);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Reader loadResource(String relativePath) {
|
||||
LOG.info("Loading resource. RelativePath = {}.", relativePath);
|
||||
InputStream is = this.classLoader.getResourceAsStream(relativePath);
|
||||
return is != null ? new InputStreamReader(is, StandardCharsets.UTF_8) : null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "classpath resource";
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,121 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2012-2018 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.BufferedReader;
|
||||
import java.io.File;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.FileReader;
|
||||
import java.io.IOException;
|
||||
import java.io.Reader;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Files;
|
||||
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.
|
||||
*/
|
||||
class ConfigurationParser {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(ConfigurationParser.class);
|
||||
|
||||
private Properties m_properties = new Properties();
|
||||
|
||||
/**
|
||||
* 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 {
|
||||
Reader reader = Files.newBufferedReader(file.toPath(), StandardCharsets.UTF_8);
|
||||
parse(reader);
|
||||
} catch (IOException fex) {
|
||||
LOG.warn("parsing not existing file {}, fallback on default configuration!", file.getAbsolutePath(), fex);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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);
|
||||
} finally {
|
||||
try {
|
||||
reader.close();
|
||||
} catch (IOException e) {
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Properties getProperties() {
|
||||
return m_properties;
|
||||
}
|
||||
}
|
||||
@@ -1,88 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2012-2018 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 java.io.File;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.FileReader;
|
||||
import java.io.IOException;
|
||||
import java.io.Reader;
|
||||
import java.nio.file.Files;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import static java.nio.charset.StandardCharsets.UTF_8;
|
||||
|
||||
public class FileResourceLoader implements IResourceLoader {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(FileResourceLoader.class);
|
||||
|
||||
private final File defaultFile;
|
||||
private final String parentPath;
|
||||
|
||||
public FileResourceLoader() {
|
||||
this((File) null);
|
||||
}
|
||||
|
||||
public FileResourceLoader(File defaultFile) {
|
||||
this(defaultFile, System.getProperty("moquette.path", null));
|
||||
}
|
||||
|
||||
public FileResourceLoader(String parentPath) {
|
||||
this(null, parentPath);
|
||||
}
|
||||
|
||||
public FileResourceLoader(File defaultFile, String parentPath) {
|
||||
this.defaultFile = defaultFile;
|
||||
this.parentPath = parentPath;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Reader loadDefaultResource() {
|
||||
if (defaultFile != null) {
|
||||
return loadResource(defaultFile);
|
||||
} else {
|
||||
throw new IllegalArgumentException("Default file not set!");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Reader loadResource(String relativePath) {
|
||||
return loadResource(new File(parentPath, relativePath));
|
||||
}
|
||||
|
||||
public Reader loadResource(File f) {
|
||||
LOG.info("Loading file. Path = {}.", f.getAbsolutePath());
|
||||
if (f.isDirectory()) {
|
||||
LOG.error("The given file is a directory. Path = {}.", f.getAbsolutePath());
|
||||
throw new ResourceIsDirectoryException("File \"" + f + "\" is a directory!");
|
||||
}
|
||||
try {
|
||||
return Files.newBufferedReader(f.toPath(), UTF_8);
|
||||
} catch (IOException e) {
|
||||
LOG.error("The file does not exist. Path = {}.", f.getAbsolutePath());
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "file";
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,79 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2012-2018 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.BrokerConstants;
|
||||
|
||||
/**
|
||||
* Base interface for all configuration implementations (filesystem, memory or classpath)
|
||||
*/
|
||||
public abstract class IConfig {
|
||||
|
||||
public static final String DEFAULT_CONFIG = "config/moquette.conf";
|
||||
|
||||
public abstract void setProperty(String name, String value);
|
||||
|
||||
/**
|
||||
* Same semantic of Properties
|
||||
*
|
||||
* @param name property name.
|
||||
* @return property value.
|
||||
* */
|
||||
public abstract String getProperty(String name);
|
||||
|
||||
/**
|
||||
* Same semantic of Properties
|
||||
*
|
||||
* @param name property name.
|
||||
* @param defaultValue default value to return in case the property doesn't exists.
|
||||
* @return property value.
|
||||
* */
|
||||
public abstract String getProperty(String name, String defaultValue);
|
||||
|
||||
void assignDefaults() {
|
||||
setProperty(BrokerConstants.PORT_PROPERTY_NAME, Integer.toString(BrokerConstants.PORT));
|
||||
setProperty(BrokerConstants.HOST_PROPERTY_NAME, BrokerConstants.HOST);
|
||||
// setProperty(BrokerConstants.WEB_SOCKET_PORT_PROPERTY_NAME,
|
||||
// Integer.toString(BrokerConstants.WEBSOCKET_PORT));
|
||||
setProperty(BrokerConstants.PASSWORD_FILE_PROPERTY_NAME, "");
|
||||
// setProperty(BrokerConstants.PERSISTENT_STORE_PROPERTY_NAME,
|
||||
// BrokerConstants.DEFAULT_PERSISTENT_PATH);
|
||||
setProperty(BrokerConstants.ALLOW_ANONYMOUS_PROPERTY_NAME, Boolean.TRUE.toString());
|
||||
setProperty(BrokerConstants.AUTHENTICATOR_CLASS_NAME, "");
|
||||
setProperty(BrokerConstants.AUTHORIZATOR_CLASS_NAME, "");
|
||||
setProperty(BrokerConstants.NETTY_MAX_BYTES_PROPERTY_NAME,
|
||||
String.valueOf(BrokerConstants.DEFAULT_NETTY_MAX_BYTES_IN_MESSAGE));
|
||||
}
|
||||
|
||||
public abstract IResourceLoader getResourceLoader();
|
||||
|
||||
public int intProp(String propertyName, int defaultValue) {
|
||||
String propertyValue = getProperty(propertyName);
|
||||
if (propertyValue == null) {
|
||||
return defaultValue;
|
||||
}
|
||||
return Integer.parseInt(propertyValue);
|
||||
}
|
||||
|
||||
public boolean boolProp(String propertyName, boolean defaultValue) {
|
||||
String propertyValue = getProperty(propertyName);
|
||||
if (propertyValue == null) {
|
||||
return defaultValue;
|
||||
}
|
||||
return Boolean.parseBoolean(propertyValue);
|
||||
}
|
||||
}
|
||||
@@ -1,38 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2012-2018 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 java.io.Reader;
|
||||
|
||||
public interface IResourceLoader {
|
||||
|
||||
Reader loadDefaultResource();
|
||||
|
||||
Reader loadResource(String relativePath);
|
||||
|
||||
String getName();
|
||||
|
||||
class ResourceIsDirectoryException extends RuntimeException {
|
||||
|
||||
private static final long serialVersionUID = -6969292229582764176L;
|
||||
|
||||
public ResourceIsDirectoryException(String message) {
|
||||
super(message);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,69 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2012-2018 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 java.util.Map;
|
||||
import java.util.Properties;
|
||||
|
||||
/**
|
||||
* Configuration backed by memory.
|
||||
*/
|
||||
public class MemoryConfig extends IConfig {
|
||||
|
||||
private final Properties m_properties = new Properties();
|
||||
|
||||
public MemoryConfig(Properties properties) {
|
||||
assignDefaults();
|
||||
for (Map.Entry<Object, Object> entrySet : properties.entrySet()) {
|
||||
m_properties.put(entrySet.getKey(), entrySet.getValue());
|
||||
}
|
||||
}
|
||||
|
||||
// private void createDefaults() {
|
||||
// m_properties.put(BrokerConstants.PORT_PROPERTY_NAME, Integer.toString(BrokerConstants.PORT));
|
||||
// m_properties.put(BrokerConstants.HOST_PROPERTY_NAME, BrokerConstants.HOST);
|
||||
// m_properties.put(BrokerConstants.WEB_SOCKET_PORT_PROPERTY_NAME,
|
||||
// Integer.toString(BrokerConstants.WEBSOCKET_PORT));
|
||||
// m_properties.put(BrokerConstants.PASSWORD_FILE_PROPERTY_NAME, "");
|
||||
// m_properties.put(BrokerConstants.PERSISTENT_STORE_PROPERTY_NAME,
|
||||
// BrokerConstants.DEFAULT_PERSISTENT_PATH);
|
||||
// m_properties.put(BrokerConstants.ALLOW_ANONYMOUS_PROPERTY_NAME, true);
|
||||
// m_properties.put(BrokerConstants.AUTHENTICATOR_CLASS_NAME, "");
|
||||
// m_properties.put(BrokerConstants.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);
|
||||
}
|
||||
|
||||
@Override
|
||||
public IResourceLoader getResourceLoader() {
|
||||
return new FileResourceLoader();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,103 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2012-2018 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.Reader;
|
||||
import java.text.ParseException;
|
||||
import java.util.Properties;
|
||||
|
||||
/**
|
||||
* Configuration that loads config stream from a {@link IResourceLoader} instance.
|
||||
*/
|
||||
public class ResourceLoaderConfig extends IConfig {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(ResourceLoaderConfig.class);
|
||||
|
||||
private final Properties m_properties;
|
||||
private final IResourceLoader resourceLoader;
|
||||
|
||||
public ResourceLoaderConfig(IResourceLoader resourceLoader) {
|
||||
this(resourceLoader, null);
|
||||
}
|
||||
|
||||
public ResourceLoaderConfig(IResourceLoader resourceLoader, String configName) {
|
||||
LOG.info("Loading configuration. ResourceLoader = {}, configName = {}.", resourceLoader.getName(), configName);
|
||||
this.resourceLoader = resourceLoader;
|
||||
|
||||
/*
|
||||
* If we use a conditional operator, the loadResource() and the loadDefaultResource()
|
||||
* methods will be always called. This makes the log traces confusing.
|
||||
*/
|
||||
|
||||
Reader configReader;
|
||||
if (configName != null) {
|
||||
configReader = resourceLoader.loadResource(configName);
|
||||
} else {
|
||||
configReader = resourceLoader.loadDefaultResource();
|
||||
}
|
||||
|
||||
if (configReader == null) {
|
||||
LOG.error(
|
||||
"The resource loader returned no configuration reader. ResourceLoader = {}, configName = {}.",
|
||||
resourceLoader.getName(),
|
||||
configName);
|
||||
throw new IllegalArgumentException("Can't locate " + resourceLoader.getName() + " \"" + configName + "\"");
|
||||
}
|
||||
|
||||
LOG.info(
|
||||
"Parsing configuration properties. ResourceLoader = {}, configName = {}.",
|
||||
resourceLoader.getName(),
|
||||
configName);
|
||||
ConfigurationParser confParser = new ConfigurationParser();
|
||||
m_properties = confParser.getProperties();
|
||||
assignDefaults();
|
||||
try {
|
||||
confParser.parse(configReader);
|
||||
} catch (ParseException pex) {
|
||||
LOG.warn(
|
||||
"Unable to parse configuration properties. Using default configuration. "
|
||||
+ "ResourceLoader = {}, configName = {}, cause = {}, errorMessage = {}.",
|
||||
resourceLoader.getName(),
|
||||
configName,
|
||||
pex.getCause(),
|
||||
pex.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@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);
|
||||
}
|
||||
|
||||
@Override
|
||||
public IResourceLoader getResourceLoader() {
|
||||
return resourceLoader;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,172 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2012-2016 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.ChannelHandlerContext;
|
||||
import io.netty.util.concurrent.EventExecutor;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.util.concurrent.ScheduledFuture;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
* Auto-flush data on channel after a read timeout. It's inspired by IdleStateHandler but it's
|
||||
* specialized version, just flushing data after no read is done on the channel after a period. It's
|
||||
* used to avoid aggressively flushing from the ProtocolProcessor.
|
||||
*/
|
||||
public class AutoFlushHandler extends ChannelDuplexHandler {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(AutoFlushHandler.class);
|
||||
private static final long MIN_TIMEOUT_NANOS = TimeUnit.MILLISECONDS.toNanos(1);
|
||||
|
||||
private final long writerIdleTimeNanos;
|
||||
volatile ScheduledFuture<?> writerIdleTimeout;
|
||||
volatile long lastWriteTime;
|
||||
// private boolean firstWriterIdleEvent = true;
|
||||
|
||||
private volatile int state; // 0 - none, 1 - initialized, 2 - destroyed
|
||||
|
||||
public AutoFlushHandler(long writerIdleTime, TimeUnit unit) {
|
||||
if (unit == null) {
|
||||
throw new NullPointerException("unit");
|
||||
}
|
||||
writerIdleTimeNanos = Math.max(unit.toNanos(writerIdleTime), MIN_TIMEOUT_NANOS);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handlerAdded(ChannelHandlerContext ctx) throws Exception {
|
||||
if (ctx.channel().isActive() && ctx.channel().isRegistered()) {
|
||||
// channelActive() event has been fired already, which means this.channelActive() will
|
||||
// not be invoked. We have to initialize here instead.
|
||||
initialize(ctx);
|
||||
} else {
|
||||
// channelActive() event has not been fired yet. this.channelActive() will be invoked
|
||||
// and initialization will occur there.
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handlerRemoved(ChannelHandlerContext ctx) {
|
||||
destroy();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void channelRegistered(ChannelHandlerContext ctx) throws Exception {
|
||||
// Initialize early if channel is active already.
|
||||
// if (ctx.channel().isActive()) {
|
||||
// initialize(ctx);
|
||||
// }
|
||||
super.channelRegistered(ctx);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void channelActive(ChannelHandlerContext ctx) throws Exception {
|
||||
// This method will be invoked only if this handler was added
|
||||
// before channelActive() event is fired. If a user adds this handler
|
||||
// after the channelActive() event, initialize() will be called by beforeAdd().
|
||||
// initialize(ctx);
|
||||
super.channelActive(ctx);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void channelInactive(ChannelHandlerContext ctx) throws Exception {
|
||||
destroy();
|
||||
super.channelInactive(ctx);
|
||||
}
|
||||
|
||||
private void initialize(ChannelHandlerContext ctx) {
|
||||
// Avoid the case where destroy() is called before scheduling timeouts.
|
||||
// See: https://github.com/netty/netty/issues/143
|
||||
if (LOG.isDebugEnabled()) {
|
||||
LOG.debug("Initializing autoflush handler on channel {} Cid: {}", ctx.channel(),
|
||||
NettyUtils.clientID(ctx.channel()));
|
||||
}
|
||||
switch (state) {
|
||||
case 1:
|
||||
case 2:
|
||||
return;
|
||||
}
|
||||
|
||||
state = 1;
|
||||
|
||||
EventExecutor loop = ctx.executor();
|
||||
|
||||
lastWriteTime = System.nanoTime();
|
||||
writerIdleTimeout = loop.schedule(new WriterIdleTimeoutTask(ctx), writerIdleTimeNanos, TimeUnit.NANOSECONDS);
|
||||
}
|
||||
|
||||
private void destroy() {
|
||||
state = 2;
|
||||
|
||||
if (writerIdleTimeout != null) {
|
||||
writerIdleTimeout.cancel(false);
|
||||
writerIdleTimeout = null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Is called when the write timeout expire.
|
||||
*
|
||||
* @param ctx the channel context.
|
||||
*/
|
||||
private void channelIdle(ChannelHandlerContext ctx) {
|
||||
// ctx.fireUserEventTriggered(evt);
|
||||
if (LOG.isTraceEnabled()) {
|
||||
LOG.trace("Flushing idle Netty channel {} Cid: {}", ctx.channel(), NettyUtils.clientID(ctx.channel()));
|
||||
}
|
||||
ctx.channel().flush();
|
||||
}
|
||||
|
||||
private final class WriterIdleTimeoutTask implements Runnable {
|
||||
|
||||
private final ChannelHandlerContext ctx;
|
||||
|
||||
WriterIdleTimeoutTask(ChannelHandlerContext ctx) {
|
||||
this.ctx = ctx;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
if (!ctx.channel().isOpen()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// long lastWriteTime = IdleStateHandler.this.lastWriteTime;
|
||||
// long lastWriteTime = lastWriteTime;
|
||||
long nextDelay = writerIdleTimeNanos - (System.nanoTime() - lastWriteTime);
|
||||
if (nextDelay <= 0) {
|
||||
// Writer is idle - set a new timeout and notify the callback.
|
||||
writerIdleTimeout = ctx.executor().schedule(this, writerIdleTimeNanos, TimeUnit.NANOSECONDS);
|
||||
try {
|
||||
/*
|
||||
* IdleStateEvent event; if (firstWriterIdleEvent) { firstWriterIdleEvent =
|
||||
* false; event = IdleStateEvent.FIRST_WRITER_IDLE_STATE_EVENT; } else { event =
|
||||
* IdleStateEvent.WRITER_IDLE_STATE_EVENT; }
|
||||
*/
|
||||
channelIdle(ctx/* , event */);
|
||||
} catch (Throwable t) {
|
||||
ctx.fireExceptionCaught(t);
|
||||
}
|
||||
} else {
|
||||
// Write occurred before the timeout - set a new timeout with shorter delay.
|
||||
writerIdleTimeout = ctx.executor().schedule(this, nextDelay, TimeUnit.NANOSECONDS);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,41 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2012-2018 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 com.bugsnag.Bugsnag;
|
||||
import io.moquette.server.config.IConfig;
|
||||
import io.netty.channel.ChannelHandler.Sharable;
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
import io.netty.channel.ChannelInboundHandlerAdapter;
|
||||
|
||||
import static io.moquette.BrokerConstants.BUGSNAG_TOKEN_PROPERTY_NAME;
|
||||
|
||||
@Sharable
|
||||
public class BugSnagErrorsHandler extends ChannelInboundHandlerAdapter {
|
||||
|
||||
private Bugsnag bugsnag;
|
||||
|
||||
public void init(IConfig props) {
|
||||
final String token = props.getProperty(BUGSNAG_TOKEN_PROPERTY_NAME);
|
||||
this.bugsnag = new Bugsnag(token);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
|
||||
bugsnag.notify(cause);
|
||||
ctx.fireExceptionCaught(cause);
|
||||
}
|
||||
}
|
||||
@@ -1,51 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2012-2018 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 org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
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;
|
||||
|
||||
import static io.netty.channel.ChannelFutureListener.CLOSE_ON_FAILURE;
|
||||
|
||||
@Sharable
|
||||
public class MoquetteIdleTimeoutHandler extends ChannelDuplexHandler {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(MoquetteIdleTimeoutHandler.class);
|
||||
|
||||
@Override
|
||||
public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
|
||||
if (evt instanceof IdleStateEvent) {
|
||||
IdleState e = ((IdleStateEvent) evt).state();
|
||||
if (e == IdleState.READER_IDLE) {
|
||||
LOG.info("Firing channel inactive event. MqttClientId = {}.", NettyUtils.clientID(ctx.channel()));
|
||||
// fire a close that then fire channelInactive to trigger publish of Will
|
||||
ctx.close().addListener(CLOSE_ON_FAILURE);
|
||||
}
|
||||
} else {
|
||||
if (LOG.isTraceEnabled()) {
|
||||
LOG.trace("Firing Netty event CId = {}, eventClass = {}", NettyUtils.clientID(ctx.channel()),
|
||||
evt.getClass().getName());
|
||||
}
|
||||
super.userEventTriggered(ctx, evt);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,74 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2012-2018 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.Channel;
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
import io.netty.util.Attribute;
|
||||
import io.netty.util.AttributeKey;
|
||||
|
||||
/**
|
||||
* Some Netty's channels utilities.
|
||||
*/
|
||||
public final class NettyUtils {
|
||||
|
||||
public static final String ATTR_USERNAME = "username";
|
||||
public static final String ATTR_SESSION_STOLEN = "sessionStolen";
|
||||
public static final String ATTR_CHANNEL_STATUS = "channelStatus";
|
||||
|
||||
private static final AttributeKey<Object> ATTR_KEY_KEEPALIVE = AttributeKey.valueOf(Constants.KEEP_ALIVE);
|
||||
private static final AttributeKey<Object> ATTR_KEY_CLEANSESSION = AttributeKey.valueOf(Constants.CLEAN_SESSION);
|
||||
private static final AttributeKey<Object> ATTR_KEY_CLIENTID = AttributeKey.valueOf(Constants.ATTR_CLIENTID);
|
||||
private static final AttributeKey<Object> ATTR_KEY_USERNAME = AttributeKey.valueOf(ATTR_USERNAME);
|
||||
|
||||
public static Object getAttribute(ChannelHandlerContext ctx, AttributeKey<Object> key) {
|
||||
Attribute<Object> attr = ctx.channel().attr(key);
|
||||
return attr.get();
|
||||
}
|
||||
|
||||
public static void keepAlive(Channel channel, int keepAlive) {
|
||||
channel.attr(NettyUtils.ATTR_KEY_KEEPALIVE).set(keepAlive);
|
||||
}
|
||||
|
||||
public static void cleanSession(Channel channel, boolean cleanSession) {
|
||||
channel.attr(NettyUtils.ATTR_KEY_CLEANSESSION).set(cleanSession);
|
||||
}
|
||||
|
||||
public static boolean cleanSession(Channel channel) {
|
||||
return (Boolean) channel.attr(NettyUtils.ATTR_KEY_CLEANSESSION).get();
|
||||
}
|
||||
|
||||
public static void clientID(Channel channel, String clientID) {
|
||||
channel.attr(NettyUtils.ATTR_KEY_CLIENTID).set(clientID);
|
||||
}
|
||||
|
||||
public static String clientID(Channel channel) {
|
||||
return (String) channel.attr(NettyUtils.ATTR_KEY_CLIENTID).get();
|
||||
}
|
||||
|
||||
public static void userName(Channel channel, String username) {
|
||||
channel.attr(NettyUtils.ATTR_KEY_USERNAME).set(username);
|
||||
}
|
||||
|
||||
public static String userName(Channel channel) {
|
||||
return (String) channel.attr(NettyUtils.ATTR_KEY_USERNAME).get();
|
||||
}
|
||||
|
||||
private NettyUtils() {
|
||||
}
|
||||
}
|
||||
@@ -1,39 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2012-2018 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;
|
||||
private long m_wroteBytes;
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -1,43 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2012-2018 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);
|
||||
}
|
||||
}
|
||||
@@ -1,72 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2012-2018 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.Channel;
|
||||
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;
|
||||
|
||||
import static io.netty.channel.ChannelFutureListener.CLOSE_ON_FAILURE;
|
||||
|
||||
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.channel().attr(ATTR_KEY_METRICS);
|
||||
attr.set(new BytesMetrics());
|
||||
|
||||
super.channelActive(ctx);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
|
||||
BytesMetrics metrics = ctx.channel().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.channel().attr(ATTR_KEY_METRICS).get();
|
||||
metrics.incrementWrote(((ByteBuf) msg).writableBytes());
|
||||
ctx.write(msg, promise).addListener(CLOSE_ON_FAILURE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close(ChannelHandlerContext ctx, ChannelPromise promise) throws Exception {
|
||||
BytesMetrics metrics = ctx.channel().attr(ATTR_KEY_METRICS).get();
|
||||
m_collector.sumReadBytes(metrics.readBytes());
|
||||
m_collector.sumWroteBytes(metrics.wroteBytes());
|
||||
super.close(ctx, promise);
|
||||
}
|
||||
|
||||
public static BytesMetrics getBytesMetrics(Channel channel) {
|
||||
return channel.attr(ATTR_KEY_METRICS).get();
|
||||
}
|
||||
}
|
||||
@@ -1,95 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2012-2018 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 com.codahale.metrics.Counter;
|
||||
import com.codahale.metrics.Meter;
|
||||
import com.codahale.metrics.MetricRegistry;
|
||||
import com.librato.metrics.reporter.Librato;
|
||||
import io.moquette.server.config.IConfig;
|
||||
import io.moquette.server.netty.NettyUtils;
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
import io.netty.channel.ChannelInboundHandlerAdapter;
|
||||
import io.netty.handler.codec.mqtt.MqttMessage;
|
||||
import io.netty.handler.codec.mqtt.MqttMessageType;
|
||||
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import static io.moquette.BrokerConstants.*;
|
||||
import static io.netty.channel.ChannelHandler.Sharable;
|
||||
|
||||
/**
|
||||
* Pipeline handler use to track some MQTT metrics.
|
||||
*/
|
||||
@Sharable
|
||||
public final class DropWizardMetricsHandler extends ChannelInboundHandlerAdapter {
|
||||
private MetricRegistry metrics;
|
||||
private Meter publishesMetrics;
|
||||
private Meter subscribeMetrics;
|
||||
private Counter connectedClientsMetrics;
|
||||
|
||||
public void init(IConfig props) {
|
||||
this.metrics = new MetricRegistry();
|
||||
this.publishesMetrics = metrics.meter("publish.requests");
|
||||
this.subscribeMetrics = metrics.meter("subscribe.requests");
|
||||
this.connectedClientsMetrics = metrics.counter("connect.num_clients");
|
||||
// ConsoleReporter reporter = ConsoleReporter.forRegistry(metrics)
|
||||
// .convertRatesTo(TimeUnit.SECONDS)
|
||||
// .convertDurationsTo(TimeUnit.MILLISECONDS)
|
||||
// .build();
|
||||
// reporter.start(1, TimeUnit.MINUTES);
|
||||
final String email = props.getProperty(METRICS_LIBRATO_EMAIL_PROPERTY_NAME);
|
||||
final String token = props.getProperty(METRICS_LIBRATO_TOKEN_PROPERTY_NAME);
|
||||
final String source = props.getProperty(METRICS_LIBRATO_SOURCE_PROPERTY_NAME);
|
||||
|
||||
Librato.reporter(this.metrics, email, token)
|
||||
.setSource(source)
|
||||
.start(10, TimeUnit.SECONDS);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void channelRead(ChannelHandlerContext ctx, Object message) {
|
||||
MqttMessage msg = (MqttMessage) message;
|
||||
MqttMessageType messageType = msg.fixedHeader().messageType();
|
||||
switch (messageType) {
|
||||
case PUBLISH:
|
||||
this.publishesMetrics.mark();
|
||||
break;
|
||||
case SUBSCRIBE:
|
||||
this.subscribeMetrics.mark();
|
||||
break;
|
||||
case CONNECT:
|
||||
this.connectedClientsMetrics.inc();
|
||||
break;
|
||||
case DISCONNECT:
|
||||
this.connectedClientsMetrics.dec();
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
ctx.fireChannelRead(message);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void channelInactive(ChannelHandlerContext ctx) throws Exception {
|
||||
String clientID = NettyUtils.clientID(ctx.channel());
|
||||
if (clientID != null && !clientID.isEmpty()) {
|
||||
this.connectedClientsMetrics.dec();
|
||||
}
|
||||
ctx.fireChannelInactive();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,120 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2012-2018 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.moquette.server.netty.NettyUtils;
|
||||
import io.netty.channel.ChannelDuplexHandler;
|
||||
import io.netty.channel.ChannelHandler.Sharable;
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
import io.netty.channel.ChannelPromise;
|
||||
import io.netty.handler.codec.mqtt.*;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
|
||||
import static io.moquette.spi.impl.Utils.messageId;
|
||||
import static io.netty.channel.ChannelFutureListener.CLOSE_ON_FAILURE;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author andrea
|
||||
*/
|
||||
@Sharable
|
||||
public class MQTTMessageLogger extends ChannelDuplexHandler {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger("messageLogger");
|
||||
|
||||
@Override
|
||||
public void channelRead(ChannelHandlerContext ctx, Object message) throws Exception {
|
||||
logMQTTMessageRead(ctx, message);
|
||||
ctx.fireChannelRead(message);
|
||||
}
|
||||
|
||||
private void logMQTTMessageRead(ChannelHandlerContext ctx, Object message) throws Exception {
|
||||
logMQTTMessage(ctx, message, "C->B");
|
||||
}
|
||||
|
||||
private void logMQTTMessageWrite(ChannelHandlerContext ctx, Object message) throws Exception {
|
||||
logMQTTMessage(ctx, message, "C<-B");
|
||||
}
|
||||
|
||||
private void logMQTTMessage(ChannelHandlerContext ctx, Object message, String direction) throws Exception {
|
||||
if (!(message instanceof MqttMessage)) {
|
||||
return;
|
||||
}
|
||||
MqttMessage msg = (MqttMessage) message;
|
||||
if (msg.fixedHeader() == null) {
|
||||
throw new IOException("Unknown packet");
|
||||
}
|
||||
String clientID = NettyUtils.clientID(ctx.channel());
|
||||
MqttMessageType messageType = msg.fixedHeader().messageType();
|
||||
switch (messageType) {
|
||||
case CONNACK:
|
||||
case PINGREQ:
|
||||
case PINGRESP:
|
||||
LOG.debug("{} {} <{}>", direction, messageType, clientID);
|
||||
break;
|
||||
case CONNECT:
|
||||
case DISCONNECT:
|
||||
LOG.info("{} {} <{}>", direction, messageType, clientID);
|
||||
break;
|
||||
case SUBSCRIBE:
|
||||
MqttSubscribeMessage subscribe = (MqttSubscribeMessage) msg;
|
||||
LOG.info("{} SUBSCRIBE <{}> to topics {}", direction, clientID,
|
||||
subscribe.payload().topicSubscriptions());
|
||||
break;
|
||||
case UNSUBSCRIBE:
|
||||
MqttUnsubscribeMessage unsubscribe = (MqttUnsubscribeMessage) msg;
|
||||
LOG.info("{} UNSUBSCRIBE <{}> to topics <{}>", direction, clientID, unsubscribe.payload().topics());
|
||||
break;
|
||||
case PUBLISH:
|
||||
MqttPublishMessage publish = (MqttPublishMessage) msg;
|
||||
LOG.debug("{} PUBLISH <{}> to topics <{}>", direction, clientID, publish.variableHeader().topicName());
|
||||
break;
|
||||
case PUBREC:
|
||||
case PUBCOMP:
|
||||
case PUBREL:
|
||||
case PUBACK:
|
||||
case UNSUBACK:
|
||||
LOG.info("{} {} <{}> packetID <{}>", direction, messageType, clientID, messageId(msg));
|
||||
break;
|
||||
case SUBACK:
|
||||
MqttSubAckMessage suback = (MqttSubAckMessage) msg;
|
||||
final List<Integer> grantedQoSLevels = suback.payload().grantedQoSLevels();
|
||||
LOG.info("{} SUBACK <{}> packetID <{}>, grantedQoses {}", direction, clientID, messageId(msg),
|
||||
grantedQoSLevels);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void channelInactive(ChannelHandlerContext ctx) throws Exception {
|
||||
String clientID = NettyUtils.clientID(ctx.channel());
|
||||
if (clientID != null && !clientID.isEmpty()) {
|
||||
LOG.info("Channel closed <{}>", clientID);
|
||||
}
|
||||
ctx.fireChannelInactive();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception {
|
||||
logMQTTMessageWrite(ctx, msg);
|
||||
ctx.write(msg, promise).addListener(CLOSE_ON_FAILURE);
|
||||
}
|
||||
}
|
||||
@@ -1,39 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2012-2018 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;
|
||||
private long m_messageWrote;
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -1,43 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2012-2018 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);
|
||||
}
|
||||
}
|
||||
@@ -1,71 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2012-2018 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.Channel;
|
||||
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;
|
||||
|
||||
import static io.netty.channel.ChannelFutureListener.CLOSE_ON_FAILURE;
|
||||
|
||||
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.channel().attr(ATTR_KEY_METRICS);
|
||||
attr.set(new MessageMetrics());
|
||||
|
||||
super.channelActive(ctx);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
|
||||
MessageMetrics metrics = ctx.channel().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.channel().attr(ATTR_KEY_METRICS).get();
|
||||
metrics.incrementWrote(1);
|
||||
ctx.write(msg, promise).addListener(CLOSE_ON_FAILURE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close(ChannelHandlerContext ctx, ChannelPromise promise) throws Exception {
|
||||
MessageMetrics metrics = ctx.channel().attr(ATTR_KEY_METRICS).get();
|
||||
m_collector.sumReadMessages(metrics.messagesRead());
|
||||
m_collector.sumWroteMessages(metrics.messagesWrote());
|
||||
super.close(ctx, promise);
|
||||
}
|
||||
|
||||
public static MessageMetrics getMessageMetrics(Channel channel) {
|
||||
return channel.attr(ATTR_KEY_METRICS).get();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user