Moved packages from old io.moquette.server to new io.moquette.broker

This commit is contained in:
andsel
2018-11-11 18:06:13 +01:00
parent 58fd90d33d
commit 1ba9eb3613
127 changed files with 274 additions and 1414 deletions
@@ -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();
}
}