commit
9994faf0e3
|
@ -7,8 +7,6 @@ deps
|
||||||
erl_crash.dump
|
erl_crash.dump
|
||||||
ebin
|
ebin
|
||||||
!ebin/.placeholder
|
!ebin/.placeholder
|
||||||
rel/emqttd
|
|
||||||
rel/emqttd*
|
|
||||||
.concrete/DEV_MODE
|
.concrete/DEV_MODE
|
||||||
.rebar
|
.rebar
|
||||||
test/ebin/*.beam
|
test/ebin/*.beam
|
||||||
|
@ -26,3 +24,6 @@ eunit.coverdata
|
||||||
test/ct.cover.spec
|
test/ct.cover.spec
|
||||||
logs
|
logs
|
||||||
ct.coverdata
|
ct.coverdata
|
||||||
|
.idea/
|
||||||
|
emqttd.iml
|
||||||
|
_rel/
|
||||||
|
|
|
@ -1,43 +0,0 @@
|
||||||
[submodule "plugins/emqttd_dashboard"]
|
|
||||||
path = plugins/emqttd_dashboard
|
|
||||||
url = https://github.com/emqtt/emqttd_dashboard.git
|
|
||||||
branch = stable
|
|
||||||
[submodule "plugins/emqttd_plugin_template"]
|
|
||||||
path = plugins/emqttd_plugin_template
|
|
||||||
url = https://github.com/emqtt/emqttd_plugin_template.git
|
|
||||||
branch = stable
|
|
||||||
[submodule "plugins/emqttd_plugin_pgsql"]
|
|
||||||
path = plugins/emqttd_plugin_pgsql
|
|
||||||
url = https://github.com/emqtt/emqttd_plugin_pgsql.git
|
|
||||||
branch = stable
|
|
||||||
[submodule "plugins/emqttd_plugin_mysql"]
|
|
||||||
path = plugins/emqttd_plugin_mysql
|
|
||||||
url = https://github.com/emqtt/emqttd_plugin_mysql.git
|
|
||||||
branch = stable
|
|
||||||
[submodule "plugins/emqttd_sockjs"]
|
|
||||||
path = plugins/emqttd_sockjs
|
|
||||||
url = https://github.com/emqtt/emqttd_sockjs.git
|
|
||||||
branch = stable
|
|
||||||
[submodule "plugins/emqttd_stomp"]
|
|
||||||
path = plugins/emqttd_stomp
|
|
||||||
url = https://github.com/emqtt/emqttd_stomp.git
|
|
||||||
branch = stable
|
|
||||||
[submodule "plugins/emqttd_recon"]
|
|
||||||
path = plugins/emqttd_recon
|
|
||||||
url = https://github.com/emqtt/emqttd_recon.git
|
|
||||||
branch = stable
|
|
||||||
[submodule "plugins/emqttd_plugin_redis"]
|
|
||||||
path = plugins/emqttd_plugin_redis
|
|
||||||
url = https://github.com/emqtt/emqttd_plugin_redis.git
|
|
||||||
branch = stable
|
|
||||||
[submodule "plugins/emqttd_reloader"]
|
|
||||||
path = plugins/emqttd_reloader
|
|
||||||
url = https://github.com/emqtt/emqttd_reloader.git
|
|
||||||
branch = stable
|
|
||||||
[submodule "plugins/emqttd_plugin_mongo"]
|
|
||||||
path = plugins/emqttd_plugin_mongo
|
|
||||||
url = https://github.com/emqtt/emqttd_plugin_mongo.git
|
|
||||||
[submodule "plugins/emqttd_auth_http"]
|
|
||||||
path = plugins/emqttd_auth_http
|
|
||||||
url = https://github.com/emqtt/emqttd_auth_http.git
|
|
||||||
branch = stable
|
|
77
Makefile
77
Makefile
|
@ -1,66 +1,31 @@
|
||||||
.PHONY: rel deps test plugins
|
PROJECT = emqttd
|
||||||
|
PROJECT_DESCRIPTION = Erlang MQTT Broker
|
||||||
|
PROJECT_VERSION = 2.0
|
||||||
|
|
||||||
APP = emqttd
|
DEPS = gproc lager gen_logger gen_conf esockd mochiweb
|
||||||
BASE_DIR = $(shell pwd)
|
|
||||||
REBAR = $(BASE_DIR)/rebar
|
|
||||||
DIST = $(BASE_DIR)/rel/$(APP)
|
|
||||||
|
|
||||||
all: submods compile
|
dep_gproc = git https://github.com/uwiger/gproc.git
|
||||||
|
dep_lager = git https://github.com/basho/lager.git
|
||||||
|
dep_gen_conf = git https://github.com/emqtt/gen_conf.git
|
||||||
|
dep_gen_logger = git https://github.com/emqtt/gen_logger.git
|
||||||
|
dep_esockd = git https://github.com/emqtt/esockd.git udp
|
||||||
|
dep_mochiweb = git https://github.com/emqtt/mochiweb.git
|
||||||
|
|
||||||
submods:
|
ERLC_OPTS += +'{parse_transform, lager_transform}'
|
||||||
@git submodule update --init
|
|
||||||
|
|
||||||
compile: deps
|
TEST_ERLC_OPTS += +debug_info
|
||||||
@$(REBAR) compile
|
TEST_ERLC_OPTS += +'{parse_transform, lager_transform}'
|
||||||
|
|
||||||
deps:
|
EUNIT_OPTS = verbose
|
||||||
@$(REBAR) get-deps
|
# EUNIT_ERL_OPTS =
|
||||||
|
|
||||||
update-deps:
|
CT_SUITES = emqttd emqttd_access emqttd_lib emqttd_mod emqttd_net \
|
||||||
@$(REBAR) update-deps
|
emqttd_mqueue emqttd_protocol emqttd_topic emqttd_trie
|
||||||
|
CT_OPTS = -cover test/ct.cover.spec -erl_args -name emqttd_ct@127.0.0.1
|
||||||
|
|
||||||
xref:
|
COVER = true
|
||||||
@$(REBAR) xref skip_deps=true
|
|
||||||
|
|
||||||
clean:
|
include erlang.mk
|
||||||
@$(REBAR) clean
|
|
||||||
|
|
||||||
test:
|
|
||||||
ERL_FLAGS="-config rel/files/emqttd.test.config" $(REBAR) -v skip_deps=true ct
|
|
||||||
#$(REBAR) skip_deps=true eunit
|
|
||||||
|
|
||||||
edoc:
|
|
||||||
@$(REBAR) doc
|
|
||||||
|
|
||||||
rel: compile
|
|
||||||
@cd rel && $(REBAR) generate -f
|
|
||||||
|
|
||||||
plugins:
|
|
||||||
@for plugin in ./plugins/* ; do \
|
|
||||||
if [ -d $${plugin} ]; then \
|
|
||||||
mkdir -p $(DIST)/$${plugin}/ ; \
|
|
||||||
cp -R $${plugin}/ebin $(DIST)/$${plugin}/ ; \
|
|
||||||
[ -d "$${plugin}/priv" ] && cp -R $${plugin}/priv $(DIST)/$${plugin}/ ; \
|
|
||||||
[ -d "$${plugin}/etc" ] && cp -R $${plugin}/etc $(DIST)/$${plugin}/ ; \
|
|
||||||
echo "$${plugin} copied" ; \
|
|
||||||
fi \
|
|
||||||
done
|
|
||||||
|
|
||||||
dist: rel plugins
|
|
||||||
|
|
||||||
PLT = $(BASE_DIR)/.emqttd_dialyzer.plt
|
|
||||||
APPS = erts kernel stdlib sasl crypto ssl os_mon syntax_tools \
|
|
||||||
public_key mnesia inets compiler
|
|
||||||
|
|
||||||
check_plt: compile
|
|
||||||
dialyzer --check_plt --plt $(PLT) --apps $(APPS) \
|
|
||||||
deps/*/ebin ./ebin plugins/*/ebin
|
|
||||||
|
|
||||||
build_plt: compile
|
|
||||||
dialyzer --build_plt --output_plt $(PLT) --apps $(APPS) \
|
|
||||||
deps/*/ebin ./ebin plugins/*/ebin
|
|
||||||
|
|
||||||
dialyzer: compile
|
|
||||||
dialyzer -Wno_return --plt $(PLT) deps/*/ebin ./ebin plugins/*/ebin
|
|
||||||
|
|
||||||
|
app:: rebar.config
|
||||||
|
|
||||||
|
|
|
@ -114,11 +114,11 @@ unzip emqttd-ubuntu64-0.16.0-beta-20160216.zip && cd emqttd
|
||||||
Installing from source:
|
Installing from source:
|
||||||
|
|
||||||
```
|
```
|
||||||
git clone https://github.com/emqtt/emqttd.git
|
git clone https://github.com/emqtt/emqttd-relx.git
|
||||||
|
|
||||||
cd emqttd && make && make dist
|
cd emqttd-relx && make
|
||||||
|
|
||||||
cd rel/emqttd && ./bin/emqttd console
|
cd _rel/emqttd && ./bin/emqttd console
|
||||||
```
|
```
|
||||||
|
|
||||||
## Documents
|
## Documents
|
||||||
|
|
Binary file not shown.
After Width: | Height: | Size: 22 KiB |
|
@ -5,6 +5,124 @@
|
||||||
Changes
|
Changes
|
||||||
=======
|
=======
|
||||||
|
|
||||||
|
.. _release_2.0:
|
||||||
|
|
||||||
|
-------------------------------------
|
||||||
|
Version 2.0-beta1 (West of West Lake)
|
||||||
|
-------------------------------------
|
||||||
|
|
||||||
|
*Release Date: 2016-08-30*
|
||||||
|
|
||||||
|
*Release Name: West of West Lake*
|
||||||
|
|
||||||
|
EMQ - Shortened Project Name
|
||||||
|
----------------------------
|
||||||
|
|
||||||
|
Adopt a shortened project name: EMQ(Erlang/Enterprise/Elastic MQTT Broker),E means Erlang/OTP, Enterprise and Elastic.
|
||||||
|
|
||||||
|
Improve the Release Management
|
||||||
|
------------------------------
|
||||||
|
|
||||||
|
In order to iterate the project fast, we will adopt a new release management strategy since 2.0. There will be two or three 'Preview Release' named beta1, beta2 or beta3, and then one or two 'Release Candidate' named rc1, rc2 before a Major version is production ready.
|
||||||
|
|
||||||
|
Seperate Rel from Application
|
||||||
|
-----------------------------
|
||||||
|
|
||||||
|
We split the emqttd 1.x project into two projects since 2.0-beta1 release to resolve the plugins' dependency issue.
|
||||||
|
|
||||||
|
A new project named `emqttd-relx`_ is created and responsible for buiding the emqttd application and the plugins::
|
||||||
|
|
||||||
|
git clone https://github.com/emqtt/emqttd-relx.git
|
||||||
|
|
||||||
|
cd emqttd-relx && make
|
||||||
|
|
||||||
|
cd _rel/emqttd && ./bin/emqttd console
|
||||||
|
|
||||||
|
erlang.mk and relx
|
||||||
|
------------------
|
||||||
|
|
||||||
|
The rebar which is used in 1.x release is replaced by `erlang.mk`_ and `relx`_ tools since 2.0-beta1 release.
|
||||||
|
|
||||||
|
You can check the 'Makefile' and 'relx.config' in the release project of the borker: `emqttd-relx`_ .
|
||||||
|
|
||||||
|
Improve Git Branch Management
|
||||||
|
-----------------------------
|
||||||
|
|
||||||
|
+------------+-------------------------------------------+
|
||||||
|
| stable | 1.x Stable Branch |
|
||||||
|
+------------+-------------------------------------------+
|
||||||
|
| master | 2.x Master Branch |
|
||||||
|
+------------+-------------------------------------------+
|
||||||
|
| emq10 | 1.x Developement Branch |
|
||||||
|
+------------+-------------------------------------------+
|
||||||
|
| emq20 | 2.x Development Branch |
|
||||||
|
+------------+-------------------------------------------+
|
||||||
|
| emq30 | 3.x Development Branch |
|
||||||
|
+------------+-------------------------------------------+
|
||||||
|
| issue#{id} | BugFix Branch |
|
||||||
|
+------------+-------------------------------------------+
|
||||||
|
|
||||||
|
New Config Syntax
|
||||||
|
-----------------
|
||||||
|
|
||||||
|
Since 2.0-beta1 release the configuration file of the broker and plugins adopt a new syntax like rebar.config and relx.config:
|
||||||
|
|
||||||
|
etc/emqttd.conf for example::
|
||||||
|
|
||||||
|
%% Max ClientId Length Allowed.
|
||||||
|
{mqtt_max_clientid_len, 512}.
|
||||||
|
|
||||||
|
%% Max Packet Size Allowed, 64K by default.
|
||||||
|
{mqtt_max_packet_size, 65536}.
|
||||||
|
|
||||||
|
%% Client Idle Timeout.
|
||||||
|
{mqtt_client_idle_timeout, 30}. % Second
|
||||||
|
|
||||||
|
MQTT-SN Protocol Plugin
|
||||||
|
-----------------------
|
||||||
|
|
||||||
|
The MQTT-SN Protocol Plugin `emqttd_sn`_ has been ready in 2.0-beta1 release. The default UDP port of MQTT-SN is 1884.
|
||||||
|
|
||||||
|
Load the plugin::
|
||||||
|
|
||||||
|
./bin/emqttd_ctl plugins load emqttd_sn
|
||||||
|
|
||||||
|
Improve the PubSub Design
|
||||||
|
-------------------------
|
||||||
|
|
||||||
|
.. image:: _static/images/publish.png
|
||||||
|
|
||||||
|
Improve the Plugin Management
|
||||||
|
-----------------------------
|
||||||
|
|
||||||
|
The plugin of EMQ 2.0 broker is a normal erlang application which depends on and extends 'emqttd'. You can create a standalone plugin application project, and add it to `emqttd-relx`_ Makefile as a DEP.
|
||||||
|
|
||||||
|
All the plugins' config files will be copied to emqttd/etc/plugins/ folder when making emqttd brinary packages in `emqttd-relx`_ project::
|
||||||
|
|
||||||
|
▾ emqttd/
|
||||||
|
▾ etc/
|
||||||
|
▸ modules/
|
||||||
|
▾ plugins/
|
||||||
|
emqtt_coap.conf
|
||||||
|
emqttd.conf
|
||||||
|
emqttd_auth_http.conf
|
||||||
|
emqttd_auth_mongo.conf
|
||||||
|
emqttd_auth_mysql.conf
|
||||||
|
emqttd_auth_pgsql.conf
|
||||||
|
emqttd_auth_redis.conf
|
||||||
|
emqttd_coap.conf
|
||||||
|
emqttd_dashboard.conf
|
||||||
|
emqttd_plugin_template.conf
|
||||||
|
emqttd_recon.conf
|
||||||
|
emqttd_reloader.conf
|
||||||
|
emqttd_sn.conf
|
||||||
|
emqttd_stomp.conf
|
||||||
|
|
||||||
|
EMQ 2.0 Documentation
|
||||||
|
---------------------
|
||||||
|
|
||||||
|
http://emqtt.io/docs/v2/index.html
|
||||||
|
|
||||||
.. _release_1.1.3:
|
.. _release_1.1.3:
|
||||||
|
|
||||||
-------------
|
-------------
|
||||||
|
|
|
@ -245,7 +245,7 @@ The Firewall
|
||||||
|
|
||||||
If there is a firewall between clustered nodes, the cluster requires to open 4369 port used by epmd daemon, and a port segment for nodes' communication.
|
If there is a firewall between clustered nodes, the cluster requires to open 4369 port used by epmd daemon, and a port segment for nodes' communication.
|
||||||
|
|
||||||
Configure the port segment in etc/emqttd.config, for example:
|
Configure the port segment in releases/2.0/sys.config, for example:
|
||||||
|
|
||||||
.. code-block:: erlang
|
.. code-block:: erlang
|
||||||
|
|
||||||
|
|
|
@ -1,3 +1,8 @@
|
||||||
==============
|
|
||||||
|
.. _coap:
|
||||||
|
|
||||||
|
=============
|
||||||
CoAP Protocol
|
CoAP Protocol
|
||||||
==============
|
=============
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -20,7 +20,7 @@ Show running status of the broker::
|
||||||
$ ./bin/emqttd_ctl status
|
$ ./bin/emqttd_ctl status
|
||||||
|
|
||||||
Node 'emqttd@127.0.0.1' is started
|
Node 'emqttd@127.0.0.1' is started
|
||||||
emqttd 1.1 is running
|
emqttd 2.0 is running
|
||||||
|
|
||||||
.. _command_broker::
|
.. _command_broker::
|
||||||
|
|
||||||
|
@ -382,10 +382,6 @@ Query the subscription table of the broker:
|
||||||
+--------------------------------------------+--------------------------------------+
|
+--------------------------------------------+--------------------------------------+
|
||||||
| subscriptions show <ClientId> | Show a subscription |
|
| subscriptions show <ClientId> | Show a subscription |
|
||||||
+--------------------------------------------+--------------------------------------+
|
+--------------------------------------------+--------------------------------------+
|
||||||
| subscriptions add <ClientId> <Topic> <Qos> | Add a static subscription manually |
|
|
||||||
+--------------------------------------------+--------------------------------------+
|
|
||||||
| subscriptions del <ClientId> <Topic> | Remove a static subscription manually|
|
|
||||||
+--------------------------------------------+--------------------------------------+
|
|
||||||
|
|
||||||
subscriptions list
|
subscriptions list
|
||||||
------------------
|
------------------
|
||||||
|
@ -415,22 +411,6 @@ Show the subscriptions of a MQTT client::
|
||||||
|
|
||||||
clientid: [{<<"x">>,1},{<<"topic2">>,1},{<<"topic3">>,1}]
|
clientid: [{<<"x">>,1},{<<"topic2">>,1},{<<"topic3">>,1}]
|
||||||
|
|
||||||
subscriptions add <ClientId> <Topic> <QoS>
|
|
||||||
------------------------------------------
|
|
||||||
|
|
||||||
Add a static subscription manually::
|
|
||||||
|
|
||||||
$ ./bin/emqttd_ctl subscriptions add clientid new_topic 1
|
|
||||||
ok
|
|
||||||
|
|
||||||
subscriptions del <ClientId> <Topic>
|
|
||||||
------------------------------------
|
|
||||||
|
|
||||||
Remove a static subscription manually::
|
|
||||||
|
|
||||||
$ ./bin/emqttd_ctl subscriptions del clientid new_topic
|
|
||||||
ok
|
|
||||||
|
|
||||||
.. _command_plugins::
|
.. _command_plugins::
|
||||||
|
|
||||||
-------
|
-------
|
||||||
|
|
|
@ -48,7 +48,7 @@ source_suffix = '.rst'
|
||||||
master_doc = 'index'
|
master_doc = 'index'
|
||||||
|
|
||||||
# General information about the project.
|
# General information about the project.
|
||||||
project = u'Erlang MQTT Broker'
|
project = u'EMQ 2.0 - Erlang MQTT Broker'
|
||||||
copyright = u'2016, Feng Lee'
|
copyright = u'2016, Feng Lee'
|
||||||
|
|
||||||
# The version info for the project you're documenting, acts as replacement for
|
# The version info for the project you're documenting, acts as replacement for
|
||||||
|
@ -56,9 +56,9 @@ copyright = u'2016, Feng Lee'
|
||||||
# built documents.
|
# built documents.
|
||||||
#
|
#
|
||||||
# The short X.Y version.
|
# The short X.Y version.
|
||||||
version = '1.0'
|
version = '2.0'
|
||||||
# The full version, including alpha/beta/rc tags.
|
# The full version, including alpha/beta/rc tags.
|
||||||
release = '1.0'
|
release = '2.0'
|
||||||
|
|
||||||
# The language for content autogenerated by Sphinx. Refer to documentation
|
# The language for content autogenerated by Sphinx. Refer to documentation
|
||||||
# for a list of supported languages.
|
# for a list of supported languages.
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -15,6 +15,17 @@ The emqttd broker 1.0 is more like a network Switch or Router, not a traditional
|
||||||
|
|
||||||
.. image:: _static/images/concept.png
|
.. image:: _static/images/concept.png
|
||||||
|
|
||||||
|
The EMQ 2.0 seperated the Message Flow Plane and Monitor/Control Plane, the Architecture is something like::
|
||||||
|
|
||||||
|
Control Plane
|
||||||
|
--------------------
|
||||||
|
| |
|
||||||
|
FrontEnd -> | Flow Plane | -> BackEnd
|
||||||
|
| |
|
||||||
|
Session Router
|
||||||
|
---------------------
|
||||||
|
Monitor Plane
|
||||||
|
|
||||||
Design Philosophy
|
Design Philosophy
|
||||||
-----------------
|
-----------------
|
||||||
|
|
||||||
|
@ -478,3 +489,32 @@ http://github.com/emqtt/emqttd_plugin_template
|
||||||
.. _eSockd: https://github.com/emqtt/esockd
|
.. _eSockd: https://github.com/emqtt/esockd
|
||||||
.. _Chain-of-responsibility_pattern: https://en.wikipedia.org/wiki/Chain-of-responsibility_pattern
|
.. _Chain-of-responsibility_pattern: https://en.wikipedia.org/wiki/Chain-of-responsibility_pattern
|
||||||
.. _emqttd_plugin_template: https://github.com/emqtt/emqttd_plugin_template/blob/master/src/emqttd_plugin_template.erl
|
.. _emqttd_plugin_template: https://github.com/emqtt/emqttd_plugin_template/blob/master/src/emqttd_plugin_template.erl
|
||||||
|
|
||||||
|
-----------------
|
||||||
|
Mnesia/ETS Tables
|
||||||
|
-----------------
|
||||||
|
|
||||||
|
+--------------------+--------+----------------------------------------+
|
||||||
|
| Table | Type | Description |
|
||||||
|
+====================+========+========================================+
|
||||||
|
| mqtt_trie | mnesia | Trie Table |
|
||||||
|
+--------------------+--------+----------------------------------------+
|
||||||
|
| mqtt_trie_node | mnesia | Trie Node Table |
|
||||||
|
+--------------------+--------+----------------------------------------+
|
||||||
|
| mqtt_route | mnesia | Global Route Table |
|
||||||
|
+--------------------+--------+----------------------------------------+
|
||||||
|
| mqtt_local_route | mnesia | Local Route Table |
|
||||||
|
+--------------------+--------+----------------------------------------+
|
||||||
|
| mqtt_pubsub | ets | PubSub Tab |
|
||||||
|
+--------------------+--------+----------------------------------------+
|
||||||
|
| mqtt_subscriber | ets | Subscriber Tab |
|
||||||
|
+--------------------+--------+----------------------------------------+
|
||||||
|
| mqtt_subscription | ets | Subscription Tab |
|
||||||
|
+--------------------+--------+----------------------------------------+
|
||||||
|
| mqtt_session | mnesia | Global Session Table |
|
||||||
|
+--------------------+--------+----------------------------------------+
|
||||||
|
| mqtt_local_session | ets | Local Session Table |
|
||||||
|
+--------------------+--------+----------------------------------------+
|
||||||
|
| mqtt_client | ets | Client Table |
|
||||||
|
+--------------------+--------+----------------------------------------+
|
||||||
|
|
||||||
|
|
|
@ -35,6 +35,8 @@ Features
|
||||||
* MQTT Over WebSocket(SSL)
|
* MQTT Over WebSocket(SSL)
|
||||||
* HTTP Publish API
|
* HTTP Publish API
|
||||||
* STOMP protocol
|
* STOMP protocol
|
||||||
|
* MQTT-SN Protocol
|
||||||
|
* CoAP Protocol
|
||||||
* STOMP over SockJS
|
* STOMP over SockJS
|
||||||
* $SYS/# Topics
|
* $SYS/# Topics
|
||||||
* ClientID Authentication
|
* ClientID Authentication
|
||||||
|
@ -63,7 +65,7 @@ Installing on Mac, for example:
|
||||||
|
|
||||||
.. code-block:: bash
|
.. code-block:: bash
|
||||||
|
|
||||||
unzip emqttd-macosx-1.1-beta-20160601.zip && cd emqttd
|
unzip emqttd-macosx-2.0-beta1-20160830.zip && cd emqttd
|
||||||
|
|
||||||
# Start emqttd
|
# Start emqttd
|
||||||
./bin/emqttd start
|
./bin/emqttd start
|
||||||
|
@ -119,8 +121,6 @@ Modules
|
||||||
+-------------------------+--------------------------------------------+
|
+-------------------------+--------------------------------------------+
|
||||||
| emqttd_auth_username | Authentication with Username and Password |
|
| emqttd_auth_username | Authentication with Username and Password |
|
||||||
+-------------------------+--------------------------------------------+
|
+-------------------------+--------------------------------------------+
|
||||||
| emqttd_auth_ldap | Authentication with LDAP |
|
|
||||||
+-------------------------+--------------------------------------------+
|
|
||||||
| emqttd_mod_presence | Publish presence message to $SYS topics |
|
| emqttd_mod_presence | Publish presence message to $SYS topics |
|
||||||
| | when client connected or disconnected |
|
| | when client connected or disconnected |
|
||||||
+-------------------------+--------------------------------------------+
|
+-------------------------+--------------------------------------------+
|
||||||
|
@ -136,22 +136,16 @@ Enable 'emqttd_auth_username' module:
|
||||||
|
|
||||||
.. code-block:: erlang
|
.. code-block:: erlang
|
||||||
|
|
||||||
{access, [
|
|
||||||
%% Authetication. Anonymous Default
|
|
||||||
{auth, [
|
|
||||||
%% Authentication with username, password
|
%% Authentication with username, password
|
||||||
{username, []},
|
{auth, username, [{passwd, "etc/modules/passwd.conf"}]}.
|
||||||
|
|
||||||
...
|
|
||||||
|
|
||||||
Enable 'emqttd_mod_presence' module:
|
Enable 'emqttd_mod_presence' module:
|
||||||
|
|
||||||
.. code-block:: erlang
|
.. code-block:: erlang
|
||||||
|
|
||||||
{modules, [
|
%% Client presence management module. Publish presence messages when
|
||||||
%% Client presence management module.
|
%% client connected or disconnected.
|
||||||
%% Publish messages when client connected or disconnected
|
{module, presence, [{qos, 0}]}.
|
||||||
{presence, [{qos, 0}]}
|
|
||||||
|
|
||||||
Plugins
|
Plugins
|
||||||
-------
|
-------
|
||||||
|
@ -163,16 +157,20 @@ A plugin is an Erlang application to extend the emqttd broker.
|
||||||
+----------------------------+-----------------------------------+
|
+----------------------------+-----------------------------------+
|
||||||
| `emqttd_dashboard`_ | Web Dashboard |
|
| `emqttd_dashboard`_ | Web Dashboard |
|
||||||
+----------------------------+-----------------------------------+
|
+----------------------------+-----------------------------------+
|
||||||
|
| `emqttd_auth_ldap`_ | LDAP Auth Plugin |
|
||||||
|
+----------------------------+-----------------------------------+
|
||||||
| `emqttd_auth_http`_ | Authentication/ACL with HTTP API |
|
| `emqttd_auth_http`_ | Authentication/ACL with HTTP API |
|
||||||
+----------------------------+-----------------------------------+
|
+----------------------------+-----------------------------------+
|
||||||
| `emqttd_plugin_mysql`_ | Authentication with MySQL |
|
| `emqttd_auth_mysql` _ | Authentication with MySQL |
|
||||||
+----------------------------+-----------------------------------+
|
+----------------------------+-----------------------------------+
|
||||||
| `emqttd_plugin_pgsql`_ | Authentication with PostgreSQL |
|
| `emqttd_auth_pgsql`_ | Authentication with PostgreSQL |
|
||||||
+----------------------------+-----------------------------------+
|
+----------------------------+-----------------------------------+
|
||||||
| `emqttd_plugin_redis`_ | Authentication with Redis |
|
| `emqttd_auth_redis`_ | Authentication with Redis |
|
||||||
+----------------------------+-----------------------------------+
|
+----------------------------+-----------------------------------+
|
||||||
| `emqttd_plugin_mongo`_ | Authentication with MongoDB |
|
| `emqttd_plugin_mongo`_ | Authentication with MongoDB |
|
||||||
+----------------------------+-----------------------------------+
|
+----------------------------+-----------------------------------+
|
||||||
|
| `emqttd_sn`_ | MQTT-SN Protocol Plugin |
|
||||||
|
+----------------------------+-----------------------------------+
|
||||||
| `emqttd_stomp`_ | STOMP Protocol Plugin |
|
| `emqttd_stomp`_ | STOMP Protocol Plugin |
|
||||||
+----------------------------+-----------------------------------+
|
+----------------------------+-----------------------------------+
|
||||||
| `emqttd_sockjs`_ | SockJS(Stomp) Plugin |
|
| `emqttd_sockjs`_ | SockJS(Stomp) Plugin |
|
||||||
|
@ -182,9 +180,9 @@ A plugin is an Erlang application to extend the emqttd broker.
|
||||||
|
|
||||||
A plugin could be enabled by 'bin/emqttd_ctl plugins load' command.
|
A plugin could be enabled by 'bin/emqttd_ctl plugins load' command.
|
||||||
|
|
||||||
For example, enable 'emqttd_plugin_pgsql' plugin::
|
For example, enable 'emqttd_auth_pgsql' plugin::
|
||||||
|
|
||||||
./bin/emqttd_ctl plugins load emqttd_plugin_pgsql
|
./bin/emqttd_ctl plugins load emqttd_auth_pgsql
|
||||||
|
|
||||||
-----------------------
|
-----------------------
|
||||||
One Million Connections
|
One Million Connections
|
||||||
|
@ -238,11 +236,11 @@ emqttd/etc/vm.args::
|
||||||
emqttd broker
|
emqttd broker
|
||||||
-------------
|
-------------
|
||||||
|
|
||||||
emqttd/etc/emqttd.config:
|
emqttd/etc/emqttd.conf:
|
||||||
|
|
||||||
.. code-block:: erlang
|
.. code-block:: erlang
|
||||||
|
|
||||||
{mqtt, 1883, [
|
{listener, mqtt, 1883, [
|
||||||
%% Size of acceptor pool
|
%% Size of acceptor pool
|
||||||
{acceptors, 64},
|
{acceptors, 64},
|
||||||
|
|
||||||
|
@ -258,6 +256,7 @@ emqttd/etc/emqttd.config:
|
||||||
%% {rate_limit, "100,10"} %% 100K burst, 10K rate
|
%% {rate_limit, "100,10"} %% 100K burst, 10K rate
|
||||||
]},
|
]},
|
||||||
...
|
...
|
||||||
|
]}.
|
||||||
|
|
||||||
Test Client
|
Test Client
|
||||||
-----------
|
-----------
|
||||||
|
@ -291,11 +290,15 @@ GitHub: https://github.com/emqtt
|
||||||
|
|
||||||
.. _emqttd_plugin_template: https://github.com/emqtt/emqttd_plugin_template
|
.. _emqttd_plugin_template: https://github.com/emqtt/emqttd_plugin_template
|
||||||
.. _emqttd_dashboard: https://github.com/emqtt/emqttd_dashboard
|
.. _emqttd_dashboard: https://github.com/emqtt/emqttd_dashboard
|
||||||
|
.. _emqttd_auth_ldap: https://github.com/emqtt/emqttd_auth_ldap
|
||||||
.. _emqttd_auth_http: https://github.com/emqtt/emqttd_auth_http
|
.. _emqttd_auth_http: https://github.com/emqtt/emqttd_auth_http
|
||||||
.. _emqttd_plugin_mysql: https://github.com/emqtt/emqttd_plugin_mysql
|
.. _emqttd_auth_mysql: https://github.com/emqtt/emqttd_plugin_mysql
|
||||||
.. _emqttd_plugin_pgsql: https://github.com/emqtt/emqttd_plugin_pgsql
|
.. _emqttd_auth_pgsql: https://github.com/emqtt/emqttd_plugin_pgsql
|
||||||
.. _emqttd_plugin_redis: https://github.com/emqtt/emqttd_plugin_redis
|
.. _emqttd_auth_redis: https://github.com/emqtt/emqttd_plugin_redis
|
||||||
.. _emqttd_plugin_mongo: https://github.com/emqtt/emqttd_plugin_mongo
|
.. _emqttd_auth_mongo: https://github.com/emqtt/emqttd_plugin_mongo
|
||||||
|
.. _emqttd_reloader: https://github.com/emqtt/emqttd_reloader
|
||||||
.. _emqttd_stomp: https://github.com/emqtt/emqttd_stomp
|
.. _emqttd_stomp: https://github.com/emqtt/emqttd_stomp
|
||||||
.. _emqttd_sockjs: https://github.com/emqtt/emqttd_sockjs
|
.. _emqttd_sockjs: https://github.com/emqtt/emqttd_sockjs
|
||||||
.. _emqttd_recon: https://github.com/emqtt/emqttd_recon
|
.. _emqttd_recon: https://github.com/emqtt/emqttd_recon
|
||||||
|
.. _emqttd_sn: https://github.com/emqtt/emqttd_sn
|
||||||
|
|
||||||
|
|
|
@ -35,8 +35,6 @@ Sensors, Mobiles, Web Browsers and Application Servers could be connected by emq
|
||||||
| Author: | Feng Lee <feng@emqtt.io> |
|
| Author: | Feng Lee <feng@emqtt.io> |
|
||||||
+---------------+-----------------------------------------+
|
+---------------+-----------------------------------------+
|
||||||
|
|
||||||
.. NOTE:: MQTT-SN,CoAP Protocols are planned to 1.x release.
|
|
||||||
|
|
||||||
Contents:
|
Contents:
|
||||||
|
|
||||||
.. toctree::
|
.. toctree::
|
||||||
|
|
|
@ -35,7 +35,7 @@ Download binary packages from: http://emqtt.io/downloads
|
||||||
|
|
||||||
The package name consists of platform, version and release time.
|
The package name consists of platform, version and release time.
|
||||||
|
|
||||||
For example: emqttd-centos64-1.1-beta-20160601.zip
|
For example: emqttd-centos64-2.0-beta1-20160830.zip
|
||||||
|
|
||||||
.. _install_on_linux:
|
.. _install_on_linux:
|
||||||
|
|
||||||
|
@ -47,7 +47,7 @@ Download CentOS Package from: http://emqtt.io/downloads/latest/centos, and then
|
||||||
|
|
||||||
.. code-block:: bash
|
.. code-block:: bash
|
||||||
|
|
||||||
unzip emqttd-centos64-1.1-beta-20160601.zip
|
unzip emqttd-centos64-2.0-beta-20160830.zip
|
||||||
|
|
||||||
Start the broker in console mode:
|
Start the broker in console mode:
|
||||||
|
|
||||||
|
@ -80,7 +80,7 @@ If the broker is started successfully, console will print:
|
||||||
mqtt listen on 0.0.0.0:1883 with 16 acceptors.
|
mqtt listen on 0.0.0.0:1883 with 16 acceptors.
|
||||||
mqtts listen on 0.0.0.0:8883 with 4 acceptors.
|
mqtts listen on 0.0.0.0:8883 with 4 acceptors.
|
||||||
http listen on 0.0.0.0:8083 with 4 acceptors.
|
http listen on 0.0.0.0:8083 with 4 acceptors.
|
||||||
Erlang MQTT Broker 1.1 is running now
|
Erlang MQTT Broker 2.0 is running now
|
||||||
Eshell V6.4 (abort with ^G)
|
Eshell V6.4 (abort with ^G)
|
||||||
(emqttd@127.0.0.1)1>
|
(emqttd@127.0.0.1)1>
|
||||||
|
|
||||||
|
@ -100,7 +100,7 @@ Check the running status of the broker:
|
||||||
|
|
||||||
$ ./bin/emqttd_ctl status
|
$ ./bin/emqttd_ctl status
|
||||||
Node 'emqttd@127.0.0.1' is started
|
Node 'emqttd@127.0.0.1' is started
|
||||||
emqttd 1.1 is running
|
emqttd 2.0 is running
|
||||||
|
|
||||||
Or check the status by URL::
|
Or check the status by URL::
|
||||||
|
|
||||||
|
@ -130,7 +130,7 @@ We could install the broker on Mac OS X to develop and debug MQTT applications.
|
||||||
|
|
||||||
Download Mac Package from: http://emqtt.io/downloads/latest/macosx
|
Download Mac Package from: http://emqtt.io/downloads/latest/macosx
|
||||||
|
|
||||||
Configure 'lager' log level in 'etc/emqttd.config', all MQTT messages recevied/sent will be printed on console:
|
Configure 'lager' log level in 'releases/2.0/sys.config', all MQTT messages recevied/sent will be printed on console:
|
||||||
|
|
||||||
.. code-block:: erlang
|
.. code-block:: erlang
|
||||||
|
|
||||||
|
@ -198,15 +198,15 @@ When all dependencies are ready, clone the emqttd project from github.com and bu
|
||||||
|
|
||||||
.. code-block:: bash
|
.. code-block:: bash
|
||||||
|
|
||||||
git clone https://github.com/emqtt/emqttd.git
|
git clone https://github.com/emqtt/emqttd-relx.git
|
||||||
|
|
||||||
cd emqttd
|
cd emqttd-relx && make
|
||||||
|
|
||||||
make && make dist
|
cd _rel/emqttd && ./bin/emqttd console
|
||||||
|
|
||||||
The binary package output in folder::
|
The binary package output in folder::
|
||||||
|
|
||||||
rel/emqttd
|
_rel/emqttd
|
||||||
|
|
||||||
.. _tcp_ports:
|
.. _tcp_ports:
|
||||||
|
|
||||||
|
@ -228,19 +228,20 @@ The TCP ports used can be configured in etc/emqttd.config:
|
||||||
|
|
||||||
.. code-block:: erlang
|
.. code-block:: erlang
|
||||||
|
|
||||||
{listeners, [
|
%% Plain MQTT
|
||||||
{mqtt, 1883, [
|
{listener, mqtt, 1883, [
|
||||||
...
|
...
|
||||||
]},
|
]}.
|
||||||
|
|
||||||
{mqtts, 8883, [
|
%% MQTT/SSL
|
||||||
|
{listener, mqtts, 8883, [
|
||||||
...
|
...
|
||||||
]},
|
]}.
|
||||||
|
|
||||||
%% HTTP and WebSocket Listener
|
%% HTTP and WebSocket Listener
|
||||||
{http, 8083, [
|
{listener, http, 8083, [
|
||||||
...
|
...
|
||||||
]}
|
]}.
|
||||||
]},
|
|
||||||
|
|
||||||
The 18083 port is used by Web Dashboard of the broker. Default login: admin, Password: public
|
The 18083 port is used by Web Dashboard of the broker. Default login: admin, Password: public
|
||||||
|
|
||||||
|
@ -255,7 +256,7 @@ Two main configuration files of the emqttd broker:
|
||||||
+-------------------+-----------------------------------+
|
+-------------------+-----------------------------------+
|
||||||
| etc/vm.args | Erlang VM Arguments |
|
| etc/vm.args | Erlang VM Arguments |
|
||||||
+-------------------+-----------------------------------+
|
+-------------------+-----------------------------------+
|
||||||
| etc/emqttd.config | emqttd broker Config |
|
| etc/emqttd.conf | emqttd broker Config |
|
||||||
+-------------------+-----------------------------------+
|
+-------------------+-----------------------------------+
|
||||||
|
|
||||||
Two important parameters in etc/vm.args:
|
Two important parameters in etc/vm.args:
|
||||||
|
@ -277,17 +278,18 @@ The maximum number of allowed MQTT clients:
|
||||||
|
|
||||||
.. code-block:: erlang
|
.. code-block:: erlang
|
||||||
|
|
||||||
{listeners, [
|
%% Plain MQTT
|
||||||
{mqtt, 1883, [
|
{listener, mqtt, 1883, [
|
||||||
%% TCP Acceptor Pool
|
|
||||||
|
%% Size of acceptor pool
|
||||||
{acceptors, 16},
|
{acceptors, 16},
|
||||||
|
|
||||||
%% Maximum number of concurrent MQTT clients
|
%% Maximum number of concurrent clients
|
||||||
{max_clients, 8192},
|
{max_clients, 8192},
|
||||||
|
|
||||||
...
|
...
|
||||||
|
|
||||||
]},
|
]}.
|
||||||
|
|
||||||
.. _init_d_emqttd:
|
.. _init_d_emqttd:
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,10 @@
|
||||||
|
|
||||||
|
.. _mqtt_sn:
|
||||||
|
|
||||||
|
TODO:...
|
||||||
|
|
||||||
|
================
|
||||||
|
MQTT-SN Protocol
|
||||||
|
================
|
||||||
|
|
||||||
|
|
|
@ -7,24 +7,28 @@ Plugins
|
||||||
|
|
||||||
The emqttd broker could be extended by plugins. Users could develop plugins to customize authentication, ACL and functions of the broker, or integrate the broker with other systems.
|
The emqttd broker could be extended by plugins. Users could develop plugins to customize authentication, ACL and functions of the broker, or integrate the broker with other systems.
|
||||||
|
|
||||||
The plugins that emqtt project released:
|
The plugins that emqttd 2.0 released:
|
||||||
|
|
||||||
+---------------------------+---------------------------+
|
+---------------------------+---------------------------+
|
||||||
| Plugin | Description |
|
| Plugin | Description |
|
||||||
+===========================+===========================+
|
+===========================+===========================+
|
||||||
|
| `emqttd_dashboard`_ | Web Dashboard |
|
||||||
|
+---------------------------+---------------------------+
|
||||||
| `emqttd_plugin_template`_ | Template Plugin |
|
| `emqttd_plugin_template`_ | Template Plugin |
|
||||||
+---------------------------+---------------------------+
|
+---------------------------+---------------------------+
|
||||||
| `emqttd_dashboard`_ | Web Dashboard |
|
| `emqttd_auth_ldap`_ | LDAP Auth |
|
||||||
+---------------------------+---------------------------+
|
+---------------------------+---------------------------+
|
||||||
| `emqttd_auth_http`_ | HTTP Auth/ACL Plugin |
|
| `emqttd_auth_http`_ | HTTP Auth/ACL Plugin |
|
||||||
+---------------------------+---------------------------+
|
+---------------------------+---------------------------+
|
||||||
| `emqttd_plugin_mysql`_ | MySQL Auth/ACL Plugin |
|
| `emqttd_auth_mysql`_ | MySQL Auth/ACL Plugin |
|
||||||
+---------------------------+---------------------------+
|
+---------------------------+---------------------------+
|
||||||
| `emqttd_plugin_pgsql`_ | PostgreSQL Auth/ACL Plugin|
|
| `emqttd_auth_pgsql`_ | PostgreSQL Auth/ACL Plugin|
|
||||||
+---------------------------+---------------------------+
|
+---------------------------+---------------------------+
|
||||||
| `emqttd_plugin_redis`_ | Redis Auth/ACL Plugin |
|
| `emqttd_auth_redis`_ | Redis Auth/ACL Plugin |
|
||||||
+---------------------------+---------------------------+
|
+---------------------------+---------------------------+
|
||||||
| `emqttd_plugin_mongo`_ | MongoDB Auth/ACL Plugin |
|
| `emqttd_auth_mongo`_ | MongoDB Auth/ACL Plugin |
|
||||||
|
+---------------------------+---------------------------+
|
||||||
|
| `emqttd_sn`_ | MQTT-SN Protocol Plugin |
|
||||||
+---------------------------+---------------------------+
|
+---------------------------+---------------------------+
|
||||||
| `emqttd_stomp`_ | STOMP Protocol Plugin |
|
| `emqttd_stomp`_ | STOMP Protocol Plugin |
|
||||||
+---------------------------+---------------------------+
|
+---------------------------+---------------------------+
|
||||||
|
@ -39,17 +43,9 @@ The plugins that emqtt project released:
|
||||||
emqttd_plugin_template - Template Plugin
|
emqttd_plugin_template - Template Plugin
|
||||||
----------------------------------------
|
----------------------------------------
|
||||||
|
|
||||||
A plugin is just a normal Erlang application under the 'emqttd/plugins' folder. Each plugin has e configuration file: 'etc/plugin.config'.
|
A plugin is just a normal Erlang application which has its own configuration file: 'etc/<PluginName>.config'.
|
||||||
|
|
||||||
plugins/emqttd_plugin_template is a demo plugin. The folder structure:
|
emqttd_plugin_template is a demo plugin.
|
||||||
|
|
||||||
+------------------------+---------------------------+
|
|
||||||
| File | Description |
|
|
||||||
+========================+===========================+
|
|
||||||
| etc/plugin.config | Plugin config file |
|
|
||||||
+------------------------+---------------------------+
|
|
||||||
| ebin/ | Erlang program files |
|
|
||||||
+------------------------+---------------------------+
|
|
||||||
|
|
||||||
Load, unload Plugin
|
Load, unload Plugin
|
||||||
-------------------
|
-------------------
|
||||||
|
@ -78,22 +74,51 @@ The Web Dashboard for emqttd broker. The plugin will be loaded automatically whe
|
||||||
|
|
||||||
.. image:: _static/images/dashboard.png
|
.. image:: _static/images/dashboard.png
|
||||||
|
|
||||||
Configure Dashboard
|
Configure Dashboard Plugin
|
||||||
-------------------
|
--------------------------
|
||||||
|
|
||||||
emqttd_dashboard/etc/plugin.config:
|
etc/plugins/emqttd_dashboard.conf:
|
||||||
|
|
||||||
.. code-block:: erlang
|
.. code-block:: erlang
|
||||||
|
|
||||||
[
|
|
||||||
{emqttd_dashboard, [
|
|
||||||
{listener,
|
{listener,
|
||||||
{emqttd_dashboard, 18083, [
|
{dashboard, 18083, [
|
||||||
{acceptors, 4},
|
{acceptors, 4},
|
||||||
{max_clients, 512}]}
|
{max_clients, 512}
|
||||||
}
|
|
||||||
]}
|
]}
|
||||||
].
|
}.
|
||||||
|
|
||||||
|
----------------------------------
|
||||||
|
emqttd_auth_ldap: LDAP Auth Plugin
|
||||||
|
----------------------------------
|
||||||
|
|
||||||
|
LDAP Auth Plugin: https://github.com/emqtt/emqttd_auth_ldap
|
||||||
|
|
||||||
|
.. NOTE:: Supported in 2.0-beta1 release
|
||||||
|
|
||||||
|
Configure LDAP Plugin
|
||||||
|
---------------------
|
||||||
|
|
||||||
|
etc/plugins/emqttd_auth_ldap.conf:
|
||||||
|
|
||||||
|
.. code-block:: erlang
|
||||||
|
|
||||||
|
{ldap, [
|
||||||
|
{servers, ["localhost"]},
|
||||||
|
{port, 389},
|
||||||
|
{timeout, 30},
|
||||||
|
{user_dn, "uid=$u,ou=People,dc=example,dc=com"},
|
||||||
|
{ssl, fasle},
|
||||||
|
{sslopts, [
|
||||||
|
{certfile, "ssl.crt"},
|
||||||
|
{keyfile, "ssl.key"}
|
||||||
|
]}
|
||||||
|
]}.
|
||||||
|
|
||||||
|
Load LDAP Plugin
|
||||||
|
----------------
|
||||||
|
|
||||||
|
./bin/emqttd_ctl plugins load emqttd_auth_ldap
|
||||||
|
|
||||||
---------------------------------------
|
---------------------------------------
|
||||||
emqttd_auth_http - HTTP Auth/ACL Plugin
|
emqttd_auth_http - HTTP Auth/ACL Plugin
|
||||||
|
@ -103,14 +128,12 @@ MQTT Authentication/ACL with HTTP API: https://github.com/emqtt/emqttd_auth_http
|
||||||
|
|
||||||
.. NOTE:: Supported in 1.1 release
|
.. NOTE:: Supported in 1.1 release
|
||||||
|
|
||||||
Configure emqttd_auth_http/etc/plugin.config
|
Configure HTTP Auth/ACL Plugin
|
||||||
--------------------------------------------
|
------------------------------
|
||||||
|
|
||||||
.. code:: erlang
|
etc/plugins/emqttd_auth_http.conf:
|
||||||
|
|
||||||
[
|
.. code-block:: erlang
|
||||||
|
|
||||||
{emqttd_auth_http, [
|
|
||||||
|
|
||||||
%% Variables: %u = username, %c = clientid, %a = ipaddress, %t = topic
|
%% Variables: %u = username, %c = clientid, %a = ipaddress, %t = topic
|
||||||
|
|
||||||
|
@ -121,7 +144,7 @@ Configure emqttd_auth_http/etc/plugin.config
|
||||||
{username, "%u"},
|
{username, "%u"},
|
||||||
{clientid, "%c"}
|
{clientid, "%c"}
|
||||||
]}
|
]}
|
||||||
]},
|
]}.
|
||||||
|
|
||||||
{auth_req, [
|
{auth_req, [
|
||||||
{method, post},
|
{method, post},
|
||||||
|
@ -131,7 +154,7 @@ Configure emqttd_auth_http/etc/plugin.config
|
||||||
{username, "%u"},
|
{username, "%u"},
|
||||||
{password, "%P"}
|
{password, "%P"}
|
||||||
]}
|
]}
|
||||||
]},
|
]}.
|
||||||
|
|
||||||
%% 'access' parameter: sub = 1, pub = 2
|
%% 'access' parameter: sub = 1, pub = 2
|
||||||
|
|
||||||
|
@ -145,19 +168,17 @@ Configure emqttd_auth_http/etc/plugin.config
|
||||||
{ipaddr, "%a"},
|
{ipaddr, "%a"},
|
||||||
{topic, "%t"}
|
{topic, "%t"}
|
||||||
]}
|
]}
|
||||||
]}
|
]}.
|
||||||
]}
|
|
||||||
|
|
||||||
].
|
|
||||||
|
|
||||||
HTTP API
|
HTTP Auth/ACL API
|
||||||
--------
|
-----------------
|
||||||
|
|
||||||
Return 200 if ok
|
Return 200 if ok
|
||||||
|
|
||||||
Return 4xx if unauthorized
|
Return 4xx if unauthorized
|
||||||
|
|
||||||
Load emqttd_auth_http plugin
|
Load HTTP Auth/ACL Plugin
|
||||||
----------------------------
|
----------------------------
|
||||||
|
|
||||||
.. code:: bash
|
.. code:: bash
|
||||||
|
@ -211,21 +232,17 @@ MQTT ACL Table
|
||||||
(6,1,'127.0.0.1',NULL,NULL,2,'#'),
|
(6,1,'127.0.0.1',NULL,NULL,2,'#'),
|
||||||
(7,1,NULL,'dashboard',NULL,1,'$SYS/#');
|
(7,1,NULL,'dashboard',NULL,1,'$SYS/#');
|
||||||
|
|
||||||
Configure emqttd_plugin_mysql/etc/plugin.config
|
Configure MySQL Auth/ACL Plugin
|
||||||
-----------------------------------------------
|
-------------------------------
|
||||||
|
|
||||||
Configure MySQL host, username, password and database:
|
etc/plugins/emqttd_plugin_mysql.conf:
|
||||||
|
|
||||||
.. code-block:: erlang
|
.. code-block:: erlang
|
||||||
|
|
||||||
[
|
|
||||||
|
|
||||||
{emqttd_plugin_mysql, [
|
|
||||||
|
|
||||||
{mysql_pool, [
|
{mysql_pool, [
|
||||||
%% ecpool options
|
%% pool options
|
||||||
{pool_size, 8},
|
{pool_size, 8},
|
||||||
{auto_reconnect, 3},
|
{auto_reconnect, 1},
|
||||||
|
|
||||||
%% mysql options
|
%% mysql options
|
||||||
{host, "localhost"},
|
{host, "localhost"},
|
||||||
|
@ -233,42 +250,39 @@ Configure MySQL host, username, password and database:
|
||||||
{user, ""},
|
{user, ""},
|
||||||
{password, ""},
|
{password, ""},
|
||||||
{database, "mqtt"},
|
{database, "mqtt"},
|
||||||
{encoding, utf8}
|
{encoding, utf8},
|
||||||
]},
|
{keep_alive, true}
|
||||||
|
]}.
|
||||||
|
|
||||||
%% Variables: %u = username, %c = clientid, %a = ipaddress
|
%% Variables: %u = username, %c = clientid, %a = ipaddress
|
||||||
|
|
||||||
%% Superuser Query
|
%% Superuser Query
|
||||||
{superquery, "select is_superuser from mqtt_user where username = '%u' limit 1"},
|
{superquery, "select is_superuser from mqtt_user where username = '%u' limit 1"}.
|
||||||
|
|
||||||
%% Authentication Query: select password only
|
%% Authentication Query: select password only
|
||||||
{authquery, "select password from mqtt_user where username = '%u' limit 1"},
|
{authquery, "select password from mqtt_user where username = '%u' limit 1"}.
|
||||||
|
|
||||||
%% hash algorithm: plain, md5, sha, sha256, pbkdf2?
|
%% hash algorithm: plain, md5, sha, sha256, pbkdf2?
|
||||||
{password_hash, sha256},
|
{password_hash, sha256}.
|
||||||
|
|
||||||
%% select password with salt
|
%% select password with salt
|
||||||
%% {authquery, "select password, salt from mqtt_user where username = '%u'"},
|
%% {authquery, "select password, salt from mqtt_user where username = '%u'"}.
|
||||||
|
|
||||||
%% sha256 with salt prefix
|
%% sha256 with salt prefix
|
||||||
%% {password_hash, {salt, sha256}},
|
%% {password_hash, {salt, sha256}}.
|
||||||
|
|
||||||
%% sha256 with salt suffix
|
%% sha256 with salt suffix
|
||||||
%% {password_hash, {sha256, salt}},
|
%% {password_hash, {sha256, salt}}.
|
||||||
|
|
||||||
%% '%a' = ipaddress, '%u' = username, '%c' = clientid
|
%% '%a' = ipaddress, '%u' = username, '%c' = clientid
|
||||||
%% Comment this query, the acl will be disabled
|
%% Comment this query, the acl will be disabled
|
||||||
{aclquery, "select allow, ipaddr, username, clientid, access, topic from mqtt_acl where ipaddr = '%a' or username = '%u' or username = '$all' or clientid = '%c'"},
|
{aclquery, "select allow, ipaddr, username, clientid, access, topic from mqtt_acl where ipaddr = '%a' or username = '%u' or username = '$all' or clientid = '%c'"}.
|
||||||
|
|
||||||
%% If no ACL rules matched, return...
|
%% If no ACL rules matched, return...
|
||||||
{acl_nomatch, allow}
|
{acl_nomatch, allow}.
|
||||||
|
|
||||||
]}
|
Load MySQL Auth/ACL plugin
|
||||||
|
--------------------------
|
||||||
].
|
|
||||||
|
|
||||||
Load emqttd_plugin_mysql plugin
|
|
||||||
-------------------------------
|
|
||||||
|
|
||||||
.. code-block:: bash
|
.. code-block:: bash
|
||||||
|
|
||||||
|
@ -280,8 +294,8 @@ emqttd_plugin_pgsql - PostgreSQL Auth/ACL Plugin
|
||||||
|
|
||||||
MQTT Authentication, ACL with PostgreSQL Database.
|
MQTT Authentication, ACL with PostgreSQL Database.
|
||||||
|
|
||||||
MQTT User Table
|
Postgre MQTT User Table
|
||||||
---------------
|
-----------------------
|
||||||
|
|
||||||
.. code-block:: sql
|
.. code-block:: sql
|
||||||
|
|
||||||
|
@ -293,8 +307,8 @@ MQTT User Table
|
||||||
salt character varying(40)
|
salt character varying(40)
|
||||||
);
|
);
|
||||||
|
|
||||||
MQTT ACL Table
|
Postgre MQTT ACL Table
|
||||||
--------------
|
----------------------
|
||||||
|
|
||||||
.. code-block:: sql
|
.. code-block:: sql
|
||||||
|
|
||||||
|
@ -317,19 +331,17 @@ MQTT ACL Table
|
||||||
(6,1,'127.0.0.1',NULL,NULL,2,'#'),
|
(6,1,'127.0.0.1',NULL,NULL,2,'#'),
|
||||||
(7,1,NULL,'dashboard',NULL,1,'$SYS/#');
|
(7,1,NULL,'dashboard',NULL,1,'$SYS/#');
|
||||||
|
|
||||||
Configure emqttd_plugin_pgsql/etc/plugin.config
|
Configure Postgre Auth/ACL Plugin
|
||||||
-----------------------------------------------
|
-----------------------------------------------
|
||||||
|
|
||||||
|
Plugin Config: etc/plugins/emqttd_plugin_pgsql.conf.
|
||||||
|
|
||||||
Configure host, username, password and database of PostgreSQL:
|
Configure host, username, password and database of PostgreSQL:
|
||||||
|
|
||||||
.. code-block:: erlang
|
.. code-block:: erlang
|
||||||
|
|
||||||
[
|
|
||||||
|
|
||||||
{emqttd_plugin_pgsql, [
|
|
||||||
|
|
||||||
{pgsql_pool, [
|
{pgsql_pool, [
|
||||||
%% ecpool options
|
%% pool options
|
||||||
{pool_size, 8},
|
{pool_size, 8},
|
||||||
{auto_reconnect, 3},
|
{auto_reconnect, 3},
|
||||||
|
|
||||||
|
@ -341,43 +353,40 @@ Configure host, username, password and database of PostgreSQL:
|
||||||
{password, ""},
|
{password, ""},
|
||||||
{database, "mqtt"},
|
{database, "mqtt"},
|
||||||
{encoding, utf8}
|
{encoding, utf8}
|
||||||
]},
|
]}.
|
||||||
|
|
||||||
%% Variables: %u = username, %c = clientid, %a = ipaddress
|
%% Variables: %u = username, %c = clientid, %a = ipaddress
|
||||||
|
|
||||||
%% Superuser Query
|
%% Superuser Query
|
||||||
{superquery, "select is_superuser from mqtt_user where username = '%u' limit 1"},
|
{superquery, "select is_superuser from mqtt_user where username = '%u' limit 1"}.
|
||||||
|
|
||||||
%% Authentication Query: select password only
|
%% Authentication Query: select password only
|
||||||
{authquery, "select password from mqtt_user where username = '%u' limit 1"},
|
{authquery, "select password from mqtt_user where username = '%u' limit 1"}.
|
||||||
|
|
||||||
%% hash algorithm: plain, md5, sha, sha256, pbkdf2?
|
%% hash algorithm: plain, md5, sha, sha256, pbkdf2?
|
||||||
{password_hash, sha256},
|
{password_hash, sha256}.
|
||||||
|
|
||||||
%% select password with salt
|
%% select password with salt
|
||||||
%% {authquery, "select password, salt from mqtt_user where username = '%u'"},
|
%% {authquery, "select password, salt from mqtt_user where username = '%u'"}.
|
||||||
|
|
||||||
%% sha256 with salt prefix
|
%% sha256 with salt prefix
|
||||||
%% {password_hash, {salt, sha256}},
|
%% {password_hash, {salt, sha256}}.
|
||||||
|
|
||||||
%% sha256 with salt suffix
|
%% sha256 with salt suffix
|
||||||
%% {password_hash, {sha256, salt}},
|
%% {password_hash, {sha256, salt}}.
|
||||||
|
|
||||||
%% Comment this query, the acl will be disabled. Notice: don't edit this query!
|
%% Comment this query, the acl will be disabled. Notice: don't edit this query!
|
||||||
{aclquery, "select allow, ipaddr, username, clientid, access, topic from mqtt_acl
|
{aclquery, "select allow, ipaddr, username, clientid, access, topic from mqtt_acl where ipaddr = '%a' or username = '%u' or username = '$all' or clientid = '%c'"}.
|
||||||
where ipaddr = '%a' or username = '%u' or username = '$all' or clientid = '%c'"},
|
|
||||||
|
|
||||||
%% If no rules matched, return...
|
%% If no rules matched, return...
|
||||||
{acl_nomatch, allow}
|
{acl_nomatch, allow}.
|
||||||
]}
|
|
||||||
].
|
|
||||||
|
|
||||||
Load emqttd_plugin_pgsql Plugin
|
Load Postgre Auth/ACL Plugin
|
||||||
-------------------------------
|
-----------------------------
|
||||||
|
|
||||||
.. code-block:: bash
|
.. code-block:: bash
|
||||||
|
|
||||||
./bin/emqttd_ctl plugins load emqttd_plugin_pgsql
|
./bin/emqttd_ctl plugins load emqttd_auth_pgsql
|
||||||
|
|
||||||
-------------------------------------------
|
-------------------------------------------
|
||||||
emqttd_plugin_redis - Redis Auth/ACL Plugin
|
emqttd_plugin_redis - Redis Auth/ACL Plugin
|
||||||
|
@ -385,58 +394,56 @@ emqttd_plugin_redis - Redis Auth/ACL Plugin
|
||||||
|
|
||||||
MQTT Authentication, ACL with Redis: https://github.com/emqtt/emqttd_plugin_redis
|
MQTT Authentication, ACL with Redis: https://github.com/emqtt/emqttd_plugin_redis
|
||||||
|
|
||||||
Configure emqttd_plugin_redis/etc/plugin.config
|
Configure Redis Auth/ACL Plugin
|
||||||
-----------------------------------------------
|
-------------------------------
|
||||||
|
|
||||||
|
etc/plugins/emqttd_auth_redis.conf:
|
||||||
|
|
||||||
.. code-block:: erlang
|
.. code-block:: erlang
|
||||||
|
|
||||||
[
|
{redis_pool, [
|
||||||
{emqttd_plugin_redis, [
|
%% pool options
|
||||||
|
|
||||||
{eredis_pool, [
|
|
||||||
%% ecpool options
|
|
||||||
{pool_size, 8},
|
{pool_size, 8},
|
||||||
{auto_reconnect, 2},
|
{auto_reconnect, 2},
|
||||||
|
|
||||||
%% eredis options
|
%% redis options
|
||||||
{host, "127.0.0.1"},
|
{host, "127.0.0.1"},
|
||||||
{port, 6379},
|
{port, 6379},
|
||||||
{database, 0},
|
{database, 0},
|
||||||
{password, ""}
|
{password, ""}
|
||||||
]},
|
]}.
|
||||||
|
|
||||||
%% Variables: %u = username, %c = clientid
|
%% Variables: %u = username, %c = clientid
|
||||||
|
|
||||||
%% HMGET mqtt_user:%u is_superuser
|
%% HMGET mqtt_user:%u is_superuser
|
||||||
{supercmd, ["HGET", "mqtt_user:%u", "is_superuser"]},
|
{supercmd, ["HGET", "mqtt_user:%u", "is_superuser"]}.
|
||||||
|
|
||||||
%% HMGET mqtt_user:%u password
|
%% HMGET mqtt_user:%u password
|
||||||
{authcmd, ["HGET", "mqtt_user:%u", "password"]},
|
{authcmd, ["HGET", "mqtt_user:%u", "password"]}.
|
||||||
|
|
||||||
%% Password hash algorithm: plain, md5, sha, sha256, pbkdf2?
|
%% Password hash algorithm: plain, md5, sha, sha256, pbkdf2?
|
||||||
{password_hash, sha256},
|
{password_hash, sha256}.
|
||||||
|
|
||||||
%% SMEMBERS mqtt_acl:%u
|
%% SMEMBERS mqtt_acl:%u
|
||||||
{aclcmd, ["SMEMBERS", "mqtt_acl:%u"]},
|
{aclcmd, ["SMEMBERS", "mqtt_acl:%u"]}.
|
||||||
|
|
||||||
%% If no rules matched, return...
|
%% If no rules matched, return...
|
||||||
{acl_nomatch, deny},
|
{acl_nomatch, deny}.
|
||||||
|
|
||||||
%% Load Subscriptions form Redis when client connected.
|
%% Load Subscriptions form Redis when client connected.
|
||||||
{subcmd, ["HGETALL", "mqtt_subs:%u"]}
|
{subcmd, ["HGETALL", "mqtt_subs:%u"]}.
|
||||||
]}
|
|
||||||
].
|
|
||||||
|
|
||||||
User HASH
|
|
||||||
---------
|
Redis User HASH
|
||||||
|
---------------
|
||||||
|
|
||||||
Set a 'user' hash with 'password' field, for example::
|
Set a 'user' hash with 'password' field, for example::
|
||||||
|
|
||||||
HSET mqtt_user:<username> is_superuser 1
|
HSET mqtt_user:<username> is_superuser 1
|
||||||
HSET mqtt_user:<username> password "passwd"
|
HSET mqtt_user:<username> password "passwd"
|
||||||
|
|
||||||
ACL Rule SET
|
Redis ACL Rule SET
|
||||||
------------
|
------------------
|
||||||
|
|
||||||
The plugin uses a redis SET to store ACL rules::
|
The plugin uses a redis SET to store ACL rules::
|
||||||
|
|
||||||
|
@ -444,8 +451,8 @@ The plugin uses a redis SET to store ACL rules::
|
||||||
SADD mqtt_acl:<username> "subscribe topic2"
|
SADD mqtt_acl:<username> "subscribe topic2"
|
||||||
SADD mqtt_acl:<username> "pubsub topic3"
|
SADD mqtt_acl:<username> "pubsub topic3"
|
||||||
|
|
||||||
Subscription HASH
|
Redis Subscription HASH
|
||||||
-----------------
|
-----------------------
|
||||||
|
|
||||||
The plugin can store static subscriptions in a redis Hash::
|
The plugin can store static subscriptions in a redis Hash::
|
||||||
|
|
||||||
|
@ -453,12 +460,12 @@ The plugin can store static subscriptions in a redis Hash::
|
||||||
HSET mqtt_subs:<username> topic2 1
|
HSET mqtt_subs:<username> topic2 1
|
||||||
HSET mqtt_subs:<username> topic3 2
|
HSET mqtt_subs:<username> topic3 2
|
||||||
|
|
||||||
Load emqttd_plugin_redis Plugin
|
Load Redis Auth/ACL Plugin
|
||||||
-------------------------------
|
--------------------------
|
||||||
|
|
||||||
.. code-block:: bash
|
.. code-block:: bash
|
||||||
|
|
||||||
./bin/emqttd_ctl plugins load emqttd_plugin_redis
|
./bin/emqttd_ctl plugins load emqttd_auth_redis
|
||||||
|
|
||||||
---------------------------------------------
|
---------------------------------------------
|
||||||
emqttd_plugin_mongo - MongoDB Auth/ACL Plugin
|
emqttd_plugin_mongo - MongoDB Auth/ACL Plugin
|
||||||
|
@ -466,55 +473,51 @@ emqttd_plugin_mongo - MongoDB Auth/ACL Plugin
|
||||||
|
|
||||||
MQTT Authentication, ACL with MongoDB: https://github.com/emqtt/emqttd_plugin_mongo
|
MQTT Authentication, ACL with MongoDB: https://github.com/emqtt/emqttd_plugin_mongo
|
||||||
|
|
||||||
Configure emqttd_plugin_mongo/etc/plugin.config
|
Configure MongoDB Auth/ACL Plugin
|
||||||
-----------------------------------------------
|
---------------------------------
|
||||||
|
|
||||||
|
etc/plugins/emqttd_plugin_mongo.conf:
|
||||||
|
|
||||||
.. code-block:: erlang
|
.. code-block:: erlang
|
||||||
|
|
||||||
[
|
|
||||||
{emqttd_plugin_mongo, [
|
|
||||||
|
|
||||||
{mongo_pool, [
|
{mongo_pool, [
|
||||||
{pool_size, 8},
|
{pool_size, 8},
|
||||||
{auto_reconnect, 3},
|
{auto_reconnect, 3},
|
||||||
|
|
||||||
%% Mongodb Driver Opts
|
%% Mongodb Opts
|
||||||
{host, "localhost"},
|
{host, "localhost"},
|
||||||
{port, 27017},
|
{port, 27017},
|
||||||
%% {login, ""},
|
%% {login, ""},
|
||||||
%% {password, ""},
|
%% {password, ""},
|
||||||
{database, "mqtt"}
|
{database, "mqtt"}
|
||||||
]},
|
]}.
|
||||||
|
|
||||||
%% Variables: %u = username, %c = clientid
|
%% Variables: %u = username, %c = clientid
|
||||||
|
|
||||||
%% Superuser Query
|
%% Superuser Query
|
||||||
{superquery, [
|
{superquery, pool, [
|
||||||
{collection, "mqtt_user"},
|
{collection, "mqtt_user"},
|
||||||
{super_field, "is_superuser"},
|
{super_field, "is_superuser"},
|
||||||
{selector, {"username", "%u"}}
|
{selector, {"username", "%u"}}
|
||||||
]},
|
]}.
|
||||||
|
|
||||||
%% Authentication Query
|
%% Authentication Query
|
||||||
{authquery, [
|
{authquery, pool, [
|
||||||
{collection, "mqtt_user"},
|
{collection, "mqtt_user"},
|
||||||
{password_field, "password"},
|
{password_field, "password"},
|
||||||
%% Hash Algorithm: plain, md5, sha, sha256, pbkdf2?
|
%% Hash Algorithm: plain, md5, sha, sha256, pbkdf2?
|
||||||
{password_hash, sha256},
|
{password_hash, sha256},
|
||||||
{selector, {"username", "%u"}}
|
{selector, {"username", "%u"}}
|
||||||
]},
|
]}.
|
||||||
|
|
||||||
%% ACL Query: "%u" = username, "%c" = clientid
|
%% ACL Query: "%u" = username, "%c" = clientid
|
||||||
{aclquery, [
|
{aclquery, pool, [
|
||||||
{collection, "mqtt_acl"},
|
{collection, "mqtt_acl"},
|
||||||
{selector, {"username", "%u"}}
|
{selector, {"username", "%u"}}
|
||||||
]},
|
]}.
|
||||||
|
|
||||||
%% If no ACL rules matched, return...
|
%% If no ACL rules matched, return...
|
||||||
{acl_nomatch, deny}
|
{acl_nomatch, deny}.
|
||||||
|
|
||||||
]}
|
|
||||||
].
|
|
||||||
|
|
||||||
MongoDB Database
|
MongoDB Database
|
||||||
----------------
|
----------------
|
||||||
|
@ -526,8 +529,8 @@ MongoDB Database
|
||||||
db.createCollection("mqtt_acl")
|
db.createCollection("mqtt_acl")
|
||||||
db.mqtt_user.ensureIndex({"username":1})
|
db.mqtt_user.ensureIndex({"username":1})
|
||||||
|
|
||||||
User Collection
|
MongoDB User Collection
|
||||||
---------------
|
-----------------------
|
||||||
|
|
||||||
.. code-block:: json
|
.. code-block:: json
|
||||||
|
|
||||||
|
@ -543,8 +546,8 @@ For example::
|
||||||
db.mqtt_user.insert({username: "test", password: "password hash", is_superuser: false})
|
db.mqtt_user.insert({username: "test", password: "password hash", is_superuser: false})
|
||||||
db.mqtt_user:insert({username: "root", is_superuser: true})
|
db.mqtt_user:insert({username: "root", is_superuser: true})
|
||||||
|
|
||||||
ACL Collection
|
MongoDB ACL Collection
|
||||||
--------------
|
----------------------
|
||||||
|
|
||||||
.. code-block:: json
|
.. code-block:: json
|
||||||
|
|
||||||
|
@ -561,12 +564,34 @@ For example::
|
||||||
db.mqtt_acl.insert({username: "test", publish: ["t/1", "t/2"], subscribe: ["user/%u", "client/%c"]})
|
db.mqtt_acl.insert({username: "test", publish: ["t/1", "t/2"], subscribe: ["user/%u", "client/%c"]})
|
||||||
db.mqtt_acl.insert({username: "admin", pubsub: ["#"]})
|
db.mqtt_acl.insert({username: "admin", pubsub: ["#"]})
|
||||||
|
|
||||||
Load emqttd_plugin_mongo Plugin
|
Load MongoDB Auth/ACL Plugin
|
||||||
-------------------------------
|
----------------------------
|
||||||
|
|
||||||
.. code-block:: bash
|
.. code-block:: bash
|
||||||
|
|
||||||
./bin/emqttd_ctl plugins load emqttd_plugin_mongo
|
./bin/emqttd_ctl plugins load emqttd_auth_mongo
|
||||||
|
|
||||||
|
---------------------------
|
||||||
|
emqttd_sn: MQTT-SN Protocol
|
||||||
|
--------------------------
|
||||||
|
|
||||||
|
MQTT-SN Protocol/Gateway Plugin.
|
||||||
|
|
||||||
|
Configure MQTT-SN Plugin
|
||||||
|
-------------------------
|
||||||
|
|
||||||
|
.. NOTE:: UDP Port for MQTT-SN: 1884
|
||||||
|
|
||||||
|
etc/plugins/emqttd_sn.conf::
|
||||||
|
|
||||||
|
{listener, {1884, []}}.
|
||||||
|
|
||||||
|
Load MQTT-SN Plugin
|
||||||
|
-------------------
|
||||||
|
|
||||||
|
.. code::
|
||||||
|
|
||||||
|
./bin/emqttd_ctl plugins load emqttd_sn
|
||||||
|
|
||||||
-----------------------------
|
-----------------------------
|
||||||
emqttd_stomp - STOMP Protocol
|
emqttd_stomp - STOMP Protocol
|
||||||
|
@ -574,48 +599,41 @@ emqttd_stomp - STOMP Protocol
|
||||||
|
|
||||||
Support STOMP 1.0/1.1/1.2 clients to connect to emqttd broker and communicate with MQTT Clients.
|
Support STOMP 1.0/1.1/1.2 clients to connect to emqttd broker and communicate with MQTT Clients.
|
||||||
|
|
||||||
Configure emqttd_stomp/etc/plugin.config
|
Configure Stomp Plugin
|
||||||
----------------------------------------
|
----------------------
|
||||||
|
|
||||||
|
etc/plugins/emqttd_stomp.conf:
|
||||||
|
|
||||||
.. NOTE:: Default Port for STOMP Protocol: 61613
|
.. NOTE:: Default Port for STOMP Protocol: 61613
|
||||||
|
|
||||||
.. code-block:: erlang
|
.. code-block:: erlang
|
||||||
|
|
||||||
[
|
|
||||||
{emqttd_stomp, [
|
|
||||||
|
|
||||||
{default_user, [
|
{default_user, [
|
||||||
{login, "guest"},
|
{login, "guest"},
|
||||||
{passcode, "guest"}
|
{passcode, "guest"}
|
||||||
]},
|
]}.
|
||||||
|
|
||||||
{allow_anonymous, true},
|
{allow_anonymous, true}.
|
||||||
|
|
||||||
%%TODO: unused...
|
|
||||||
{frame, [
|
{frame, [
|
||||||
{max_headers, 10},
|
{max_headers, 10},
|
||||||
{max_header_length, 1024},
|
{max_header_length, 1024},
|
||||||
{max_body_length, 8192}
|
{max_body_length, 8192}
|
||||||
]},
|
]}.
|
||||||
|
|
||||||
{listeners, [
|
{listener, emqttd_stomp, 61613, [
|
||||||
{emqttd_stomp, 61613, [
|
|
||||||
{acceptors, 4},
|
{acceptors, 4},
|
||||||
{max_clients, 512}
|
{max_clients, 512}
|
||||||
]}
|
]}.
|
||||||
]}
|
|
||||||
|
|
||||||
]}
|
|
||||||
].
|
|
||||||
|
|
||||||
Load emqttd_stomp Plugin
|
Load Stomp Plugin
|
||||||
------------------------
|
-----------------
|
||||||
|
|
||||||
.. code-block:: bash
|
.. code-block:: bash
|
||||||
|
|
||||||
./bin/emqttd_ctl plugins load emqttd_stomp
|
./bin/emqttd_ctl plugins load emqttd_stomp
|
||||||
|
|
||||||
|
|
||||||
-----------------------------------
|
-----------------------------------
|
||||||
emqttd_sockjs - STOMP/SockJS Plugin
|
emqttd_sockjs - STOMP/SockJS Plugin
|
||||||
-----------------------------------
|
-----------------------------------
|
||||||
|
@ -629,18 +647,21 @@ Configure emqttd_sockjs
|
||||||
|
|
||||||
.. code-block:: erlang
|
.. code-block:: erlang
|
||||||
|
|
||||||
[
|
{sockjs, []}.
|
||||||
{emqttd_sockjs, [
|
|
||||||
|
|
||||||
{sockjs, []},
|
{cowboy_listener, {stomp_sockjs, 61616, 4}}.
|
||||||
|
|
||||||
{cowboy_listener, {stomp_sockjs, 61616, 4}},
|
|
||||||
|
|
||||||
|
%% TODO: unused...
|
||||||
|
{stomp, [
|
||||||
|
{frame, [
|
||||||
|
{max_headers, 10},
|
||||||
|
{max_header_length, 1024},
|
||||||
|
{max_body_length, 8192}
|
||||||
]}
|
]}
|
||||||
].
|
]}.
|
||||||
|
|
||||||
Load emqttd_sockjs Plugin
|
Load SockJS Plugin
|
||||||
-------------------------
|
------------------
|
||||||
|
|
||||||
.. NOTE:: emqttd_stomp Plugin required.
|
.. NOTE:: emqttd_stomp Plugin required.
|
||||||
|
|
||||||
|
@ -661,8 +682,8 @@ emqttd_recon - Recon Plugin
|
||||||
|
|
||||||
The plugin loads `recon`_ library on a running emqttd broker. Recon libray helps debug and optimize an Erlang application.
|
The plugin loads `recon`_ library on a running emqttd broker. Recon libray helps debug and optimize an Erlang application.
|
||||||
|
|
||||||
Load emqttd_recon Plugin
|
Load Recon Plugin
|
||||||
------------------------
|
-----------------
|
||||||
|
|
||||||
.. code-block:: bash
|
.. code-block:: bash
|
||||||
|
|
||||||
|
@ -689,8 +710,8 @@ Erlang Module Reloader for Development
|
||||||
|
|
||||||
.. NOTE:: Don't load the plugin in production!
|
.. NOTE:: Don't load the plugin in production!
|
||||||
|
|
||||||
Load emqttd_reloader Plugin
|
Load 'Reloader' Plugin
|
||||||
---------------------------
|
----------------------
|
||||||
|
|
||||||
.. code-block:: bash
|
.. code-block:: bash
|
||||||
|
|
||||||
|
@ -712,15 +733,22 @@ Plugin Development Guide
|
||||||
Create a Plugin Project
|
Create a Plugin Project
|
||||||
-----------------------
|
-----------------------
|
||||||
|
|
||||||
Clone emqttd source from github.com::
|
Clone emqttd_plugin_template source from github.com::
|
||||||
|
|
||||||
git clone https://github.com/emqtt/emqttd.git
|
git clone https://github.com/emqtt/emqttd_plugin_template.git
|
||||||
|
|
||||||
Create a plugin project under 'plugins' folder::
|
Create a plugin project with erlang.mk and depends on 'emqttd' application, the 'Makefile'::
|
||||||
|
|
||||||
cd plugins && mkdir emqttd_my_plugin
|
PROJECT = emqttd_plugin_abc
|
||||||
|
PROJECT_DESCRIPTION = emqttd abc plugin
|
||||||
|
PROJECT_VERSION = 1.0
|
||||||
|
|
||||||
cd emqttd_my_plugin && rebar create-app appid=emqttd_my_plugin
|
DEPS = emqttd
|
||||||
|
dep_emqttd = git https://github.com/emqtt/emqttd emq20
|
||||||
|
|
||||||
|
COVER = true
|
||||||
|
|
||||||
|
include erlang.mk
|
||||||
|
|
||||||
Template Plugin: https://github.com/emqtt/emqttd_plugin_template
|
Template Plugin: https://github.com/emqtt/emqttd_plugin_template
|
||||||
|
|
||||||
|
@ -735,7 +763,7 @@ emqttd_auth_demo.erl - demo authentication module:
|
||||||
|
|
||||||
-behaviour(emqttd_auth_mod).
|
-behaviour(emqttd_auth_mod).
|
||||||
|
|
||||||
-include("../../../include/emqttd.hrl").
|
-include_lib("emqttd/include/emqttd.hrl").
|
||||||
|
|
||||||
-export([init/1, check/3, description/0]).
|
-export([init/1, check/3, description/0]).
|
||||||
|
|
||||||
|
@ -754,7 +782,7 @@ emqttd_acl_demo.erl - demo ACL module:
|
||||||
|
|
||||||
-module(emqttd_acl_demo).
|
-module(emqttd_acl_demo).
|
||||||
|
|
||||||
-include("../../../include/emqttd.hrl").
|
-include_lib("emqttd/include/emqttd.hrl").
|
||||||
|
|
||||||
%% ACL callbacks
|
%% ACL callbacks
|
||||||
-export([init/1, check_acl/2, reload_acl/1, description/0]).
|
-export([init/1, check_acl/2, reload_acl/1, description/0]).
|
||||||
|
@ -830,7 +858,7 @@ emqttd_cli_demo.erl:
|
||||||
|
|
||||||
-module(emqttd_cli_demo).
|
-module(emqttd_cli_demo).
|
||||||
|
|
||||||
-include("../../../include/emqttd_cli.hrl").
|
-include_lib("emqttd/include/emqttd_cli.hrl").
|
||||||
|
|
||||||
-export([cmd/1]).
|
-export([cmd/1]).
|
||||||
|
|
||||||
|
@ -850,13 +878,14 @@ There will be a new CLI after the plugin loaded::
|
||||||
|
|
||||||
./bin/emqttd_ctl cmd arg1 arg2
|
./bin/emqttd_ctl cmd arg1 arg2
|
||||||
|
|
||||||
|
|
||||||
.. _emqttd_dashboard: https://github.com/emqtt/emqttd_dashboard
|
.. _emqttd_dashboard: https://github.com/emqtt/emqttd_dashboard
|
||||||
|
.. _emqttd_auth_ldap: https://github.com/emqtt/emqttd_auth_ldap
|
||||||
.. _emqttd_auth_http: https://github.com/emqtt/emqttd_auth_http
|
.. _emqttd_auth_http: https://github.com/emqtt/emqttd_auth_http
|
||||||
.. _emqttd_plugin_mysql: https://github.com/emqtt/emqttd_plugin_mysql
|
.. _emqttd_auth_mysql: https://github.com/emqtt/emqttd_plugin_mysql
|
||||||
.. _emqttd_plugin_pgsql: https://github.com/emqtt/emqttd_plugin_pgsql
|
.. _emqttd_auth_pgsql: https://github.com/emqtt/emqttd_plugin_pgsql
|
||||||
.. _emqttd_plugin_redis: https://github.com/emqtt/emqttd_plugin_redis
|
.. _emqttd_auth_redis: https://github.com/emqtt/emqttd_plugin_redis
|
||||||
.. _emqttd_plugin_mongo: https://github.com/emqtt/emqttd_plugin_mongo
|
.. _emqttd_auth_mongo: https://github.com/emqtt/emqttd_plugin_mongo
|
||||||
|
.. _emqttd_sn: https://github.com/emqtt/emqttd_sn
|
||||||
.. _emqttd_stomp: https://github.com/emqtt/emqttd_stomp
|
.. _emqttd_stomp: https://github.com/emqtt/emqttd_stomp
|
||||||
.. _emqttd_sockjs: https://github.com/emqtt/emqttd_sockjs
|
.. _emqttd_sockjs: https://github.com/emqtt/emqttd_sockjs
|
||||||
.. _emqttd_recon: https://github.com/emqtt/emqttd_recon
|
.. _emqttd_recon: https://github.com/emqtt/emqttd_recon
|
||||||
|
|
|
@ -0,0 +1,31 @@
|
||||||
|
|
||||||
|
.. _tables:
|
||||||
|
|
||||||
|
======
|
||||||
|
Tables
|
||||||
|
======
|
||||||
|
|
||||||
|
+--------------------+--------+----------------------------------------+
|
||||||
|
| Table | Type | Description |
|
||||||
|
+====================+========+========================================+
|
||||||
|
| mqtt_trie | mnesia | Trie Table |
|
||||||
|
+--------------------+--------+----------------------------------------+
|
||||||
|
| mqtt_trie_node | mnesia | Trie Node Table |
|
||||||
|
+--------------------+--------+----------------------------------------+
|
||||||
|
| mqtt_route | mnesia | Global Route Table |
|
||||||
|
+--------------------+--------+----------------------------------------+
|
||||||
|
| mqtt_local_route | mnesia | Local Route Table |
|
||||||
|
+--------------------+--------+----------------------------------------+
|
||||||
|
| mqtt_pubsub | ets | PubSub Tab |
|
||||||
|
+--------------------+--------+----------------------------------------+
|
||||||
|
| mqtt_subscriber | ets | Subscriber Tab |
|
||||||
|
+--------------------+--------+----------------------------------------+
|
||||||
|
| mqtt_subscription | ets | Subscription Tab |
|
||||||
|
+--------------------+--------+----------------------------------------+
|
||||||
|
| mqtt_session | mnesia | Global Session Table |
|
||||||
|
+--------------------+--------+----------------------------------------+
|
||||||
|
| mqtt_local_session | ets | Local Session Table |
|
||||||
|
+--------------------+--------+----------------------------------------+
|
||||||
|
| mqtt_client | ets | Client Table |
|
||||||
|
+--------------------+--------+----------------------------------------+
|
||||||
|
|
|
@ -1 +0,0 @@
|
||||||
emqttd plugin cannot include "emqttd/include/emqttd.hrl" without this directory:(
|
|
File diff suppressed because it is too large
Load Diff
|
@ -14,8 +14,6 @@
|
||||||
%% limitations under the License.
|
%% limitations under the License.
|
||||||
%%--------------------------------------------------------------------
|
%%--------------------------------------------------------------------
|
||||||
|
|
||||||
%% MQTT Broker Header
|
|
||||||
|
|
||||||
%%--------------------------------------------------------------------
|
%%--------------------------------------------------------------------
|
||||||
%% Banner
|
%% Banner
|
||||||
%%--------------------------------------------------------------------
|
%%--------------------------------------------------------------------
|
||||||
|
@ -26,59 +24,43 @@
|
||||||
|
|
||||||
-define(PROTOCOL_VERSION, "MQTT/3.1.1").
|
-define(PROTOCOL_VERSION, "MQTT/3.1.1").
|
||||||
|
|
||||||
-define(ERTS_MINIMUM, "6.0").
|
-define(ERTS_MINIMUM, "7.0").
|
||||||
|
|
||||||
%% System Topics.
|
%%--------------------------------------------------------------------
|
||||||
-define(SYSTOP, <<"$SYS">>).
|
%% Sys/Queue/Share Topics' Prefix
|
||||||
|
%%--------------------------------------------------------------------
|
||||||
|
|
||||||
%% Queue Topics.
|
-define(SYSTOP, <<"$SYS/">>). %% System Topic
|
||||||
-define(QTop, <<"$Q">>).
|
|
||||||
|
-define(QUEUE, <<"$queue/">>). %% Queue Topic
|
||||||
|
|
||||||
|
-define(SHARE, <<"$share/">>). %% Shared Topic
|
||||||
|
|
||||||
%%--------------------------------------------------------------------
|
%%--------------------------------------------------------------------
|
||||||
%% PubSub
|
%% PubSub
|
||||||
%%--------------------------------------------------------------------
|
%%--------------------------------------------------------------------
|
||||||
|
|
||||||
-type pubsub() :: publish | subscribe.
|
-type(pubsub() :: publish | subscribe).
|
||||||
|
|
||||||
-define(IS_PUBSUB(PS), (PS =:= publish orelse PS =:= subscribe)).
|
-define(PUBSUB(PS), (PS =:= publish orelse PS =:= subscribe)).
|
||||||
|
|
||||||
%%--------------------------------------------------------------------
|
%%--------------------------------------------------------------------
|
||||||
%% MQTT Topic
|
%% MQTT Topic
|
||||||
%%--------------------------------------------------------------------
|
%%--------------------------------------------------------------------
|
||||||
|
|
||||||
-record(mqtt_topic, {
|
-record(mqtt_topic, {
|
||||||
topic :: binary(),
|
topic :: binary(),
|
||||||
flags :: [retained | static]
|
flags = [] :: [retained | static]
|
||||||
}).
|
}).
|
||||||
|
|
||||||
-type mqtt_topic() :: #mqtt_topic{}.
|
-type(mqtt_topic() :: #mqtt_topic{}).
|
||||||
|
|
||||||
%%--------------------------------------------------------------------
|
|
||||||
%% MQTT Subscription
|
|
||||||
%%--------------------------------------------------------------------
|
|
||||||
-record(mqtt_subscription, {
|
|
||||||
subid :: binary() | atom(),
|
|
||||||
topic :: binary(),
|
|
||||||
qos = 0 :: 0 | 1 | 2
|
|
||||||
}).
|
|
||||||
|
|
||||||
-type mqtt_subscription() :: #mqtt_subscription{}.
|
|
||||||
|
|
||||||
%%--------------------------------------------------------------------
|
|
||||||
%% MQTT Route
|
|
||||||
%%--------------------------------------------------------------------
|
|
||||||
-record(mqtt_route, {
|
|
||||||
topic :: binary(),
|
|
||||||
node :: node()
|
|
||||||
}).
|
|
||||||
|
|
||||||
-type mqtt_route() :: #mqtt_route{}.
|
|
||||||
|
|
||||||
%%--------------------------------------------------------------------
|
%%--------------------------------------------------------------------
|
||||||
%% MQTT Client
|
%% MQTT Client
|
||||||
%%--------------------------------------------------------------------
|
%%--------------------------------------------------------------------
|
||||||
|
|
||||||
-type ws_header_key() :: atom() | binary() | string().
|
-type(ws_header_key() :: atom() | binary() | string()).
|
||||||
-type ws_header_val() :: atom() | binary() | string() | integer().
|
-type(ws_header_val() :: atom() | binary() | string() | integer()).
|
||||||
|
|
||||||
-record(mqtt_client, {
|
-record(mqtt_client, {
|
||||||
client_id :: binary() | undefined,
|
client_id :: binary() | undefined,
|
||||||
|
@ -93,41 +75,63 @@
|
||||||
connected_at :: erlang:timestamp()
|
connected_at :: erlang:timestamp()
|
||||||
}).
|
}).
|
||||||
|
|
||||||
-type mqtt_client() :: #mqtt_client{}.
|
-type(mqtt_client() :: #mqtt_client{}).
|
||||||
|
|
||||||
%%--------------------------------------------------------------------
|
%%--------------------------------------------------------------------
|
||||||
%% MQTT Session
|
%% MQTT Session
|
||||||
%%--------------------------------------------------------------------
|
%%--------------------------------------------------------------------
|
||||||
|
|
||||||
-record(mqtt_session, {
|
-record(mqtt_session, {
|
||||||
client_id :: binary(),
|
client_id :: binary(),
|
||||||
sess_pid :: pid(),
|
sess_pid :: pid(),
|
||||||
persistent :: boolean()
|
persistent :: boolean()
|
||||||
}).
|
}).
|
||||||
|
|
||||||
-type mqtt_session() :: #mqtt_session{}.
|
-type(mqtt_session() :: #mqtt_session{}).
|
||||||
|
|
||||||
%%--------------------------------------------------------------------
|
%%--------------------------------------------------------------------
|
||||||
%% MQTT Message
|
%% MQTT Message
|
||||||
%%--------------------------------------------------------------------
|
%%--------------------------------------------------------------------
|
||||||
-type mqtt_msgid() :: binary() | undefined.
|
-type(mqtt_msgid() :: binary() | undefined).
|
||||||
-type mqtt_pktid() :: 1..16#ffff | undefined.
|
-type(mqtt_pktid() :: 1..16#ffff | undefined).
|
||||||
|
|
||||||
-record(mqtt_message, {
|
-record(mqtt_message, {
|
||||||
msgid :: mqtt_msgid(), %% Global unique message ID
|
id :: mqtt_msgid(), %% Global unique message ID
|
||||||
pktid :: mqtt_pktid(), %% PacketId
|
pktid :: mqtt_pktid(), %% PacketId
|
||||||
|
from :: {binary(), undefined | binary()}, %% ClientId and Username
|
||||||
topic :: binary(), %% Topic that the message is published to
|
topic :: binary(), %% Topic that the message is published to
|
||||||
from :: binary() | atom(), %% ClientId of the publisher
|
|
||||||
sender :: binary() | undefined, %% Username of the publisher
|
|
||||||
qos = 0 :: 0 | 1 | 2, %% Message QoS
|
qos = 0 :: 0 | 1 | 2, %% Message QoS
|
||||||
flags = [] :: [retain | dup | sys], %% Message Flags
|
flags = [] :: [retain | dup | sys], %% Message Flags
|
||||||
retain = false :: boolean(), %% Retain flag
|
retain = false :: boolean(), %% Retain flag
|
||||||
dup = false :: boolean(), %% Dup flag
|
dup = false :: boolean(), %% Dup flag
|
||||||
sys = false :: boolean(), %% $SYS flag
|
sys = false :: boolean(), %% $SYS flag
|
||||||
|
headers = [] :: list(),
|
||||||
payload :: binary(), %% Payload
|
payload :: binary(), %% Payload
|
||||||
timestamp :: erlang:timestamp() %% os:timestamp
|
timestamp :: pos_integer() %% os:timestamp to seconds
|
||||||
}).
|
}).
|
||||||
|
|
||||||
-type mqtt_message() :: #mqtt_message{}.
|
-type(mqtt_message() :: #mqtt_message{}).
|
||||||
|
|
||||||
|
%%--------------------------------------------------------------------
|
||||||
|
%% MQTT Delivery
|
||||||
|
%%--------------------------------------------------------------------
|
||||||
|
-record(mqtt_delivery, {
|
||||||
|
sender :: pid(), %% Pid of the sender/publisher
|
||||||
|
message :: mqtt_message(), %% Message
|
||||||
|
flows :: list()
|
||||||
|
}).
|
||||||
|
|
||||||
|
-type(mqtt_delivery() :: #mqtt_delivery{}).
|
||||||
|
|
||||||
|
%%--------------------------------------------------------------------
|
||||||
|
%% MQTT Route
|
||||||
|
%%--------------------------------------------------------------------
|
||||||
|
-record(mqtt_route, {
|
||||||
|
topic :: binary(),
|
||||||
|
node :: node()
|
||||||
|
}).
|
||||||
|
|
||||||
|
-type(mqtt_route() :: #mqtt_route{}).
|
||||||
|
|
||||||
%%--------------------------------------------------------------------
|
%%--------------------------------------------------------------------
|
||||||
%% MQTT Alarm
|
%% MQTT Alarm
|
||||||
|
@ -140,7 +144,7 @@
|
||||||
timestamp :: erlang:timestamp() %% Timestamp
|
timestamp :: erlang:timestamp() %% Timestamp
|
||||||
}).
|
}).
|
||||||
|
|
||||||
-type mqtt_alarm() :: #mqtt_alarm{}.
|
-type(mqtt_alarm() :: #mqtt_alarm{}).
|
||||||
|
|
||||||
%%--------------------------------------------------------------------
|
%%--------------------------------------------------------------------
|
||||||
%% MQTT Plugin
|
%% MQTT Plugin
|
||||||
|
@ -149,11 +153,10 @@
|
||||||
name,
|
name,
|
||||||
version,
|
version,
|
||||||
descr,
|
descr,
|
||||||
config,
|
|
||||||
active = false
|
active = false
|
||||||
}).
|
}).
|
||||||
|
|
||||||
-type mqtt_plugin() :: #mqtt_plugin{}.
|
-type(mqtt_plugin() :: #mqtt_plugin{}).
|
||||||
|
|
||||||
%%--------------------------------------------------------------------
|
%%--------------------------------------------------------------------
|
||||||
%% MQTT CLI Command
|
%% MQTT CLI Command
|
||||||
|
@ -168,5 +171,5 @@
|
||||||
descr
|
descr
|
||||||
}).
|
}).
|
||||||
|
|
||||||
-type mqtt_cli() :: #mqtt_cli{}.
|
-type(mqtt_cli() :: #mqtt_cli{}).
|
||||||
|
|
||||||
|
|
|
@ -26,7 +26,7 @@
|
||||||
{?MQTT_PROTO_V31, <<"MQIsdp">>},
|
{?MQTT_PROTO_V31, <<"MQIsdp">>},
|
||||||
{?MQTT_PROTO_V311, <<"MQTT">>}]).
|
{?MQTT_PROTO_V311, <<"MQTT">>}]).
|
||||||
|
|
||||||
-type mqtt_vsn() :: ?MQTT_PROTO_V31 | ?MQTT_PROTO_V311.
|
-type(mqtt_vsn() :: ?MQTT_PROTO_V31 | ?MQTT_PROTO_V311).
|
||||||
|
|
||||||
%%--------------------------------------------------------------------
|
%%--------------------------------------------------------------------
|
||||||
%% MQTT QoS
|
%% MQTT QoS
|
||||||
|
@ -41,11 +41,11 @@
|
||||||
|
|
||||||
-define(IS_QOS(I), (I >= ?QOS0 andalso I =< ?QOS2)).
|
-define(IS_QOS(I), (I >= ?QOS0 andalso I =< ?QOS2)).
|
||||||
|
|
||||||
-type mqtt_qos() :: ?QOS0 | ?QOS1 | ?QOS2.
|
-type(mqtt_qos() :: ?QOS0 | ?QOS1 | ?QOS2).
|
||||||
|
|
||||||
-type mqtt_qos_name() :: qos0 | at_most_once |
|
-type(mqtt_qos_name() :: qos0 | at_most_once |
|
||||||
qos1 | at_least_once |
|
qos1 | at_least_once |
|
||||||
qos2 | exactly_once.
|
qos2 | exactly_once).
|
||||||
|
|
||||||
-define(QOS_I(Name),
|
-define(QOS_I(Name),
|
||||||
begin
|
begin
|
||||||
|
@ -102,7 +102,7 @@
|
||||||
'PINGRESP',
|
'PINGRESP',
|
||||||
'DISCONNECT']).
|
'DISCONNECT']).
|
||||||
|
|
||||||
-type mqtt_packet_type() :: ?RESERVED..?DISCONNECT.
|
-type(mqtt_packet_type() :: ?RESERVED..?DISCONNECT).
|
||||||
|
|
||||||
%%--------------------------------------------------------------------
|
%%--------------------------------------------------------------------
|
||||||
%% MQTT Connect Return Codes
|
%% MQTT Connect Return Codes
|
||||||
|
@ -114,7 +114,7 @@
|
||||||
-define(CONNACK_CREDENTIALS, 4). %% Username or password is malformed
|
-define(CONNACK_CREDENTIALS, 4). %% Username or password is malformed
|
||||||
-define(CONNACK_AUTH, 5). %% Client is not authorized to connect
|
-define(CONNACK_AUTH, 5). %% Client is not authorized to connect
|
||||||
|
|
||||||
-type mqtt_connack() :: ?CONNACK_ACCEPT..?CONNACK_AUTH.
|
-type(mqtt_connack() :: ?CONNACK_ACCEPT..?CONNACK_AUTH).
|
||||||
|
|
||||||
%%--------------------------------------------------------------------
|
%%--------------------------------------------------------------------
|
||||||
%% MQTT Parser and Serializer
|
%% MQTT Parser and Serializer
|
||||||
|
@ -135,8 +135,9 @@
|
||||||
%%--------------------------------------------------------------------
|
%%--------------------------------------------------------------------
|
||||||
%% MQTT Packets
|
%% MQTT Packets
|
||||||
%%--------------------------------------------------------------------
|
%%--------------------------------------------------------------------
|
||||||
-type mqtt_client_id() :: binary().
|
-type(mqtt_client_id() :: binary()).
|
||||||
-type mqtt_packet_id() :: 1..16#ffff | undefined.
|
-type(mqtt_username() :: binary() | undefined).
|
||||||
|
-type(mqtt_packet_id() :: 1..16#ffff | undefined).
|
||||||
|
|
||||||
-record(mqtt_packet_connect, {
|
-record(mqtt_packet_connect, {
|
||||||
client_id = <<>> :: mqtt_client_id(),
|
client_id = <<>> :: mqtt_client_id(),
|
||||||
|
|
|
@ -14,7 +14,7 @@
|
||||||
%% limitations under the License.
|
%% limitations under the License.
|
||||||
%%--------------------------------------------------------------------
|
%%--------------------------------------------------------------------
|
||||||
|
|
||||||
-type trie_node_id() :: binary() | atom().
|
-type(trie_node_id() :: binary() | atom()).
|
||||||
|
|
||||||
-record(trie_node, {
|
-record(trie_node, {
|
||||||
node_id :: trie_node_id(),
|
node_id :: trie_node_id(),
|
||||||
|
|
|
@ -1 +0,0 @@
|
||||||
Subproject commit d5a40dbea347b3aa4a7631dec40cc09ee5ec4024
|
|
|
@ -1 +0,0 @@
|
||||||
Subproject commit 95a3d0964b2d6d4541fdabe6c07869a14f4c990d
|
|
|
@ -1 +0,0 @@
|
||||||
Subproject commit 7ed2f9c51f0f3a2ea9b0003705b0dd30c3f3ba9e
|
|
|
@ -1 +0,0 @@
|
||||||
Subproject commit e91b17f2880756ae0c411373594e5e226478c4fb
|
|
|
@ -1 +0,0 @@
|
||||||
Subproject commit f4c571e74fbc02c1f02368ba69e5b08f4468e4e8
|
|
|
@ -1 +0,0 @@
|
||||||
Subproject commit 10bda5043e437f8bd5c4997d4929006fbb6931e1
|
|
|
@ -1 +0,0 @@
|
||||||
Subproject commit c05985ae5828ea8a1cc364cc9a0ad764ea009a5d
|
|
|
@ -1 +0,0 @@
|
||||||
Subproject commit d4879c24f055e4215b5a984cfed72ecd5945df2d
|
|
|
@ -1 +0,0 @@
|
||||||
Subproject commit 0e1da23391951ad7f7d83e07d26b78bb7d0c3191
|
|
|
@ -1 +0,0 @@
|
||||||
Subproject commit ccf19284402fa305ef392577aa2ce6edc18890ae
|
|
48
rebar.config
48
rebar.config
|
@ -1,48 +1,4 @@
|
||||||
%% -*- mode: erlang;erlang-indent-level: 4;indent-tabs-mode: nil -*-
|
|
||||||
%% ex: ts=4 sw=4 ft=erlang et
|
|
||||||
|
|
||||||
{require_min_otp_vsn, "R17"}.
|
|
||||||
|
|
||||||
%% fail_on_warning,
|
|
||||||
{erl_opts, [debug_info, {parse_transform, lager_transform}]}.
|
|
||||||
|
|
||||||
{erl_opts, [warn_export_all,
|
|
||||||
warn_unused_import,
|
|
||||||
{i, "include"},
|
|
||||||
{src_dirs, ["src"]}]}.
|
|
||||||
|
|
||||||
{validate_app_modules, true}.
|
|
||||||
|
|
||||||
{erl_first_files, ["src/gen_server2.erl",
|
|
||||||
"src/emqttd_auth_mod.erl",
|
|
||||||
"src/emqttd_acl_mod.erl"]}.
|
|
||||||
|
|
||||||
{eunit_opts, []}. %%verbose
|
|
||||||
|
|
||||||
{ct_dir, "test"}.
|
|
||||||
|
|
||||||
{ct_log_dir, "logs"}.
|
|
||||||
|
|
||||||
{ct_extra_params, "-name ct_emqttd@127.0.0.1 -config rel/files/emqttd.test.config"}.
|
|
||||||
|
|
||||||
{ct_use_short_names, false}.
|
|
||||||
|
|
||||||
{xref_checks, [undefined_function_calls]}.
|
|
||||||
|
|
||||||
{cover_enabled, true}.
|
|
||||||
|
|
||||||
%% plugins cannot find emqttd.hrl without ".." lib dirs:(
|
|
||||||
%% but this setting will make deps apps collision
|
|
||||||
%% comment in 0.13.0 release
|
|
||||||
%% {lib_dirs, ["../"]}.
|
|
||||||
|
|
||||||
{sub_dirs, ["rel", "plugins/*/"]}.
|
|
||||||
|
|
||||||
{deps, [
|
{deps, [
|
||||||
{gproc, ".*", {git, "git://github.com/uwiger/gproc.git", {branch, "master"}}},
|
{gproc,".*",{git,"https://github.com/uwiger/gproc.git",""}},{lager,".*",{git,"https://github.com/basho/lager.git",""}},{gen_logger,".*",{git,"https://github.com/emqtt/gen_logger.git",""}},{gen_conf,".*",{git,"https://github.com/emqtt/gen_conf.git",""}},{esockd,".*",{git,"https://github.com/emqtt/esockd.git","udp"}},{mochiweb,".*",{git,"https://github.com/emqtt/mochiweb.git",""}}
|
||||||
{lager, ".*", {git, "git://github.com/basho/lager.git", {branch, "master"}}},
|
|
||||||
{esockd, ".*", {git, "git://github.com/emqtt/esockd.git", {tag, "4.0"}}},
|
|
||||||
{mochiweb, "4.*", {git, "git://github.com/emqtt/mochiweb.git", {tag, "4.1.1"}}}
|
|
||||||
]}.
|
]}.
|
||||||
|
{erl_opts, [{parse_transform,lager_transform}]}.
|
||||||
{recursive_cmds, [ct, eunit, clean]}.
|
|
||||||
|
|
|
@ -1,28 +0,0 @@
|
||||||
%%%-----------------------------------------------------------------------------
|
|
||||||
%%%
|
|
||||||
%%% [ACL](https://github.com/emqtt/emqttd/wiki/ACL)
|
|
||||||
%%%
|
|
||||||
%%% -type who() :: all | binary() |
|
|
||||||
%%% {ipaddr, esockd_access:cidr()} |
|
|
||||||
%%% {client, binary()} |
|
|
||||||
%%% {user, binary()}.
|
|
||||||
%%%
|
|
||||||
%%% -type access() :: subscribe | publish | pubsub.
|
|
||||||
%%%
|
|
||||||
%%% -type topic() :: binary().
|
|
||||||
%%%
|
|
||||||
%%% -type rule() :: {allow, all} |
|
|
||||||
%%% {allow, who(), access(), list(topic())} |
|
|
||||||
%%% {deny, all} |
|
|
||||||
%%% {deny, who(), access(), list(topic())}.
|
|
||||||
%%%
|
|
||||||
%%%-----------------------------------------------------------------------------
|
|
||||||
|
|
||||||
{allow, {user, "dashboard"}, subscribe, ["$SYS/#"]}.
|
|
||||||
|
|
||||||
{allow, {ipaddr, "127.0.0.1"}, pubsub, ["$SYS/#", "#"]}.
|
|
||||||
|
|
||||||
{deny, all, subscribe, ["$SYS/#", {eq, "#"}]}.
|
|
||||||
|
|
||||||
{allow, all}.
|
|
||||||
|
|
|
@ -1,3 +0,0 @@
|
||||||
testclientid0
|
|
||||||
testclientid1 127.0.0.1
|
|
||||||
testclientid2 192.168.0.1/24
|
|
323
rel/files/emqttd
323
rel/files/emqttd
|
@ -1,323 +0,0 @@
|
||||||
#!/bin/sh
|
|
||||||
# -*- tab-width:4;indent-tabs-mode:nil -*-
|
|
||||||
# ex: ts=4 sw=4 et
|
|
||||||
|
|
||||||
# /bin/sh on Solaris is not a POSIX compatible shell, but /usr/bin/ksh is.
|
|
||||||
if [ `uname -s` = 'SunOS' -a "${POSIX_SHELL}" != "true" ]; then
|
|
||||||
POSIX_SHELL="true"
|
|
||||||
export POSIX_SHELL
|
|
||||||
# To support 'whoami' add /usr/ucb to path
|
|
||||||
PATH=/usr/ucb:$PATH
|
|
||||||
export PATH
|
|
||||||
exec /usr/bin/ksh $0 "$@"
|
|
||||||
fi
|
|
||||||
unset POSIX_SHELL # clear it so if we invoke other scripts, they run as ksh as well
|
|
||||||
|
|
||||||
RUNNER_SCRIPT_DIR={{runner_script_dir}}
|
|
||||||
RUNNER_SCRIPT=${0##*/}
|
|
||||||
|
|
||||||
RUNNER_BASE_DIR={{runner_base_dir}}
|
|
||||||
RUNNER_ETC_DIR={{runner_etc_dir}}
|
|
||||||
RUNNER_LIB_DIR={{platform_lib_dir}}
|
|
||||||
RUNNER_LOG_DIR={{runner_log_dir}}
|
|
||||||
RUNNER_DATA_DIR=$RUNNER_BASE_DIR/data
|
|
||||||
RUNNER_PLUGINS_DIR=$RUNNER_BASE_DIR/plugins
|
|
||||||
|
|
||||||
# Note the trailing slash on $PIPE_DIR/
|
|
||||||
PIPE_DIR={{pipe_dir}}
|
|
||||||
RUNNER_USER={{runner_user}}
|
|
||||||
PLATFORM_DATA_DIR={{platform_data_dir}}
|
|
||||||
SSL_DIST_CONFIG=$PLATFORM_DATA_DIR/ssl_distribution.args_file
|
|
||||||
RIAK_VERSION="git"
|
|
||||||
|
|
||||||
WHOAMI=$(whoami)
|
|
||||||
|
|
||||||
# Make sure this script is running as the appropriate user
|
|
||||||
if ([ "$RUNNER_USER" ] && [ "x$WHOAMI" != "x$RUNNER_USER" ]); then
|
|
||||||
type sudo > /dev/null 2>&1
|
|
||||||
if [ $? -ne 0 ]; then
|
|
||||||
echo "sudo doesn't appear to be installed and your EUID isn't $RUNNER_USER" 1>&2
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
echo "Attempting to restart script through sudo -H -u $RUNNER_USER" >&2
|
|
||||||
exec sudo -H -u $RUNNER_USER -i $RUNNER_SCRIPT_DIR/$RUNNER_SCRIPT $@
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Warn the user if ulimit -n is less than 1024
|
|
||||||
ULIMIT_F=`ulimit -n`
|
|
||||||
if [ "$ULIMIT_F" -lt 1024 ]; then
|
|
||||||
echo "!!!!"
|
|
||||||
echo "!!!! WARNING: ulimit -n is ${ULIMIT_F}; 1024 is the recommended minimum."
|
|
||||||
echo "!!!!"
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Make sure CWD is set to runner base dir
|
|
||||||
cd $RUNNER_BASE_DIR
|
|
||||||
|
|
||||||
# Make sure log directory exists
|
|
||||||
mkdir -p $RUNNER_LOG_DIR
|
|
||||||
|
|
||||||
# Make sure the data directory exists
|
|
||||||
mkdir -p $PLATFORM_DATA_DIR
|
|
||||||
|
|
||||||
# Warn the user if they don't have write permissions on the log dir
|
|
||||||
if [ ! -w $RUNNER_LOG_DIR ]; then
|
|
||||||
echo "!!!!"
|
|
||||||
echo "!!!! WARNING: $RUNNER_LOG_DIR not writable; logs and crash dumps unavailable."
|
|
||||||
echo "!!!!"
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Extract the target node name from node.args
|
|
||||||
NAME_ARG=`egrep '^\-s?name' $RUNNER_ETC_DIR/vm.args`
|
|
||||||
if [ -z "$NAME_ARG" ]; then
|
|
||||||
echo "vm.args needs to have either -name or -sname parameter."
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
NODE_NAME=${NAME_ARG##* }
|
|
||||||
|
|
||||||
# Extract the target cookie
|
|
||||||
COOKIE_ARG=`grep '^\-setcookie' $RUNNER_ETC_DIR/vm.args`
|
|
||||||
if [ -z "$COOKIE_ARG" ]; then
|
|
||||||
echo "vm.args needs to have a -setcookie parameter."
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Identify the script name
|
|
||||||
SCRIPT=`basename $0`
|
|
||||||
|
|
||||||
# Parse out release and erts info
|
|
||||||
START_ERL=`cat $RUNNER_BASE_DIR/releases/start_erl.data`
|
|
||||||
ERTS_VSN=${START_ERL% *}
|
|
||||||
APP_VSN=${START_ERL#* }
|
|
||||||
|
|
||||||
# Add ERTS bin dir to our path
|
|
||||||
ERTS_PATH=$RUNNER_BASE_DIR/erts-$ERTS_VSN/bin
|
|
||||||
|
|
||||||
# Setup command to control the node
|
|
||||||
NODETOOL="$ERTS_PATH/escript $ERTS_PATH/nodetool $NAME_ARG $COOKIE_ARG"
|
|
||||||
NODETOOL_LITE="$ERTS_PATH/escript $ERTS_PATH/nodetool"
|
|
||||||
|
|
||||||
# Common functions
|
|
||||||
|
|
||||||
# Ping node without allowing nodetool to take stdin
|
|
||||||
ping_node() {
|
|
||||||
$NODETOOL ping < /dev/null
|
|
||||||
}
|
|
||||||
|
|
||||||
# Set the PID global variable, return 1 on error
|
|
||||||
get_pid() {
|
|
||||||
PID=`$NODETOOL getpid < /dev/null`
|
|
||||||
ES=$?
|
|
||||||
if [ "$ES" -ne 0 ]; then
|
|
||||||
echo "Node is not running!"
|
|
||||||
return 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
# don't allow empty or init pid's
|
|
||||||
if [ -z $PID ] || [ "$PID" -le 1 ]; then
|
|
||||||
return 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
# Scrape out SSL distribution config info from vm.args into $SSL_DIST_CONFIG
|
|
||||||
rm -f $SSL_DIST_CONFIG
|
|
||||||
sed -n '/Begin SSL distribution items/,/End SSL distribution items/p' \
|
|
||||||
$RUNNER_ETC_DIR/vm.args > $SSL_DIST_CONFIG
|
|
||||||
|
|
||||||
# Check the first argument for instructions
|
|
||||||
case "$1" in
|
|
||||||
start)
|
|
||||||
# Make sure there is not already a node running
|
|
||||||
RES=`ping_node`
|
|
||||||
if [ "$RES" = "pong" ]; then
|
|
||||||
echo "Node is already running!"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
# Sanity check the emqttd.config file
|
|
||||||
RES=`$NODETOOL_LITE chkconfig $RUNNER_ETC_DIR/emqttd.config`
|
|
||||||
if [ $? != 0 ]; then
|
|
||||||
echo "Error reading $RUNNER_ETC_DIR/emqttd.config"
|
|
||||||
echo $RES
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
HEART_COMMAND="$RUNNER_SCRIPT_DIR/$SCRIPT start"
|
|
||||||
export HEART_COMMAND
|
|
||||||
mkdir -p $PIPE_DIR
|
|
||||||
$ERTS_PATH/run_erl -daemon $PIPE_DIR $RUNNER_LOG_DIR \
|
|
||||||
"exec $RUNNER_SCRIPT_DIR/$SCRIPT console" 2>&1
|
|
||||||
|
|
||||||
# Wait for the node to come up. We can't just ping it because
|
|
||||||
# distributed erlang comes up for a second before emqttd crashes
|
|
||||||
# (eg. in the case of an unwriteable disk). Once the node comes
|
|
||||||
# up we check for the node watcher process. If that's running
|
|
||||||
# then we assume things are good enough. This will at least let
|
|
||||||
# the user know when emqttd is crashing right after startup.
|
|
||||||
WAIT=${WAIT_FOR_ERLANG:-15}
|
|
||||||
while [ $WAIT -gt 0 ]; do
|
|
||||||
WAIT=`expr $WAIT - 1`
|
|
||||||
sleep 1
|
|
||||||
RES=`ping_node`
|
|
||||||
if [ "$?" -ne 0 ]; then
|
|
||||||
continue
|
|
||||||
fi
|
|
||||||
echo "emqttd is started successfully!"
|
|
||||||
exit 0
|
|
||||||
done
|
|
||||||
echo "emqttd failed to start within ${WAIT_FOR_ERLANG:-15} seconds,"
|
|
||||||
echo "see the output of 'emqttd console' for more information."
|
|
||||||
echo "If you want to wait longer, set the environment variable"
|
|
||||||
echo "WAIT_FOR_ERLANG to the number of seconds to wait."
|
|
||||||
exit 1
|
|
||||||
;;
|
|
||||||
|
|
||||||
stop)
|
|
||||||
UNAME_S=`uname -s`
|
|
||||||
case $UNAME_S in
|
|
||||||
Darwin)
|
|
||||||
# Make sure we explicitly set this because iTerm.app doesn't for
|
|
||||||
# some reason.
|
|
||||||
COMMAND_MODE=unix2003
|
|
||||||
esac
|
|
||||||
|
|
||||||
# Get the PID from nodetool
|
|
||||||
get_pid
|
|
||||||
GPR=$?
|
|
||||||
if [ "$GPR" -ne 0 ] || [ -z $PID ]; then
|
|
||||||
exit $GPR
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Tell nodetool to initiate a stop
|
|
||||||
$NODETOOL stop
|
|
||||||
ES=$?
|
|
||||||
if [ "$ES" -ne 0 ]; then
|
|
||||||
exit $ES
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Wait for the node to completely stop...
|
|
||||||
while `kill -s 0 $PID 2>/dev/null`;
|
|
||||||
do
|
|
||||||
sleep 1
|
|
||||||
done
|
|
||||||
;;
|
|
||||||
|
|
||||||
restart)
|
|
||||||
## Restart the VM without exiting the process
|
|
||||||
$NODETOOL restart
|
|
||||||
ES=$?
|
|
||||||
if [ "$ES" -ne 0 ]; then
|
|
||||||
exit $ES
|
|
||||||
fi
|
|
||||||
;;
|
|
||||||
|
|
||||||
reboot)
|
|
||||||
## Restart the VM completely (uses heart to restart it)
|
|
||||||
$NODETOOL reboot
|
|
||||||
ES=$?
|
|
||||||
if [ "$ES" -ne 0 ]; then
|
|
||||||
exit $ES
|
|
||||||
fi
|
|
||||||
;;
|
|
||||||
|
|
||||||
ping)
|
|
||||||
## See if the VM is alive
|
|
||||||
ping_node
|
|
||||||
ES=$?
|
|
||||||
if [ "$ES" -ne 0 ]; then
|
|
||||||
exit $ES
|
|
||||||
fi
|
|
||||||
;;
|
|
||||||
|
|
||||||
attach)
|
|
||||||
if [ "$2" = "-f" ]; then
|
|
||||||
echo "Forcing connection..."
|
|
||||||
else
|
|
||||||
# Make sure a node is running
|
|
||||||
RES=`ping_node`
|
|
||||||
ES=$?
|
|
||||||
if [ "$ES" -ne 0 ]; then
|
|
||||||
echo "Node is not running!"
|
|
||||||
exit $ES
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
|
|
||||||
shift
|
|
||||||
exec $ERTS_PATH/to_erl $PIPE_DIR
|
|
||||||
;;
|
|
||||||
|
|
||||||
console)
|
|
||||||
RES=`ping_node`
|
|
||||||
if [ "$RES" = "pong" ]; then
|
|
||||||
echo "Node is already running - use '$SCRIPT attach' instead"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
# Sanity check the emqttd.config file
|
|
||||||
RES=`$NODETOOL_LITE chkconfig $RUNNER_ETC_DIR/emqttd.config`
|
|
||||||
if [ $? != 0 ]; then
|
|
||||||
echo "Error reading $RUNNER_ETC_DIR/emqttd.config"
|
|
||||||
echo $RES
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
# Setup beam-required vars
|
|
||||||
ROOTDIR=$RUNNER_BASE_DIR
|
|
||||||
ERL_LIBS=$ROOTDIR/plugins
|
|
||||||
BINDIR=$ROOTDIR/erts-$ERTS_VSN/bin
|
|
||||||
EMU=beam
|
|
||||||
PROGNAME=`echo $0 | sed 's/.*\///'`
|
|
||||||
# Setup Mnesia Dir
|
|
||||||
MNESIA_DIR="$RUNNER_DATA_DIR/mnesia/$NODE_NAME"
|
|
||||||
CMD="$BINDIR/erlexec -boot $RUNNER_BASE_DIR/releases/$APP_VSN/$SCRIPT \
|
|
||||||
-embedded -config $RUNNER_ETC_DIR/emqttd.config \
|
|
||||||
-pa $RUNNER_LIB_DIR/basho-patches \
|
|
||||||
-mnesia dir "\"${MNESIA_DIR}\"" \
|
|
||||||
-args_file $RUNNER_ETC_DIR/vm.args -- ${1+"$@"}"
|
|
||||||
export EMU
|
|
||||||
export ROOTDIR
|
|
||||||
export ERL_LIBS
|
|
||||||
export BINDIR
|
|
||||||
export PROGNAME
|
|
||||||
|
|
||||||
# Dump environment info for logging purposes
|
|
||||||
echo "Exec: $CMD"
|
|
||||||
echo "Root: $ROOTDIR"
|
|
||||||
|
|
||||||
# Log the startup
|
|
||||||
logger -t "$SCRIPT[$$]" "Starting up"
|
|
||||||
|
|
||||||
# Start the VM
|
|
||||||
exec $CMD
|
|
||||||
;;
|
|
||||||
chkconfig)
|
|
||||||
RES=`$NODETOOL_LITE chkconfig $RUNNER_ETC_DIR/emqttd.config`
|
|
||||||
if [ $? != 0 ]; then
|
|
||||||
echo "Error reading $RUNNER_ETC_DIR/emqttd.config"
|
|
||||||
echo $RES
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
echo "config is OK"
|
|
||||||
;;
|
|
||||||
escript)
|
|
||||||
shift
|
|
||||||
$ERTS_PATH/escript "$@"
|
|
||||||
;;
|
|
||||||
version)
|
|
||||||
echo $RIAK_VERSION
|
|
||||||
;;
|
|
||||||
getpid)
|
|
||||||
# Get the PID from nodetool
|
|
||||||
get_pid
|
|
||||||
ES=$?
|
|
||||||
if [ "$ES" -ne 0 ] || [ -z $PID ]; then
|
|
||||||
exit $ES
|
|
||||||
fi
|
|
||||||
echo $PID
|
|
||||||
;;
|
|
||||||
*)
|
|
||||||
echo "Usage: $SCRIPT {start|stop|restart|reboot|ping|console|attach|chkconfig|escript|version|getpid}"
|
|
||||||
exit 1
|
|
||||||
;;
|
|
||||||
esac
|
|
||||||
|
|
||||||
exit 0
|
|
|
@ -1,108 +0,0 @@
|
||||||
@echo off
|
|
||||||
@setlocal
|
|
||||||
@setlocal enabledelayedexpansion
|
|
||||||
|
|
||||||
@set node_name=emqttd
|
|
||||||
|
|
||||||
@rem Get the absolute path to the parent directory,
|
|
||||||
@rem which is assumed to be the node root.
|
|
||||||
@for /F "delims=" %%I in ("%~dp0..") do @set node_root=%%~fI
|
|
||||||
|
|
||||||
@set releases_dir=%node_root%\releases
|
|
||||||
@set runner_etc_dir=%node_root%\etc
|
|
||||||
|
|
||||||
@rem Parse ERTS version and release version from start_erl.data
|
|
||||||
@for /F "usebackq tokens=1,2" %%I in ("%releases_dir%\start_erl.data") do @(
|
|
||||||
@call :set_trim erts_version %%I
|
|
||||||
@call :set_trim release_version %%J
|
|
||||||
)
|
|
||||||
|
|
||||||
@set vm_args=%runner_etc_dir%\vm.args
|
|
||||||
@set sys_config=%runner_etc_dir%\emqttd.config
|
|
||||||
@set node_boot_script=%releases_dir%\%release_version%\%node_name%
|
|
||||||
@set clean_boot_script=%releases_dir%\%release_version%\start_clean
|
|
||||||
|
|
||||||
@rem extract erlang cookie from vm.args
|
|
||||||
@for /f "usebackq tokens=1-2" %%I in (`findstr /b \-setcookie "%vm_args%"`) do @set erlang_cookie=%%J
|
|
||||||
|
|
||||||
@set erts_bin=%node_root%\erts-%erts_version%\bin
|
|
||||||
|
|
||||||
@set service_name=%node_name%_%release_version%
|
|
||||||
|
|
||||||
@set erlsrv="%erts_bin%\erlsrv.exe"
|
|
||||||
@set epmd="%erts_bin%\epmd.exe"
|
|
||||||
@set escript="%erts_bin%\escript.exe"
|
|
||||||
@set werl="%erts_bin%\werl.exe"
|
|
||||||
|
|
||||||
@if "%1"=="usage" @goto usage
|
|
||||||
@if "%1"=="install" @goto install
|
|
||||||
@if "%1"=="uninstall" @goto uninstall
|
|
||||||
@if "%1"=="start" @goto start
|
|
||||||
@if "%1"=="stop" @goto stop
|
|
||||||
@if "%1"=="restart" @call :stop && @goto start
|
|
||||||
@if "%1"=="console" @goto console
|
|
||||||
@if "%1"=="query" @goto query
|
|
||||||
@if "%1"=="attach" @goto attach
|
|
||||||
@if "%1"=="upgrade" @goto upgrade
|
|
||||||
@echo Unknown command: "%1"
|
|
||||||
|
|
||||||
:usage
|
|
||||||
@echo Usage: %~n0 [install^|uninstall^|start^|stop^|restart^|console^|query^|attach^|upgrade]
|
|
||||||
@goto :EOF
|
|
||||||
|
|
||||||
:install
|
|
||||||
@set description=Erlang node %node_name% in %node_root%
|
|
||||||
@set start_erl=%node_root%\bin\start_erl.cmd
|
|
||||||
@set args= ++ %node_name% ++ %node_root%
|
|
||||||
@%erlsrv% add %service_name% -c "%description%" -sname %node_name% -w "%node_root%" -m "%start_erl%" -args "%args%" -stopaction "init:stop()."
|
|
||||||
@goto :EOF
|
|
||||||
|
|
||||||
:uninstall
|
|
||||||
@%erlsrv% remove %service_name%
|
|
||||||
@%epmd% -kill
|
|
||||||
@goto :EOF
|
|
||||||
|
|
||||||
:start
|
|
||||||
@%erlsrv% start %service_name%
|
|
||||||
@goto :EOF
|
|
||||||
|
|
||||||
:stop
|
|
||||||
@%erlsrv% stop %service_name%
|
|
||||||
@goto :EOF
|
|
||||||
|
|
||||||
:console
|
|
||||||
set dest_path=%~dp0
|
|
||||||
cd /d !dest_path!..\plugins
|
|
||||||
set current_path=%cd%
|
|
||||||
set plugins=
|
|
||||||
for /d %%P in (*) do (
|
|
||||||
set "plugins=!plugins!"!current_path!\%%P\ebin" "
|
|
||||||
)
|
|
||||||
cd /d %node_root%
|
|
||||||
|
|
||||||
@start "%node_name% console" %werl% -boot "%node_boot_script%" -config "%sys_config%" -args_file "%vm_args%" -sname %node_name% -pa %plugins%
|
|
||||||
@goto :EOF
|
|
||||||
|
|
||||||
:query
|
|
||||||
@%erlsrv% list %service_name%
|
|
||||||
@exit %ERRORLEVEL%
|
|
||||||
@goto :EOF
|
|
||||||
|
|
||||||
:attach
|
|
||||||
@for /f "usebackq" %%I in (`hostname`) do @set hostname=%%I
|
|
||||||
start "%node_name% attach" %werl% -boot "%clean_boot_script%" -remsh %node_name%@%hostname% -sname console -setcookie %erlang_cookie%
|
|
||||||
@goto :EOF
|
|
||||||
|
|
||||||
:upgrade
|
|
||||||
@if "%2"=="" (
|
|
||||||
@echo Missing upgrade package argument
|
|
||||||
@echo Usage: %~n0 upgrade {package base name}
|
|
||||||
@echo NOTE {package base name} MUST NOT include the .tar.gz suffix
|
|
||||||
@goto :EOF
|
|
||||||
)
|
|
||||||
@%escript% %node_root%\bin\install_upgrade.escript %node_name% %erlang_cookie% %2
|
|
||||||
@goto :EOF
|
|
||||||
|
|
||||||
:set_trim
|
|
||||||
@set %1=%2
|
|
||||||
@goto :EOF
|
|
|
@ -1,303 +0,0 @@
|
||||||
% -*- mode: erlang;erlang-indent-level: 4;indent-tabs-mode: nil -*-
|
|
||||||
%% ex: ft=erlang ts=4 sw=4 et
|
|
||||||
[{kernel, [
|
|
||||||
{start_timer, true},
|
|
||||||
{start_pg2, true}
|
|
||||||
]},
|
|
||||||
{sasl, [
|
|
||||||
{sasl_error_logger, {file, "log/emqttd_sasl.log"}}
|
|
||||||
]},
|
|
||||||
{ssl, [
|
|
||||||
%{versions, ['tlsv1.2', 'tlsv1.1']}
|
|
||||||
]},
|
|
||||||
{lager, [
|
|
||||||
{colored, true},
|
|
||||||
{async_threshold, 1000},
|
|
||||||
{error_logger_redirect, false},
|
|
||||||
{crash_log, "log/emqttd_crash.log"},
|
|
||||||
{handlers, [
|
|
||||||
{lager_console_backend, info},
|
|
||||||
%%NOTICE: Level >= error
|
|
||||||
%%{lager_emqtt_backend, error},
|
|
||||||
{lager_file_backend, [
|
|
||||||
{formatter_config, [time, " ", pid, " [",severity,"] ", message, "\n"]},
|
|
||||||
{file, "log/emqttd_info.log"},
|
|
||||||
{level, info},
|
|
||||||
{size, 104857600},
|
|
||||||
{date, "$D0"},
|
|
||||||
{count, 30}
|
|
||||||
]},
|
|
||||||
{lager_file_backend, [
|
|
||||||
{formatter_config, [time, " ", pid, " [",severity,"] ", message, "\n"]},
|
|
||||||
{file, "log/emqttd_error.log"},
|
|
||||||
{level, error},
|
|
||||||
{size, 104857600},
|
|
||||||
{date, "$D0"},
|
|
||||||
{count, 30}
|
|
||||||
]}
|
|
||||||
]}
|
|
||||||
]},
|
|
||||||
{esockd, [
|
|
||||||
{logger, {lager, info}}
|
|
||||||
]},
|
|
||||||
{emqttd, [
|
|
||||||
%% Authentication and Authorization
|
|
||||||
{access, [
|
|
||||||
%% Authetication. Anonymous Default
|
|
||||||
{auth, [
|
|
||||||
%% Authentication with username, password
|
|
||||||
%{username, []},
|
|
||||||
|
|
||||||
%% Authentication with clientid
|
|
||||||
%{clientid, [{password, no}, {file, "etc/clients.config"}]},
|
|
||||||
|
|
||||||
%% Authentication with LDAP
|
|
||||||
% {ldap, [
|
|
||||||
% {servers, ["localhost"]},
|
|
||||||
% {port, 389},
|
|
||||||
% {timeout, 30},
|
|
||||||
% {user_dn, "uid=$u,ou=People,dc=example,dc=com"},
|
|
||||||
% {ssl, fasle},
|
|
||||||
% {sslopts, [
|
|
||||||
% {certfile, "ssl.crt"},
|
|
||||||
% {keyfile, "ssl.key"}]}
|
|
||||||
% ]},
|
|
||||||
|
|
||||||
%% Allow all
|
|
||||||
{anonymous, []}
|
|
||||||
]},
|
|
||||||
%% ACL config
|
|
||||||
{acl, [
|
|
||||||
%% Internal ACL module
|
|
||||||
{internal, [{file, "etc/acl.config"}, {nomatch, allow}]}
|
|
||||||
]}
|
|
||||||
]},
|
|
||||||
%% MQTT Protocol Options
|
|
||||||
{mqtt, [
|
|
||||||
%% Packet
|
|
||||||
{packet, [
|
|
||||||
%% Max ClientId Length Allowed
|
|
||||||
{max_clientid_len, 1024},
|
|
||||||
%% Max Packet Size Allowed, 64K default
|
|
||||||
{max_packet_size, 65536}
|
|
||||||
]},
|
|
||||||
%% Client
|
|
||||||
{client, [
|
|
||||||
%% Socket is connected, but no 'CONNECT' packet received
|
|
||||||
{idle_timeout, 10} %% seconds
|
|
||||||
]},
|
|
||||||
%% Session
|
|
||||||
{session, [
|
|
||||||
%% Max number of QoS 1 and 2 messages that can be “in flight” at one time.
|
|
||||||
%% 0 means no limit
|
|
||||||
{max_inflight, 100},
|
|
||||||
|
|
||||||
%% Retry interval for redelivering QoS1/2 messages.
|
|
||||||
{unack_retry_interval, 20},
|
|
||||||
|
|
||||||
%% Awaiting PUBREL Timeout
|
|
||||||
{await_rel_timeout, 20},
|
|
||||||
|
|
||||||
%% Max Packets that Awaiting PUBREL, 0 means no limit
|
|
||||||
{max_awaiting_rel, 0},
|
|
||||||
|
|
||||||
%% Statistics Collection Interval(seconds)
|
|
||||||
{collect_interval, 20},
|
|
||||||
|
|
||||||
%% Expired after 2 day (unit: minute)
|
|
||||||
{expired_after, 2880}
|
|
||||||
|
|
||||||
]},
|
|
||||||
%% Queue
|
|
||||||
{queue, [
|
|
||||||
%% simple | priority
|
|
||||||
{type, simple},
|
|
||||||
|
|
||||||
%% Topic Priority: 0~255, Default is 0
|
|
||||||
%% {priority, [{"topic/1", 10}, {"topic/2", 8}]},
|
|
||||||
|
|
||||||
%% Max queue length. Enqueued messages when persistent client disconnected,
|
|
||||||
%% or inflight window is full.
|
|
||||||
{max_length, infinity},
|
|
||||||
|
|
||||||
%% Low-water mark of queued messages
|
|
||||||
{low_watermark, 0.2},
|
|
||||||
|
|
||||||
%% High-water mark of queued messages
|
|
||||||
{high_watermark, 0.6},
|
|
||||||
|
|
||||||
%% Queue Qos0 messages?
|
|
||||||
{queue_qos0, true}
|
|
||||||
]}
|
|
||||||
]},
|
|
||||||
%% Broker Options
|
|
||||||
{broker, [
|
|
||||||
%% System interval of publishing broker $SYS messages
|
|
||||||
{sys_interval, 60},
|
|
||||||
|
|
||||||
%% Retained messages
|
|
||||||
{retained, [
|
|
||||||
%% Expired after seconds, never expired if 0
|
|
||||||
{expired_after, 0},
|
|
||||||
|
|
||||||
%% Max number of retained messages
|
|
||||||
{max_message_num, 100000},
|
|
||||||
|
|
||||||
%% Max Payload Size of retained message
|
|
||||||
{max_playload_size, 65536}
|
|
||||||
]},
|
|
||||||
|
|
||||||
%% PubSub and Router
|
|
||||||
{pubsub, [
|
|
||||||
%% Default should be scheduler numbers
|
|
||||||
{pool_size, 8},
|
|
||||||
|
|
||||||
%% Store Subscription: true | false
|
|
||||||
{subscription, true},
|
|
||||||
|
|
||||||
%% Route aging time(seconds)
|
|
||||||
{route_aging, 5}
|
|
||||||
]},
|
|
||||||
|
|
||||||
%% Bridge
|
|
||||||
{bridge, [
|
|
||||||
%%TODO: bridge queue size
|
|
||||||
{max_queue_len, 10000},
|
|
||||||
|
|
||||||
%% Ping Interval of bridge node
|
|
||||||
{ping_down_interval, 1} %seconds
|
|
||||||
]}
|
|
||||||
]},
|
|
||||||
%% Modules
|
|
||||||
{modules, [
|
|
||||||
%% Client presence management module.
|
|
||||||
%% Publish messages when client connected or disconnected
|
|
||||||
{presence, [{qos, 0}]},
|
|
||||||
|
|
||||||
%% Subscribe topics automatically when client connected
|
|
||||||
{subscription, [
|
|
||||||
|
|
||||||
%% $c will be replaced by clientid
|
|
||||||
%% {"$queue/clients/$c", 1},
|
|
||||||
|
|
||||||
%% Static subscriptions from backend
|
|
||||||
backend
|
|
||||||
]}
|
|
||||||
|
|
||||||
%% Rewrite rules
|
|
||||||
%% {rewrite, [{file, "etc/rewrite.config"}]}
|
|
||||||
]},
|
|
||||||
%% Plugins
|
|
||||||
{plugins, [
|
|
||||||
%% Plugin App Library Dir
|
|
||||||
{plugins_dir, "./plugins"},
|
|
||||||
|
|
||||||
%% File to store loaded plugin names.
|
|
||||||
{loaded_file, "./data/loaded_plugins"}
|
|
||||||
]},
|
|
||||||
|
|
||||||
%% Listeners
|
|
||||||
{listeners, [
|
|
||||||
{mqtt, 1883, [
|
|
||||||
%% Size of acceptor pool
|
|
||||||
{acceptors, 16},
|
|
||||||
|
|
||||||
%% Maximum number of concurrent clients
|
|
||||||
{max_clients, 512},
|
|
||||||
|
|
||||||
%% Socket Access Control
|
|
||||||
{access, [{allow, all}]},
|
|
||||||
|
|
||||||
%% Connection Options
|
|
||||||
{connopts, [
|
|
||||||
%% Rate Limit. Format is 'burst, rate', Unit is KB/Sec
|
|
||||||
%% {rate_limit, "100,10"} %% 100K burst, 10K rate
|
|
||||||
]},
|
|
||||||
|
|
||||||
%% Socket Options
|
|
||||||
{sockopts, [
|
|
||||||
%Set buffer if hight thoughtput
|
|
||||||
%{recbuf, 4096},
|
|
||||||
%{sndbuf, 4096},
|
|
||||||
%{buffer, 4096},
|
|
||||||
%{nodelay, true},
|
|
||||||
{backlog, 512}
|
|
||||||
]}
|
|
||||||
]},
|
|
||||||
|
|
||||||
{mqtts, 8883, [
|
|
||||||
%% Size of acceptor pool
|
|
||||||
{acceptors, 4},
|
|
||||||
|
|
||||||
%% Maximum number of concurrent clients
|
|
||||||
{max_clients, 512},
|
|
||||||
|
|
||||||
%% Socket Access Control
|
|
||||||
{access, [{allow, all}]},
|
|
||||||
|
|
||||||
%% SSL certificate and key files
|
|
||||||
{ssl, [{certfile, "etc/ssl/ssl.crt"},
|
|
||||||
{keyfile, "etc/ssl/ssl.key"}]},
|
|
||||||
|
|
||||||
%% Socket Options
|
|
||||||
{sockopts, [
|
|
||||||
{backlog, 1024}
|
|
||||||
%{buffer, 4096},
|
|
||||||
]}
|
|
||||||
]},
|
|
||||||
%% WebSocket over HTTPS Listener
|
|
||||||
%% {https, 8083, [
|
|
||||||
%% %% Size of acceptor pool
|
|
||||||
%% {acceptors, 4},
|
|
||||||
%% %% Maximum number of concurrent clients
|
|
||||||
%% {max_clients, 512},
|
|
||||||
%% %% Socket Access Control
|
|
||||||
%% {access, [{allow, all}]},
|
|
||||||
%% %% SSL certificate and key files
|
|
||||||
%% {ssl, [{certfile, "etc/ssl/ssl.crt"},
|
|
||||||
%% {keyfile, "etc/ssl/ssl.key"}]},
|
|
||||||
%% %% Socket Options
|
|
||||||
%% {sockopts, [
|
|
||||||
%% %{buffer, 4096},
|
|
||||||
%% {backlog, 1024}
|
|
||||||
%% ]}
|
|
||||||
%%]},
|
|
||||||
|
|
||||||
%% HTTP and WebSocket Listener
|
|
||||||
{http, 8083, [
|
|
||||||
%% Size of acceptor pool
|
|
||||||
{acceptors, 4},
|
|
||||||
%% Maximum number of concurrent clients
|
|
||||||
{max_clients, 64},
|
|
||||||
%% Socket Access Control
|
|
||||||
{access, [{allow, all}]},
|
|
||||||
%% Socket Options
|
|
||||||
{sockopts, [
|
|
||||||
{backlog, 1024}
|
|
||||||
%{buffer, 4096},
|
|
||||||
]}
|
|
||||||
]}
|
|
||||||
]},
|
|
||||||
|
|
||||||
%% Erlang System Monitor
|
|
||||||
{sysmon, [
|
|
||||||
%% Long GC
|
|
||||||
{long_gc, 100},
|
|
||||||
|
|
||||||
%% Long Schedule(ms)
|
|
||||||
{long_schedule, 100},
|
|
||||||
|
|
||||||
%% 8M words. 32MB on 32-bit VM, 64MB on 64-bit VM.
|
|
||||||
%% 8 * 1024 * 1024
|
|
||||||
{large_heap, 8388608},
|
|
||||||
|
|
||||||
%% Busy Port
|
|
||||||
{busy_port, true},
|
|
||||||
|
|
||||||
%% Busy Dist Port
|
|
||||||
{busy_dist_port, true}
|
|
||||||
|
|
||||||
]}
|
|
||||||
]}
|
|
||||||
].
|
|
||||||
|
|
|
@ -1,296 +0,0 @@
|
||||||
% -*- mode: erlang;erlang-indent-level: 4;indent-tabs-mode: nil -*-
|
|
||||||
%% ex: ft=erlang ts=4 sw=4 et
|
|
||||||
[{kernel, [
|
|
||||||
{start_timer, true},
|
|
||||||
{start_pg2, true}
|
|
||||||
]},
|
|
||||||
{sasl, [
|
|
||||||
{sasl_error_logger, {file, "log/emqttd_sasl.log"}}
|
|
||||||
]},
|
|
||||||
{ssl, [
|
|
||||||
%{versions, ['tlsv1.2', 'tlsv1.1']}
|
|
||||||
]},
|
|
||||||
{lager, [
|
|
||||||
{colored, true},
|
|
||||||
{async_threshold, 5000},
|
|
||||||
{error_logger_redirect, false},
|
|
||||||
{crash_log, "log/emqttd_crash.log"},
|
|
||||||
{handlers, [
|
|
||||||
{lager_console_backend, error},
|
|
||||||
%%NOTICE: Level >= error
|
|
||||||
%%{lager_emqtt_backend, error},
|
|
||||||
{lager_file_backend, [
|
|
||||||
{formatter_config, [time, " ", pid, " [",severity,"] ", message, "\n"]},
|
|
||||||
{file, "log/emqttd_error.log"},
|
|
||||||
{level, error},
|
|
||||||
{size, 104857600},
|
|
||||||
{date, "$D0"},
|
|
||||||
{count, 30}
|
|
||||||
]}
|
|
||||||
]}
|
|
||||||
]},
|
|
||||||
{esockd, [
|
|
||||||
{logger, {lager, error}}
|
|
||||||
]},
|
|
||||||
{emqttd, [
|
|
||||||
%% Authentication and Authorization
|
|
||||||
{access, [
|
|
||||||
%% Authetication. Anonymous Default
|
|
||||||
{auth, [
|
|
||||||
%% Authentication with username, password
|
|
||||||
%{username, []},
|
|
||||||
|
|
||||||
%% Authentication with clientid
|
|
||||||
%{clientid, [{password, no}, {file, "etc/clients.config"}]},
|
|
||||||
|
|
||||||
%% Authentication with LDAP
|
|
||||||
% {ldap, [
|
|
||||||
% {servers, ["localhost"]},
|
|
||||||
% {port, 389},
|
|
||||||
% {timeout, 30},
|
|
||||||
% {user_dn, "uid=$u,ou=People,dc=example,dc=com"},
|
|
||||||
% {ssl, fasle},
|
|
||||||
% {sslopts, [
|
|
||||||
% {certfile, "ssl.crt"},
|
|
||||||
% {keyfile, "ssl.key"}]}
|
|
||||||
% ]},
|
|
||||||
|
|
||||||
%% Allow all
|
|
||||||
{anonymous, []}
|
|
||||||
]},
|
|
||||||
%% ACL config
|
|
||||||
{acl, [
|
|
||||||
%% Internal ACL module
|
|
||||||
{internal, [{file, "etc/acl.config"}, {nomatch, allow}]}
|
|
||||||
]}
|
|
||||||
]},
|
|
||||||
%% MQTT Protocol Options
|
|
||||||
{mqtt, [
|
|
||||||
%% Packet
|
|
||||||
{packet, [
|
|
||||||
%% Max ClientId Length Allowed
|
|
||||||
{max_clientid_len, 512},
|
|
||||||
%% Max Packet Size Allowed, 64K default
|
|
||||||
{max_packet_size, 65536}
|
|
||||||
]},
|
|
||||||
%% Client
|
|
||||||
{client, [
|
|
||||||
%% Socket is connected, but no 'CONNECT' packet received
|
|
||||||
{idle_timeout, 30} %% seconds
|
|
||||||
]},
|
|
||||||
%% Session
|
|
||||||
{session, [
|
|
||||||
%% Max number of QoS 1 and 2 messages that can be “in flight” at one time.
|
|
||||||
%% 0 means no limit
|
|
||||||
{max_inflight, 100},
|
|
||||||
|
|
||||||
%% Retry interval for redelivering QoS1/2 messages.
|
|
||||||
{unack_retry_interval, 60},
|
|
||||||
|
|
||||||
%% Awaiting PUBREL Timeout
|
|
||||||
{await_rel_timeout, 20},
|
|
||||||
|
|
||||||
%% Max Packets that Awaiting PUBREL, 0 means no limit
|
|
||||||
{max_awaiting_rel, 0},
|
|
||||||
|
|
||||||
%% Statistics Collection Interval(seconds)
|
|
||||||
{collect_interval, 0},
|
|
||||||
|
|
||||||
%% Expired after 2 day (unit: minute)
|
|
||||||
{expired_after, 2880}
|
|
||||||
|
|
||||||
]},
|
|
||||||
%% Queue
|
|
||||||
{queue, [
|
|
||||||
%% simple | priority
|
|
||||||
{type, simple},
|
|
||||||
|
|
||||||
%% Topic Priority: 0~255, Default is 0
|
|
||||||
%% {priority, [{"topic/1", 10}, {"topic/2", 8}]},
|
|
||||||
|
|
||||||
%% Max queue length. Enqueued messages when persistent client disconnected,
|
|
||||||
%% or inflight window is full.
|
|
||||||
{max_length, infinity},
|
|
||||||
|
|
||||||
%% Low-water mark of queued messages
|
|
||||||
{low_watermark, 0.2},
|
|
||||||
|
|
||||||
%% High-water mark of queued messages
|
|
||||||
{high_watermark, 0.6},
|
|
||||||
|
|
||||||
%% Queue Qos0 messages?
|
|
||||||
{queue_qos0, true}
|
|
||||||
]}
|
|
||||||
]},
|
|
||||||
%% Broker Options
|
|
||||||
{broker, [
|
|
||||||
%% System interval of publishing broker $SYS messages
|
|
||||||
{sys_interval, 60},
|
|
||||||
|
|
||||||
%% Retained messages
|
|
||||||
{retained, [
|
|
||||||
%% Expired after seconds, never expired if 0
|
|
||||||
{expired_after, 0},
|
|
||||||
|
|
||||||
%% Max number of retained messages
|
|
||||||
{max_message_num, 100000},
|
|
||||||
|
|
||||||
%% Max Payload Size of retained message
|
|
||||||
{max_playload_size, 65536}
|
|
||||||
]},
|
|
||||||
|
|
||||||
%% PubSub and Router
|
|
||||||
{pubsub, [
|
|
||||||
%% Default should be scheduler numbers
|
|
||||||
{pool_size, 8},
|
|
||||||
|
|
||||||
%% Store Subscription: true | false
|
|
||||||
{subscription, true},
|
|
||||||
|
|
||||||
%% Route aging time(seconds)
|
|
||||||
{route_aging, 5}
|
|
||||||
]},
|
|
||||||
|
|
||||||
%% Bridge
|
|
||||||
{bridge, [
|
|
||||||
%%TODO: bridge queue size
|
|
||||||
{max_queue_len, 10000},
|
|
||||||
|
|
||||||
%% Ping Interval of bridge node
|
|
||||||
{ping_down_interval, 1} %seconds
|
|
||||||
]}
|
|
||||||
]},
|
|
||||||
%% Modules
|
|
||||||
{modules, [
|
|
||||||
%% Client presence management module.
|
|
||||||
%% Publish messages when client connected or disconnected
|
|
||||||
{presence, [{qos, 0}]},
|
|
||||||
|
|
||||||
%% Subscribe topics automatically when client connected
|
|
||||||
{subscription, [
|
|
||||||
|
|
||||||
%% $c will be replaced by clientid
|
|
||||||
%% {"$queue/clients/$c", 1},
|
|
||||||
|
|
||||||
%% Static subscriptions from backend
|
|
||||||
backend
|
|
||||||
]}
|
|
||||||
|
|
||||||
%% Rewrite rules
|
|
||||||
%% {rewrite, [{file, "etc/rewrite.config"}]}
|
|
||||||
]},
|
|
||||||
%% Plugins
|
|
||||||
{plugins, [
|
|
||||||
%% Plugin App Library Dir
|
|
||||||
{plugins_dir, "./plugins"},
|
|
||||||
|
|
||||||
%% File to store loaded plugin names.
|
|
||||||
{loaded_file, "./data/loaded_plugins"}
|
|
||||||
]},
|
|
||||||
|
|
||||||
%% Listeners
|
|
||||||
{listeners, [
|
|
||||||
{mqtt, 1883, [
|
|
||||||
%% Size of acceptor pool
|
|
||||||
{acceptors, 16},
|
|
||||||
|
|
||||||
%% Maximum number of concurrent clients
|
|
||||||
{max_clients, 512},
|
|
||||||
|
|
||||||
%% Socket Access Control
|
|
||||||
{access, [{allow, all}]},
|
|
||||||
|
|
||||||
%% Connection Options
|
|
||||||
{connopts, [
|
|
||||||
%% Rate Limit. Format is 'burst, rate', Unit is KB/Sec
|
|
||||||
%% {rate_limit, "100,10"} %% 100K burst, 10K rate
|
|
||||||
]},
|
|
||||||
|
|
||||||
%% Socket Options
|
|
||||||
{sockopts, [
|
|
||||||
%Set buffer if hight thoughtput
|
|
||||||
%{recbuf, 4096},
|
|
||||||
%{sndbuf, 4096},
|
|
||||||
%{buffer, 4096},
|
|
||||||
%{nodelay, true},
|
|
||||||
{backlog, 1024}
|
|
||||||
]}
|
|
||||||
]},
|
|
||||||
|
|
||||||
{mqtts, 8883, [
|
|
||||||
%% Size of acceptor pool
|
|
||||||
{acceptors, 4},
|
|
||||||
|
|
||||||
%% Maximum number of concurrent clients
|
|
||||||
{max_clients, 512},
|
|
||||||
|
|
||||||
%% Socket Access Control
|
|
||||||
{access, [{allow, all}]},
|
|
||||||
|
|
||||||
%% SSL certificate and key files
|
|
||||||
{ssl, [{certfile, "etc/ssl/ssl.crt"},
|
|
||||||
{keyfile, "etc/ssl/ssl.key"}]},
|
|
||||||
|
|
||||||
%% Socket Options
|
|
||||||
{sockopts, [
|
|
||||||
{backlog, 1024}
|
|
||||||
%{buffer, 4096},
|
|
||||||
]}
|
|
||||||
]},
|
|
||||||
%% WebSocket over HTTPS Listener
|
|
||||||
%% {https, 8083, [
|
|
||||||
%% %% Size of acceptor pool
|
|
||||||
%% {acceptors, 4},
|
|
||||||
%% %% Maximum number of concurrent clients
|
|
||||||
%% {max_clients, 512},
|
|
||||||
%% %% Socket Access Control
|
|
||||||
%% {access, [{allow, all}]},
|
|
||||||
%% %% SSL certificate and key files
|
|
||||||
%% {ssl, [{certfile, "etc/ssl/ssl.crt"},
|
|
||||||
%% {keyfile, "etc/ssl/ssl.key"}]},
|
|
||||||
%% %% Socket Options
|
|
||||||
%% {sockopts, [
|
|
||||||
%% %{buffer, 4096},
|
|
||||||
%% {backlog, 1024}
|
|
||||||
%% ]}
|
|
||||||
%%]},
|
|
||||||
|
|
||||||
%% HTTP and WebSocket Listener
|
|
||||||
{http, 8083, [
|
|
||||||
%% Size of acceptor pool
|
|
||||||
{acceptors, 4},
|
|
||||||
%% Maximum number of concurrent clients
|
|
||||||
{max_clients, 64},
|
|
||||||
%% Socket Access Control
|
|
||||||
{access, [{allow, all}]},
|
|
||||||
%% Socket Options
|
|
||||||
{sockopts, [
|
|
||||||
{backlog, 1024}
|
|
||||||
%{buffer, 4096},
|
|
||||||
]}
|
|
||||||
]}
|
|
||||||
]},
|
|
||||||
|
|
||||||
%% Erlang System Monitor
|
|
||||||
{sysmon, [
|
|
||||||
%% Long GC, don't monitor in production mode for:
|
|
||||||
%% https://github.com/erlang/otp/blob/feb45017da36be78d4c5784d758ede619fa7bfd3/erts/emulator/beam/erl_gc.c#L421
|
|
||||||
{long_gc, false},
|
|
||||||
|
|
||||||
%% Long Schedule(ms)
|
|
||||||
{long_schedule, 240},
|
|
||||||
|
|
||||||
%% 8M words. 32MB on 32-bit VM, 64MB on 64-bit VM.
|
|
||||||
%% 8 * 1024 * 1024
|
|
||||||
{large_heap, 8388608},
|
|
||||||
|
|
||||||
%% Busy Port
|
|
||||||
{busy_port, false},
|
|
||||||
|
|
||||||
%% Busy Dist Port
|
|
||||||
{busy_dist_port, true}
|
|
||||||
|
|
||||||
]}
|
|
||||||
]}
|
|
||||||
].
|
|
||||||
|
|
|
@ -1,300 +0,0 @@
|
||||||
% -*- mode: erlang;erlang-indent-level: 4;indent-tabs-mode: nil -*-
|
|
||||||
%% ex: ft=erlang ts=4 sw=4 et
|
|
||||||
[{kernel, [
|
|
||||||
{start_timer, true},
|
|
||||||
{start_pg2, true}
|
|
||||||
]},
|
|
||||||
{sasl, [
|
|
||||||
{sasl_error_logger, {file, "emqttd_sasl.log"}}
|
|
||||||
]},
|
|
||||||
{ssl, [
|
|
||||||
%{versions, ['tlsv1.2', 'tlsv1.1']}
|
|
||||||
]},
|
|
||||||
{lager, [
|
|
||||||
{colored, true},
|
|
||||||
{async_threshold, 1000},
|
|
||||||
{error_logger_redirect, false},
|
|
||||||
{crash_log, "log/emqttd_crash.log"},
|
|
||||||
{handlers, [
|
|
||||||
{lager_console_backend, info},
|
|
||||||
%%NOTICE: Level >= error
|
|
||||||
%%{lager_emqtt_backend, error},
|
|
||||||
{lager_file_backend, [
|
|
||||||
{formatter_config, [time, " ", pid, " [",severity,"] ", message, "\n"]},
|
|
||||||
{file, "log/emqttd_info.log"},
|
|
||||||
{level, info},
|
|
||||||
{size, 104857600},
|
|
||||||
{date, "$D0"},
|
|
||||||
{count, 30}
|
|
||||||
]},
|
|
||||||
{lager_file_backend, [
|
|
||||||
{formatter_config, [time, " ", pid, " [",severity,"] ", message, "\n"]},
|
|
||||||
{file, "log/emqttd_error.log"},
|
|
||||||
{level, error},
|
|
||||||
{size, 104857600},
|
|
||||||
{date, "$D0"},
|
|
||||||
{count, 30}
|
|
||||||
]}
|
|
||||||
]}
|
|
||||||
]},
|
|
||||||
{esockd, [
|
|
||||||
{logger, {lager, info}}
|
|
||||||
]},
|
|
||||||
{emqttd, [
|
|
||||||
%% Authentication and Authorization
|
|
||||||
{access, [
|
|
||||||
%% Authetication. Anonymous Default
|
|
||||||
{auth, [
|
|
||||||
%% Authentication with username, password
|
|
||||||
%% {username, [{test, "password"}, {"test1", "password1"}]},
|
|
||||||
|
|
||||||
%% Authentication with clientid
|
|
||||||
%{clientid, [{password, no}, {file, "etc/clients.config"}]},
|
|
||||||
|
|
||||||
%% Authentication with LDAP
|
|
||||||
% {ldap, [
|
|
||||||
% {servers, ["localhost"]},
|
|
||||||
% {port, 389},
|
|
||||||
% {timeout, 30},
|
|
||||||
% {user_dn, "uid=$u,ou=People,dc=example,dc=com"},
|
|
||||||
% {ssl, fasle},
|
|
||||||
% {sslopts, [
|
|
||||||
% {"certfile", "ssl.crt"},
|
|
||||||
% {"keyfile", "ssl.key"}]}
|
|
||||||
% ]},
|
|
||||||
|
|
||||||
%% Allow all
|
|
||||||
{anonymous, []}
|
|
||||||
]},
|
|
||||||
%% ACL config
|
|
||||||
{acl, [
|
|
||||||
%% Internal ACL module
|
|
||||||
%% {internal, [{file, "testdata/test_acl.config"}, {nomatch, allow}]}
|
|
||||||
]}
|
|
||||||
]},
|
|
||||||
%% MQTT Protocol Options
|
|
||||||
{mqtt, [
|
|
||||||
%% Packet
|
|
||||||
{packet, [
|
|
||||||
%% Max ClientId Length Allowed
|
|
||||||
{max_clientid_len, 1024},
|
|
||||||
%% Max Packet Size Allowed, 64K default
|
|
||||||
{max_packet_size, 65536}
|
|
||||||
]},
|
|
||||||
%% Client
|
|
||||||
{client, [
|
|
||||||
%% Socket is connected, but no 'CONNECT' packet received
|
|
||||||
{idle_timeout, 10} %% seconds
|
|
||||||
]},
|
|
||||||
%% Session
|
|
||||||
{session, [
|
|
||||||
%% Max number of QoS 1 and 2 messages that can be “in flight” at one time.
|
|
||||||
%% 0 means no limit
|
|
||||||
{max_inflight, 100},
|
|
||||||
|
|
||||||
%% Retry interval for redelivering QoS1/2 messages.
|
|
||||||
{unack_retry_interval, 20},
|
|
||||||
|
|
||||||
%% Awaiting PUBREL Timeout
|
|
||||||
{await_rel_timeout, 20},
|
|
||||||
|
|
||||||
%% Max Packets that Awaiting PUBREL, 0 means no limit
|
|
||||||
{max_awaiting_rel, 0},
|
|
||||||
|
|
||||||
%% Statistics Collection Interval(seconds)
|
|
||||||
{collect_interval, 20},
|
|
||||||
|
|
||||||
%% Expired after 2 day (unit: minute)
|
|
||||||
{expired_after, 2880}
|
|
||||||
|
|
||||||
]},
|
|
||||||
%% Queue
|
|
||||||
{queue, [
|
|
||||||
%% simple | priority
|
|
||||||
{type, simple},
|
|
||||||
|
|
||||||
%% Topic Priority: 0~255, Default is 0
|
|
||||||
%% {priority, [{"topic/1", 10}, {"topic/2", 8}]},
|
|
||||||
|
|
||||||
%% Max queue length. Enqueued messages when persistent client disconnected,
|
|
||||||
%% or inflight window is full.
|
|
||||||
{max_length, infinity},
|
|
||||||
|
|
||||||
%% Low-water mark of queued messages
|
|
||||||
{low_watermark, 0.2},
|
|
||||||
|
|
||||||
%% High-water mark of queued messages
|
|
||||||
{high_watermark, 0.6},
|
|
||||||
|
|
||||||
%% Queue Qos0 messages?
|
|
||||||
{queue_qos0, true}
|
|
||||||
]}
|
|
||||||
]},
|
|
||||||
%% Broker Options
|
|
||||||
{broker, [
|
|
||||||
%% System interval of publishing broker $SYS messages
|
|
||||||
{sys_interval, 60},
|
|
||||||
|
|
||||||
%% Retained messages
|
|
||||||
{retained, [
|
|
||||||
%% Expired after seconds, never expired if 0
|
|
||||||
{expired_after, 0},
|
|
||||||
|
|
||||||
%% Max number of retained messages
|
|
||||||
{max_message_num, 100000},
|
|
||||||
|
|
||||||
%% Max Payload Size of retained message
|
|
||||||
{max_playload_size, 65536}
|
|
||||||
]},
|
|
||||||
|
|
||||||
%% PubSub and Router
|
|
||||||
{pubsub, [
|
|
||||||
%% Default should be scheduler numbers
|
|
||||||
{pool_size, 8},
|
|
||||||
|
|
||||||
%% Route aging time(seconds)
|
|
||||||
{route_aging, 5}
|
|
||||||
]},
|
|
||||||
|
|
||||||
%% Bridge
|
|
||||||
{bridge, [
|
|
||||||
%%TODO: bridge queue size
|
|
||||||
{max_queue_len, 10000},
|
|
||||||
|
|
||||||
%% Ping Interval of bridge node
|
|
||||||
{ping_down_interval, 1} %seconds
|
|
||||||
]}
|
|
||||||
]},
|
|
||||||
%% Modules
|
|
||||||
{modules, [
|
|
||||||
%% Client presence management module.
|
|
||||||
%% Publish messages when client connected or disconnected
|
|
||||||
{presence, [{qos, 0}]},
|
|
||||||
|
|
||||||
%% Subscribe topics automatically when client connected
|
|
||||||
{subscription, [
|
|
||||||
|
|
||||||
%% $c will be replaced by clientid
|
|
||||||
%% {"$queue/clients/$c", 1},
|
|
||||||
|
|
||||||
%% Static subscriptions from backend
|
|
||||||
backend
|
|
||||||
]}
|
|
||||||
|
|
||||||
%% Rewrite rules
|
|
||||||
%% {rewrite, [{file, "etc/rewrite.config"}]}
|
|
||||||
]},
|
|
||||||
%% Plugins
|
|
||||||
{plugins, [
|
|
||||||
%% Plugin App Library Dir
|
|
||||||
{plugins_dir, "./plugins"},
|
|
||||||
|
|
||||||
%% File to store loaded plugin names.
|
|
||||||
{loaded_file, "./data/loaded_plugins"}
|
|
||||||
]},
|
|
||||||
|
|
||||||
%% Listeners
|
|
||||||
{listeners, [
|
|
||||||
{mqtt, 1883, [
|
|
||||||
%% Size of acceptor pool
|
|
||||||
{acceptors, 16},
|
|
||||||
|
|
||||||
%% Maximum number of concurrent clients
|
|
||||||
{max_clients, 512},
|
|
||||||
|
|
||||||
%% Socket Access Control
|
|
||||||
{access, [{allow, all}]},
|
|
||||||
|
|
||||||
%% Connection Options
|
|
||||||
{connopts, [
|
|
||||||
%% Rate Limit. Format is 'burst, rate', Unit is KB/Sec
|
|
||||||
%% {rate_limit, "100,10"} %% 100K burst, 10K rate
|
|
||||||
]},
|
|
||||||
|
|
||||||
%% Socket Options
|
|
||||||
{sockopts, [
|
|
||||||
%Set buffer if hight thoughtput
|
|
||||||
%{recbuf, 4096},
|
|
||||||
%{sndbuf, 4096},
|
|
||||||
%{buffer, 4096},
|
|
||||||
%{nodelay, true},
|
|
||||||
{backlog, 512}
|
|
||||||
]}
|
|
||||||
]},
|
|
||||||
|
|
||||||
{mqtts, 8883, [
|
|
||||||
%% Size of acceptor pool
|
|
||||||
{acceptors, 4},
|
|
||||||
|
|
||||||
%% Maximum number of concurrent clients
|
|
||||||
{max_clients, 512},
|
|
||||||
|
|
||||||
%% Socket Access Control
|
|
||||||
{access, [{allow, all}]},
|
|
||||||
|
|
||||||
%% SSL certificate and key files
|
|
||||||
{ssl, [{certfile, "etc/ssl/ssl.crt"},
|
|
||||||
{keyfile, "etc/ssl/ssl.key"}]},
|
|
||||||
|
|
||||||
%% Socket Options
|
|
||||||
{sockopts, [
|
|
||||||
{backlog, 1024}
|
|
||||||
%{buffer, 4096},
|
|
||||||
]}
|
|
||||||
]},
|
|
||||||
%% WebSocket over HTTPS Listener
|
|
||||||
%% {https, 8083, [
|
|
||||||
%% %% Size of acceptor pool
|
|
||||||
%% {acceptors, 4},
|
|
||||||
%% %% Maximum number of concurrent clients
|
|
||||||
%% {max_clients, 512},
|
|
||||||
%% %% Socket Access Control
|
|
||||||
%% {access, [{allow, all}]},
|
|
||||||
%% %% SSL certificate and key files
|
|
||||||
%% {ssl, [{certfile, "etc/ssl/ssl.crt"},
|
|
||||||
%% {keyfile, "etc/ssl/ssl.key"}]},
|
|
||||||
%% %% Socket Options
|
|
||||||
%% {sockopts, [
|
|
||||||
%% %{buffer, 4096},
|
|
||||||
%% {backlog, 1024}
|
|
||||||
%% ]}
|
|
||||||
%%]},
|
|
||||||
|
|
||||||
%% HTTP and WebSocket Listener
|
|
||||||
{http, 8083, [
|
|
||||||
%% Size of acceptor pool
|
|
||||||
{acceptors, 4},
|
|
||||||
%% Maximum number of concurrent clients
|
|
||||||
{max_clients, 64},
|
|
||||||
%% Socket Access Control
|
|
||||||
{access, [{allow, all}]},
|
|
||||||
%% Socket Options
|
|
||||||
{sockopts, [
|
|
||||||
{backlog, 1024}
|
|
||||||
%{buffer, 4096},
|
|
||||||
]}
|
|
||||||
]}
|
|
||||||
]},
|
|
||||||
|
|
||||||
%% Erlang System Monitor
|
|
||||||
{sysmon, [
|
|
||||||
%% Long GC
|
|
||||||
{long_gc, 100},
|
|
||||||
|
|
||||||
%% Long Schedule(ms)
|
|
||||||
{long_schedule, 100},
|
|
||||||
|
|
||||||
%% 8M words. 32MB on 32-bit VM, 64MB on 64-bit VM.
|
|
||||||
%% 8 * 1024 * 1024
|
|
||||||
{large_heap, 8388608},
|
|
||||||
|
|
||||||
%% Busy Port
|
|
||||||
{busy_port, true},
|
|
||||||
|
|
||||||
%% Busy Dist Port
|
|
||||||
{busy_dist_port, true}
|
|
||||||
|
|
||||||
]}
|
|
||||||
]}
|
|
||||||
].
|
|
||||||
|
|
|
@ -1,91 +0,0 @@
|
||||||
#!/bin/sh
|
|
||||||
# -*- tab-width:4;indent-tabs-mode:nil -*-
|
|
||||||
# ex: ts=4 sw=4 et
|
|
||||||
|
|
||||||
# /bin/sh on Solaris is not a POSIX compatible shell, but /usr/bin/ksh is.
|
|
||||||
if [ `uname -s` = 'SunOS' -a "${POSIX_SHELL}" != "true" ]; then
|
|
||||||
POSIX_SHELL="true"
|
|
||||||
export POSIX_SHELL
|
|
||||||
# To support 'whoami' add /usr/ucb to path
|
|
||||||
PATH=/usr/ucb:$PATH
|
|
||||||
export PATH
|
|
||||||
exec /usr/bin/ksh $0 "$@"
|
|
||||||
fi
|
|
||||||
unset POSIX_SHELL # clear it so if we invoke other scripts, they run as ksh as well
|
|
||||||
|
|
||||||
RUNNER_SCRIPT_DIR={{runner_script_dir}}
|
|
||||||
RUNNER_SCRIPT=${0##*/}
|
|
||||||
|
|
||||||
RUNNER_BASE_DIR={{runner_base_dir}}
|
|
||||||
RUNNER_ETC_DIR={{runner_etc_dir}}
|
|
||||||
RUNNER_LIB_DIR={{platform_lib_dir}}
|
|
||||||
RUNNER_LOG_DIR={{runner_log_dir}}
|
|
||||||
RUNNER_USER={{runner_user}}
|
|
||||||
|
|
||||||
WHOAMI=$(whoami)
|
|
||||||
|
|
||||||
# Make sure this script is running as the appropriate user
|
|
||||||
if ([ "$RUNNER_USER" ] && [ "x$WHOAMI" != "x$RUNNER_USER" ]); then
|
|
||||||
type sudo > /dev/null 2>&1
|
|
||||||
if [ $? -ne 0 ]; then
|
|
||||||
echo "sudo doesn't appear to be installed and your EUID isn't $RUNNER_USER" 1>&2
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
echo "Attempting to restart script through sudo -H -u $RUNNER_USER" >&2
|
|
||||||
exec sudo -H -u $RUNNER_USER -i $RUNNER_SCRIPT_DIR/$RUNNER_SCRIPT $@
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Make sure CWD is set to runner base dir
|
|
||||||
cd $RUNNER_BASE_DIR
|
|
||||||
|
|
||||||
# Extract the target node name from node.args
|
|
||||||
NAME_ARG=`egrep "^ *-s?name" $RUNNER_ETC_DIR/vm.args`
|
|
||||||
if [ -z "$NAME_ARG" ]; then
|
|
||||||
echo "vm.args needs to have either -name or -sname parameter."
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Learn how to specify node name for connection from remote nodes
|
|
||||||
echo "$NAME_ARG" | grep '^-sname' > /dev/null 2>&1
|
|
||||||
if [ "X$?" = "X0" ]; then
|
|
||||||
NAME_PARAM="-sname"
|
|
||||||
NAME_HOST=""
|
|
||||||
else
|
|
||||||
NAME_PARAM="-name"
|
|
||||||
echo "$NAME_ARG" | grep '@.*' > /dev/null 2>&1
|
|
||||||
if [ "X$?" = "X0" ]; then
|
|
||||||
NAME_HOST=`echo "${NAME_ARG}" | sed -e 's/.*\(@.*\)$/\1/'`
|
|
||||||
else
|
|
||||||
NAME_HOST=""
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Extract the target cookie
|
|
||||||
COOKIE_ARG=`grep '\-setcookie' $RUNNER_ETC_DIR/vm.args`
|
|
||||||
if [ -z "$COOKIE_ARG" ]; then
|
|
||||||
echo "vm.args needs to have a -setcookie parameter."
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Identify the script name
|
|
||||||
SCRIPT=`basename $0`
|
|
||||||
|
|
||||||
# Parse out release and erts info
|
|
||||||
START_ERL=`cat $RUNNER_BASE_DIR/releases/start_erl.data`
|
|
||||||
ERTS_VSN=${START_ERL% *}
|
|
||||||
APP_VSN=${START_ERL#* }
|
|
||||||
|
|
||||||
# Add ERTS bin dir to our path
|
|
||||||
ERTS_PATH=$RUNNER_BASE_DIR/erts-$ERTS_VSN/bin
|
|
||||||
|
|
||||||
# Setup command to control the node
|
|
||||||
NODETOOL="$ERTS_PATH/escript $ERTS_PATH/nodetool $NAME_ARG $COOKIE_ARG"
|
|
||||||
|
|
||||||
RES=`$NODETOOL ping`
|
|
||||||
if [ "$RES" != "pong" ]; then
|
|
||||||
echo "Node is not running!"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
$NODETOOL rpc emqttd_ctl run $@
|
|
||||||
|
|
|
@ -1,117 +0,0 @@
|
||||||
#!/bin/sh
|
|
||||||
# -*- tab-width:4;indent-tabs-mode:nil -*-
|
|
||||||
# ex: ts=4 sw=4 et
|
|
||||||
|
|
||||||
# /bin/sh on Solaris is not a POSIX compatible shell, but /usr/bin/ksh is.
|
|
||||||
if [ `uname -s` = 'SunOS' -a "${POSIX_SHELL}" != "true" ]; then
|
|
||||||
POSIX_SHELL="true"
|
|
||||||
export POSIX_SHELL
|
|
||||||
# To support 'whoami' add /usr/ucb to path
|
|
||||||
PATH=/usr/ucb:$PATH
|
|
||||||
export PATH
|
|
||||||
exec /usr/bin/ksh $0 "$@"
|
|
||||||
fi
|
|
||||||
unset POSIX_SHELL # clear it so if we invoke other scripts, they run as ksh as well
|
|
||||||
|
|
||||||
RUNNER_SCRIPT_DIR={{runner_script_dir}}
|
|
||||||
RUNNER_SCRIPT=${0##*/}
|
|
||||||
|
|
||||||
RUNNER_BASE_DIR={{runner_base_dir}}
|
|
||||||
RUNNER_ETC_DIR={{runner_etc_dir}}
|
|
||||||
RUNNER_LIB_DIR={{platform_lib_dir}}
|
|
||||||
RUNNER_USER={{runner_user}}
|
|
||||||
|
|
||||||
WHOAMI=$(whoami)
|
|
||||||
|
|
||||||
# Make sure this script is running as the appropriate user
|
|
||||||
if ([ "$RUNNER_USER" ] && [ "x$WHOAMI" != "x$RUNNER_USER" ]); then
|
|
||||||
type sudo > /dev/null 2>&1
|
|
||||||
if [ $? -ne 0 ]; then
|
|
||||||
echo "sudo doesn't appear to be installed and your EUID isn't $RUNNER_USER" 1>&2
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
echo "Attempting to restart script through sudo -H -u $RUNNER_USER" >&2
|
|
||||||
exec sudo -H -u $RUNNER_USER -i $RUNNER_SCRIPT_DIR/$RUNNER_SCRIPT $@
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Make sure CWD is set to runner base dir
|
|
||||||
cd $RUNNER_BASE_DIR
|
|
||||||
|
|
||||||
# Extract the target node name from node.args
|
|
||||||
NAME_ARG=`egrep "^ *-s?name" $RUNNER_ETC_DIR/vm.args`
|
|
||||||
if [ -z "$NAME_ARG" ]; then
|
|
||||||
echo "vm.args needs to have either -name or -sname parameter."
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Learn how to specify node name for connection from remote nodes
|
|
||||||
echo "$NAME_ARG" | grep '^-sname' > /dev/null 2>&1
|
|
||||||
if [ "X$?" = "X0" ]; then
|
|
||||||
NAME_PARAM="-sname"
|
|
||||||
NAME_HOST=""
|
|
||||||
else
|
|
||||||
NAME_PARAM="-name"
|
|
||||||
echo "$NAME_ARG" | grep '@.*' > /dev/null 2>&1
|
|
||||||
if [ "X$?" = "X0" ]; then
|
|
||||||
NAME_HOST=`echo "${NAME_ARG}" | sed -e 's/.*\(@.*\)$/\1/'`
|
|
||||||
else
|
|
||||||
NAME_HOST=""
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Extract the target cookie
|
|
||||||
COOKIE_ARG=`grep '\-setcookie' $RUNNER_ETC_DIR/vm.args`
|
|
||||||
if [ -z "$COOKIE_ARG" ]; then
|
|
||||||
echo "vm.args needs to have a -setcookie parameter."
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Identify the script name
|
|
||||||
SCRIPT=`basename $0`
|
|
||||||
|
|
||||||
# Parse out release and erts info
|
|
||||||
START_ERL=`cat $RUNNER_BASE_DIR/releases/start_erl.data`
|
|
||||||
ERTS_VSN=${START_ERL% *}
|
|
||||||
APP_VSN=${START_ERL#* }
|
|
||||||
|
|
||||||
# Add ERTS bin dir to our path
|
|
||||||
ERTS_PATH=$RUNNER_BASE_DIR/erts-$ERTS_VSN/bin
|
|
||||||
|
|
||||||
NODE_NAME=${NAME_ARG#* }
|
|
||||||
|
|
||||||
# Setup command to control the node
|
|
||||||
NODETOOL="$ERTS_PATH/escript $ERTS_PATH/nodetool $NAME_ARG $COOKIE_ARG"
|
|
||||||
|
|
||||||
RES=`$NODETOOL ping`
|
|
||||||
if [ "$RES" != "pong" ]; then
|
|
||||||
echo "Node is not running!"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
case "$1" in
|
|
||||||
runtime)
|
|
||||||
SORTBY="runtime"
|
|
||||||
;;
|
|
||||||
reductions)
|
|
||||||
SORTBY="reductions"
|
|
||||||
;;
|
|
||||||
memory)
|
|
||||||
SORTBY="memory"
|
|
||||||
;;
|
|
||||||
msg_q)
|
|
||||||
SORTBY="msg_q"
|
|
||||||
;;
|
|
||||||
*)
|
|
||||||
echo "Usage: $SCRIPT {runtime | reductions | memory | msg_q}"
|
|
||||||
exit 1
|
|
||||||
;;
|
|
||||||
esac
|
|
||||||
|
|
||||||
MYPID=$$
|
|
||||||
ETOP_ARGS="-sort $SORTBY -interval 10 -lines 50 -tracing off"
|
|
||||||
$ERTS_PATH/erl -noshell -noinput \
|
|
||||||
-pa $RUNNER_LIB_DIR/basho-patches \
|
|
||||||
-hidden $NAME_PARAM emqttd_top$MYPID$NAME_HOST $COOKIE_ARG \
|
|
||||||
-s etop -s erlang halt -output text \
|
|
||||||
-node $NODE_NAME $ETOP_ARGS
|
|
||||||
|
|
|
@ -1,34 +0,0 @@
|
||||||
#!/bin/sh
|
|
||||||
|
|
||||||
## This script replaces the default "erl" in erts-VSN/bin. This is necessary
|
|
||||||
## as escript depends on erl and in turn, erl depends on having access to a
|
|
||||||
## bootscript (start.boot). Note that this script is ONLY invoked as a side-effect
|
|
||||||
## of running escript -- the embedded node bypasses erl and uses erlexec directly
|
|
||||||
## (as it should).
|
|
||||||
##
|
|
||||||
## Note that this script makes the assumption that there is a start_clean.boot
|
|
||||||
## file available in $ROOTDIR/release/VSN.
|
|
||||||
|
|
||||||
# Determine the abspath of where this script is executing from.
|
|
||||||
ERTS_BIN_DIR=$(cd ${0%/*} && pwd)
|
|
||||||
|
|
||||||
# Now determine the root directory -- this script runs from erts-VSN/bin,
|
|
||||||
# so we simply need to strip off two dirs from the end of the ERTS_BIN_DIR
|
|
||||||
# path.
|
|
||||||
ROOTDIR=${ERTS_BIN_DIR%/*/*}
|
|
||||||
|
|
||||||
# Parse out release and erts info
|
|
||||||
START_ERL=`cat $ROOTDIR/releases/start_erl.data`
|
|
||||||
ERTS_VSN=${START_ERL% *}
|
|
||||||
APP_VSN=${START_ERL#* }
|
|
||||||
|
|
||||||
BINDIR=$ROOTDIR/erts-$ERTS_VSN/bin
|
|
||||||
EMU=beam
|
|
||||||
PROGNAME=`echo $0 | sed 's/.*\\///'`
|
|
||||||
CMD="$BINDIR/erlexec"
|
|
||||||
export EMU
|
|
||||||
export ROOTDIR
|
|
||||||
export BINDIR
|
|
||||||
export PROGNAME
|
|
||||||
|
|
||||||
exec $CMD -boot $ROOTDIR/releases/$APP_VSN/start_clean ${1+"$@"}
|
|
|
@ -1,44 +0,0 @@
|
||||||
#!/usr/bin/env escript
|
|
||||||
%%! -noshell -noinput
|
|
||||||
%% -*- mode: erlang;erlang-indent-level: 4;indent-tabs-mode: nil -*-
|
|
||||||
%% ex: ft=erlang ts=4 sw=4 et
|
|
||||||
|
|
||||||
-define(TIMEOUT, 60000).
|
|
||||||
-define(INFO(Fmt,Args), io:format(Fmt,Args)).
|
|
||||||
|
|
||||||
main([NodeName, Cookie, ReleasePackage]) ->
|
|
||||||
TargetNode = start_distribution(NodeName, Cookie),
|
|
||||||
{ok, Vsn} = rpc:call(TargetNode, release_handler, unpack_release,
|
|
||||||
[ReleasePackage], ?TIMEOUT),
|
|
||||||
?INFO("Unpacked Release ~p~n", [Vsn]),
|
|
||||||
{ok, OtherVsn, Desc} = rpc:call(TargetNode, release_handler,
|
|
||||||
check_install_release, [Vsn], ?TIMEOUT),
|
|
||||||
{ok, OtherVsn, Desc} = rpc:call(TargetNode, release_handler,
|
|
||||||
install_release, [Vsn], ?TIMEOUT),
|
|
||||||
?INFO("Installed Release ~p~n", [Vsn]),
|
|
||||||
ok = rpc:call(TargetNode, release_handler, make_permanent, [Vsn], ?TIMEOUT),
|
|
||||||
?INFO("Made Release ~p Permanent~n", [Vsn]);
|
|
||||||
main(_) ->
|
|
||||||
init:stop(1).
|
|
||||||
|
|
||||||
start_distribution(NodeName, Cookie) ->
|
|
||||||
MyNode = make_script_node(NodeName),
|
|
||||||
{ok, _Pid} = net_kernel:start([MyNode, shortnames]),
|
|
||||||
erlang:set_cookie(node(), list_to_atom(Cookie)),
|
|
||||||
TargetNode = make_target_node(NodeName),
|
|
||||||
case {net_kernel:hidden_connect_node(TargetNode),
|
|
||||||
net_adm:ping(TargetNode)} of
|
|
||||||
{true, pong} ->
|
|
||||||
ok;
|
|
||||||
{_, pang} ->
|
|
||||||
io:format("Node ~p not responding to pings.\n", [TargetNode]),
|
|
||||||
init:stop(1)
|
|
||||||
end,
|
|
||||||
TargetNode.
|
|
||||||
|
|
||||||
make_target_node(Node) ->
|
|
||||||
[_, Host] = string:tokens(atom_to_list(node()), "@"),
|
|
||||||
list_to_atom(lists:concat([Node, "@", Host])).
|
|
||||||
|
|
||||||
make_script_node(Node) ->
|
|
||||||
list_to_atom(lists:concat([Node, "_upgrader_", os:getpid()])).
|
|
|
@ -1 +0,0 @@
|
||||||
emqttd_dashboard.
|
|
|
@ -1,214 +0,0 @@
|
||||||
#!/usr/bin/env escript
|
|
||||||
|
|
||||||
%% -*- mode: erlang;erlang-indent-level: 4;indent-tabs-mode: nil -*-
|
|
||||||
%% ex: ft=erlang ts=4 sw=4 et
|
|
||||||
%% -------------------------------------------------------------------
|
|
||||||
%%
|
|
||||||
%% nodetool: Helper Script for interacting with live nodes
|
|
||||||
%%
|
|
||||||
%% -------------------------------------------------------------------
|
|
||||||
-mode(compile).
|
|
||||||
|
|
||||||
main(Args) ->
|
|
||||||
ok = start_epmd(),
|
|
||||||
%% Extract the args
|
|
||||||
{RestArgs, TargetNode} = process_args(Args, [], undefined),
|
|
||||||
|
|
||||||
%% process_args() has side-effects (e.g. when processing "-name"),
|
|
||||||
%% so take care of app-starting business first.
|
|
||||||
[application:start(App) || App <- [crypto, public_key, ssl]],
|
|
||||||
|
|
||||||
%% any commands that don't need a running node
|
|
||||||
case RestArgs of
|
|
||||||
["chkconfig", File] ->
|
|
||||||
case file:consult(File) of
|
|
||||||
{ok, Terms} ->
|
|
||||||
case validate(Terms) of
|
|
||||||
ok ->
|
|
||||||
io:format("ok\n"),
|
|
||||||
halt(0);
|
|
||||||
{error, Problems} ->
|
|
||||||
lists:foreach(fun print_issue/1, Problems),
|
|
||||||
%% halt(1) if any problems were errors
|
|
||||||
halt(case [x || {error, _} <- Problems] of
|
|
||||||
[] -> 0;
|
|
||||||
_ -> 1
|
|
||||||
end)
|
|
||||||
end;
|
|
||||||
{error, {Line, Mod, Term}} ->
|
|
||||||
io:format(standard_error, ["Error on line ", file:format_error({Line, Mod, Term}), "\n"], []),
|
|
||||||
halt(1);
|
|
||||||
{error, R} ->
|
|
||||||
io:format(standard_error, ["Error reading config file: ", file:format_error(R), "\n"], []),
|
|
||||||
halt(1)
|
|
||||||
end;
|
|
||||||
_ ->
|
|
||||||
ok
|
|
||||||
end,
|
|
||||||
|
|
||||||
%% See if the node is currently running -- if it's not, we'll bail
|
|
||||||
case {net_kernel:hidden_connect_node(TargetNode), net_adm:ping(TargetNode)} of
|
|
||||||
{true, pong} ->
|
|
||||||
ok;
|
|
||||||
{false,pong} ->
|
|
||||||
io:format("Failed to connect to node ~p .\n", [TargetNode]),
|
|
||||||
halt(1);
|
|
||||||
{_, pang} ->
|
|
||||||
io:format("Node ~p not responding to pings.\n", [TargetNode]),
|
|
||||||
halt(1)
|
|
||||||
end,
|
|
||||||
|
|
||||||
case RestArgs of
|
|
||||||
["getpid"] ->
|
|
||||||
io:format("~p\n", [list_to_integer(rpc:call(TargetNode, os, getpid, []))]);
|
|
||||||
["ping"] ->
|
|
||||||
%% If we got this far, the node already responsed to a ping, so just dump
|
|
||||||
%% a "pong"
|
|
||||||
io:format("pong\n");
|
|
||||||
["stop"] ->
|
|
||||||
io:format("~p\n", [rpc:call(TargetNode, init, stop, [], 60000)]);
|
|
||||||
["restart"] ->
|
|
||||||
io:format("~p\n", [rpc:call(TargetNode, init, restart, [], 60000)]);
|
|
||||||
["reboot"] ->
|
|
||||||
io:format("~p\n", [rpc:call(TargetNode, init, reboot, [], 60000)]);
|
|
||||||
["rpc", Module, Function | RpcArgs] ->
|
|
||||||
case rpc:call(TargetNode, list_to_atom(Module), list_to_atom(Function),
|
|
||||||
[RpcArgs], 60000) of
|
|
||||||
ok ->
|
|
||||||
ok;
|
|
||||||
{badrpc, Reason} ->
|
|
||||||
io:format("RPC to ~p failed: ~p\n", [TargetNode, Reason]),
|
|
||||||
halt(1);
|
|
||||||
_ ->
|
|
||||||
halt(1)
|
|
||||||
end;
|
|
||||||
["rpc_infinity", Module, Function | RpcArgs] ->
|
|
||||||
case rpc:call(TargetNode, list_to_atom(Module), list_to_atom(Function), [RpcArgs], infinity) of
|
|
||||||
ok ->
|
|
||||||
ok;
|
|
||||||
{badrpc, Reason} ->
|
|
||||||
io:format("RPC to ~p failed: ~p\n", [TargetNode, Reason]),
|
|
||||||
halt(1);
|
|
||||||
_ ->
|
|
||||||
halt(1)
|
|
||||||
end;
|
|
||||||
["rpcterms", Module, Function, ArgsAsString] ->
|
|
||||||
case rpc:call(TargetNode, list_to_atom(Module), list_to_atom(Function),
|
|
||||||
consult(ArgsAsString), 60000) of
|
|
||||||
{badrpc, Reason} ->
|
|
||||||
io:format("RPC to ~p failed: ~p\n", [TargetNode, Reason]),
|
|
||||||
halt(1);
|
|
||||||
Other ->
|
|
||||||
io:format("~p\n", [Other])
|
|
||||||
end;
|
|
||||||
Other ->
|
|
||||||
io:format("Other: ~p\n", [Other]),
|
|
||||||
io:format("Usage: nodetool {chkconfig|getpid|ping|stop|restart|reboot|rpc|rpc_infinity|rpcterms}\n")
|
|
||||||
end,
|
|
||||||
net_kernel:stop().
|
|
||||||
|
|
||||||
|
|
||||||
process_args([], Acc, TargetNode) ->
|
|
||||||
{lists:reverse(Acc), TargetNode};
|
|
||||||
process_args(["-setcookie", Cookie | Rest], Acc, TargetNode) ->
|
|
||||||
erlang:set_cookie(node(), list_to_atom(Cookie)),
|
|
||||||
process_args(Rest, Acc, TargetNode);
|
|
||||||
process_args(["-name", TargetName | Rest], Acc, _) ->
|
|
||||||
ThisNode = append_node_suffix(TargetName, "_maint_"),
|
|
||||||
{ok, _} = net_kernel:start([ThisNode, longnames]),
|
|
||||||
process_args(Rest, Acc, nodename(TargetName));
|
|
||||||
process_args(["-sname", TargetName | Rest], Acc, _) ->
|
|
||||||
ThisNode = append_node_suffix(TargetName, "_maint_"),
|
|
||||||
{ok, _} = net_kernel:start([ThisNode, shortnames]),
|
|
||||||
process_args(Rest, Acc, nodename(TargetName));
|
|
||||||
process_args([Arg | Rest], Acc, Opts) ->
|
|
||||||
process_args(Rest, [Arg | Acc], Opts).
|
|
||||||
|
|
||||||
|
|
||||||
start_epmd() ->
|
|
||||||
[] = os:cmd(epmd_path() ++ " -daemon"),
|
|
||||||
ok.
|
|
||||||
|
|
||||||
epmd_path() ->
|
|
||||||
ErtsBinDir = filename:dirname(escript:script_name()),
|
|
||||||
Name = "epmd",
|
|
||||||
case os:find_executable(Name, ErtsBinDir) of
|
|
||||||
false ->
|
|
||||||
case os:find_executable(Name) of
|
|
||||||
false ->
|
|
||||||
io:format("Could not find epmd.~n"),
|
|
||||||
halt(1);
|
|
||||||
GlobalEpmd ->
|
|
||||||
GlobalEpmd
|
|
||||||
end;
|
|
||||||
Epmd ->
|
|
||||||
Epmd
|
|
||||||
end.
|
|
||||||
|
|
||||||
|
|
||||||
nodename(Name) ->
|
|
||||||
case string:tokens(Name, "@") of
|
|
||||||
[_Node, _Host] ->
|
|
||||||
list_to_atom(Name);
|
|
||||||
[Node] ->
|
|
||||||
[_, Host] = string:tokens(atom_to_list(node()), "@"),
|
|
||||||
list_to_atom(lists:concat([Node, "@", Host]))
|
|
||||||
end.
|
|
||||||
|
|
||||||
append_node_suffix(Name, Suffix) ->
|
|
||||||
case string:tokens(Name, "@") of
|
|
||||||
[Node, Host] ->
|
|
||||||
list_to_atom(lists:concat([Node, Suffix, os:getpid(), "@", Host]));
|
|
||||||
[Node] ->
|
|
||||||
list_to_atom(lists:concat([Node, Suffix, os:getpid()]))
|
|
||||||
end.
|
|
||||||
|
|
||||||
|
|
||||||
%%
|
|
||||||
%% Given a string or binary, parse it into a list of terms, ala file:consult/0
|
|
||||||
%%
|
|
||||||
consult(Str) when is_list(Str) ->
|
|
||||||
consult([], Str, []);
|
|
||||||
consult(Bin) when is_binary(Bin)->
|
|
||||||
consult([], binary_to_list(Bin), []).
|
|
||||||
|
|
||||||
consult(Cont, Str, Acc) ->
|
|
||||||
case erl_scan:tokens(Cont, Str, 0) of
|
|
||||||
{done, Result, Remaining} ->
|
|
||||||
case Result of
|
|
||||||
{ok, Tokens, _} ->
|
|
||||||
{ok, Term} = erl_parse:parse_term(Tokens),
|
|
||||||
consult([], Remaining, [Term | Acc]);
|
|
||||||
{eof, _Other} ->
|
|
||||||
lists:reverse(Acc);
|
|
||||||
{error, Info, _} ->
|
|
||||||
{error, Info}
|
|
||||||
end;
|
|
||||||
{more, Cont1} ->
|
|
||||||
consult(Cont1, eof, Acc)
|
|
||||||
end.
|
|
||||||
|
|
||||||
|
|
||||||
%%
|
|
||||||
%% Validation functions for checking the emqttd.config
|
|
||||||
%%
|
|
||||||
validate([Terms]) ->
|
|
||||||
Results = [ValidateFun(Terms) || ValidateFun <- get_validation_funs()],
|
|
||||||
Failures = [Res || Res <- Results, Res /= true],
|
|
||||||
case Failures of
|
|
||||||
[] ->
|
|
||||||
ok;
|
|
||||||
_ ->
|
|
||||||
{error, Failures}
|
|
||||||
end.
|
|
||||||
|
|
||||||
%% Some initial and basic checks for the app.config file
|
|
||||||
get_validation_funs() ->
|
|
||||||
[ ].
|
|
||||||
|
|
||||||
print_issue({warning, Warning}) ->
|
|
||||||
io:format(standard_error, "Warning in emqttd.config: ~s~n", [Warning]);
|
|
||||||
print_issue({error, Error}) ->
|
|
||||||
io:format(standard_error, "Error in emqttd.config: ~s~n", [Error]).
|
|
||||||
|
|
||||||
|
|
|
@ -1,14 +0,0 @@
|
||||||
%%%-----------------------------------------------------------------------------
|
|
||||||
%%
|
|
||||||
%% [Rewrite](https://github.com/emqtt/emqttd/wiki/Rewrite)
|
|
||||||
%%
|
|
||||||
%%%-----------------------------------------------------------------------------
|
|
||||||
|
|
||||||
%{topic, "x/#", [
|
|
||||||
% {rewrite, "^x/y/(.+)$", "z/y/$1"},
|
|
||||||
% {rewrite, "^x/(.+)$", "y/$1"}
|
|
||||||
%]}.
|
|
||||||
|
|
||||||
%{topic, "y/+/z/#", [
|
|
||||||
% {rewrite, "^y/(.+)/z/(.+)$", "y/z/$2"}
|
|
||||||
%]}.
|
|
|
@ -1,17 +0,0 @@
|
||||||
-----BEGIN CERTIFICATE-----
|
|
||||||
MIICuTCCAiICCQC8+3PPaqATfDANBgkqhkiG9w0BAQUFADCBoDELMAkGA1UEBhMC
|
|
||||||
Q0gxETAPBgNVBAgTCFpoZUppYW5nMREwDwYDVQQHEwhIYW5nWmhvdTEUMBIGA1UE
|
|
||||||
ChMLWGlhb0xpIFRlY2gxHzAdBgNVBAsTFkluZm9ybWF0aW9uIFRlY2hub2xvZ3kx
|
|
||||||
EzARBgNVBAMTCnQuZW1xdHQuaW8xHzAdBgkqhkiG9w0BCQEWEGZlbmcgYXQgZW1x
|
|
||||||
dHQuaW8wHhcNMTUwMjI1MTc0NjQwWhcNMTYwMjI1MTc0NjQwWjCBoDELMAkGA1UE
|
|
||||||
BhMCQ0gxETAPBgNVBAgTCFpoZUppYW5nMREwDwYDVQQHEwhIYW5nWmhvdTEUMBIG
|
|
||||||
A1UEChMLWGlhb0xpIFRlY2gxHzAdBgNVBAsTFkluZm9ybWF0aW9uIFRlY2hub2xv
|
|
||||||
Z3kxEzARBgNVBAMTCnQuZW1xdHQuaW8xHzAdBgkqhkiG9w0BCQEWEGZlbmcgYXQg
|
|
||||||
ZW1xdHQuaW8wgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBALAtN2OHsvltOk+9
|
|
||||||
AtlwMtKuaWW2WpV/S0lRRG9x9k8pyd5PJeeYAr2jVsoWnZInb1CoEOHFcwxZLjv3
|
|
||||||
gEvz+X+//W02YyI9hnvCJUpT/+6P0gJEbmTmqL078M6vbtwtiF1YC7mdo0nGAZuK
|
|
||||||
qedpIoEZbVJavf4S0vXWTsb3s5unAgMBAAEwDQYJKoZIhvcNAQEFBQADgYEAgUR3
|
|
||||||
z4uDUsAl+xUorPMBIOS/ncHHVk1XucVv9Wi4chzzZ+4/Y77/fFqP6oxhQ59C9Q8i
|
|
||||||
iT5wjaE4R1eCge18lPSw3yb1tsTe5B3WkRTzziPq/Q/AsC+DifkkE1YW67leuJV/
|
|
||||||
vz74sEi0dudmOVoe6peYxjEH8xXoIUqhnwXt/4Q=
|
|
||||||
-----END CERTIFICATE-----
|
|
|
@ -1,15 +0,0 @@
|
||||||
-----BEGIN RSA PRIVATE KEY-----
|
|
||||||
MIICXAIBAAKBgQCwLTdjh7L5bTpPvQLZcDLSrmlltlqVf0tJUURvcfZPKcneTyXn
|
|
||||||
mAK9o1bKFp2SJ29QqBDhxXMMWS4794BL8/l/v/1tNmMiPYZ7wiVKU//uj9ICRG5k
|
|
||||||
5qi9O/DOr27cLYhdWAu5naNJxgGbiqnnaSKBGW1SWr3+EtL11k7G97ObpwIDAQAB
|
|
||||||
AoGBAKU1cbiLG0GdtU3rME3ZUj+RQNMZ4u5IVcBmTie4FcN8q4ombKQ2P3O4RX3z
|
|
||||||
IUZaZp+bS2F8uHt+8cVYPl57Zp5fwbIlv6jWgGpvXLsX8JBQl2OTw38B+hVwJvAM
|
|
||||||
h0mBzprUOs3KGZyF5cyA4osrZ4QvCZhwId9fAjwLGBF9i1yBAkEA4jWAF1sWQiwF
|
|
||||||
vY476m+0ihpRwGKjldKHWFZmvoB/AnNV/rXO+HRl3MB5wmO+Dqg3gJZrjGBgDeaV
|
|
||||||
g9hoQjK6ZwJBAMdg57iKLd8uUb7c4pR8fDdDbeeI5X7WDf2k9emT3BMPJPQ3EiSf
|
|
||||||
CStn1hRfp31U9CXEnw94rKHhrdMFrYjdzMECQCcWD3f5qTLt4GAMf5XWj199hLq1
|
|
||||||
UIbGxdQhuccY9Nk7jJRiXczYb/Fg4KkSCvkFX/G8DAFJdc9xFEyfzAQEN+kCQH3a
|
|
||||||
nMrvZn9gBLffRKOIZPyZctHZp0xGIHTA4X39GMlrIN+Lt8coIKimlgssSlSiAK+q
|
|
||||||
iuFAQnC5PXlcNyuTHsECQAMNMY6jXikgSUZfVXitAFX3g9+IbjT9eJ92f60QneW8
|
|
||||||
mxWQoqP3fqCSbTEysb7NojEEwppSZtaNgnBb5R4E+mU=
|
|
||||||
-----END RSA PRIVATE KEY-----
|
|
|
@ -1,50 +0,0 @@
|
||||||
@setlocal
|
|
||||||
@echo off
|
|
||||||
@setlocal enabledelayedexpansion
|
|
||||||
|
|
||||||
@rem Parse arguments. erlsrv.exe prepends erl arguments prior to first ++.
|
|
||||||
@rem Other args are position dependent.
|
|
||||||
@set args="%*"
|
|
||||||
@for /F "delims=++ tokens=1,2,3" %%I in (%args%) do @(
|
|
||||||
@set erl_args=%%I
|
|
||||||
@call :set_trim node_name %%J
|
|
||||||
@rem Trim spaces from the left of %%K (node_root), which may have spaces inside
|
|
||||||
@for /f "tokens=* delims= " %%a in ("%%K") do @set node_root=%%a
|
|
||||||
)
|
|
||||||
|
|
||||||
@set releases_dir=%node_root%\releases
|
|
||||||
|
|
||||||
@rem parse ERTS version and release version from start_erl.dat
|
|
||||||
@for /F "usebackq tokens=1,2" %%I in ("%releases_dir%\start_erl.data") do @(
|
|
||||||
@call :set_trim erts_version %%I
|
|
||||||
@call :set_trim release_version %%J
|
|
||||||
)
|
|
||||||
|
|
||||||
@set erl_exe="%node_root%\erts-%erts_version%\bin\erl.exe"
|
|
||||||
@set boot_file="%releases_dir%\%release_version%\%node_name%"
|
|
||||||
|
|
||||||
@if exist "%releases_dir%\%release_version%\sys.config" (
|
|
||||||
@set app_config="%releases_dir%\%release_version%\sys.config"
|
|
||||||
) else (
|
|
||||||
@set app_config="%node_root%\etc\emqttd.config"
|
|
||||||
)
|
|
||||||
|
|
||||||
@if exist "%releases_dir%\%release_version%\vm.args" (
|
|
||||||
@set vm_args="%releases_dir%\%release_version%\vm.args"
|
|
||||||
) else (
|
|
||||||
@set vm_args="%node_root%\etc\vm.args"
|
|
||||||
)
|
|
||||||
|
|
||||||
set dest_path=%~dp0
|
|
||||||
cd /d !dest_path!..\plugins
|
|
||||||
set current_path=%cd%
|
|
||||||
set plugins=
|
|
||||||
for /d %%P in (*) do (
|
|
||||||
set "plugins=!plugins!"!current_path!\%%P\ebin" "
|
|
||||||
)
|
|
||||||
cd /d %node_root%
|
|
||||||
@%erl_exe% %erl_args% -boot %boot_file% -config %app_config% -args_file %vm_args% -pa %plugins%
|
|
||||||
|
|
||||||
:set_trim
|
|
||||||
@set %1=%2
|
|
||||||
@goto :EOF
|
|
|
@ -1,61 +0,0 @@
|
||||||
##-------------------------------------------------------------------------
|
|
||||||
## Name of the emqttd node: Name@Host
|
|
||||||
##
|
|
||||||
## NOTICE: The Host should be IP address or the fully qualified host name.
|
|
||||||
## The short hostname cannot work!
|
|
||||||
##-------------------------------------------------------------------------
|
|
||||||
|
|
||||||
-name emqttd@127.0.0.1
|
|
||||||
# or
|
|
||||||
#-name emqttd@localhost.
|
|
||||||
|
|
||||||
## Cookie for distributed erlang
|
|
||||||
-setcookie emqttdsecretcookie
|
|
||||||
|
|
||||||
##-------------------------------------------------------------------------
|
|
||||||
## Flags
|
|
||||||
##-------------------------------------------------------------------------
|
|
||||||
|
|
||||||
## Heartbeat management; auto-restarts VM if it dies or becomes unresponsive
|
|
||||||
## (Disabled by default..use with caution!)
|
|
||||||
##-heart
|
|
||||||
-smp true
|
|
||||||
|
|
||||||
## Enable kernel poll and a few async threads
|
|
||||||
+K true
|
|
||||||
|
|
||||||
## 12 threads/core.
|
|
||||||
+A 48
|
|
||||||
|
|
||||||
## max process numbers
|
|
||||||
+P 8192
|
|
||||||
|
|
||||||
## Sets the maximum number of simultaneously existing ports for this system
|
|
||||||
+Q 8192
|
|
||||||
|
|
||||||
## max atom number
|
|
||||||
## +t
|
|
||||||
|
|
||||||
## Set the distribution buffer busy limit (dist_buf_busy_limit) in kilobytes.
|
|
||||||
## Valid range is 1-2097151. Default is 1024.
|
|
||||||
## +zdbbl 8192
|
|
||||||
|
|
||||||
## Set scheduler bind type.
|
|
||||||
## +sbt db
|
|
||||||
|
|
||||||
##-------------------------------------------------------------------------
|
|
||||||
## Env
|
|
||||||
##-------------------------------------------------------------------------
|
|
||||||
|
|
||||||
## Increase number of concurrent ports/sockets, deprecated in R17
|
|
||||||
-env ERL_MAX_PORTS 8192
|
|
||||||
|
|
||||||
-env ERTS_MAX_PORTS 8192
|
|
||||||
|
|
||||||
## Mnesia and SSL will create temporary ets tables.
|
|
||||||
-env ERL_MAX_ETS_TABLES 1024
|
|
||||||
|
|
||||||
## Tweak GC to run more often
|
|
||||||
-env ERL_FULLSWEEP_AFTER 1000
|
|
||||||
|
|
||||||
-env ERL_CRASH_DUMP log/emqttd_crash.dump
|
|
|
@ -1,98 +0,0 @@
|
||||||
%% -*- mode: erlang;erlang-indent-level: 4;indent-tabs-mode: nil -*-
|
|
||||||
%% ex: ft=erlang ts=4 sw=4 et
|
|
||||||
{sys, [
|
|
||||||
{lib_dirs, ["../deps"]},
|
|
||||||
{erts, [{mod_cond, derived}, {app_file, strip}]},
|
|
||||||
{app_file, strip},
|
|
||||||
{rel, "emqttd", git,
|
|
||||||
[
|
|
||||||
kernel,
|
|
||||||
stdlib,
|
|
||||||
sasl,
|
|
||||||
asn1,
|
|
||||||
syntax_tools,
|
|
||||||
ssl,
|
|
||||||
crypto,
|
|
||||||
eldap,
|
|
||||||
xmerl,
|
|
||||||
os_mon,
|
|
||||||
inets,
|
|
||||||
goldrush,
|
|
||||||
compiler,
|
|
||||||
runtime_tools,
|
|
||||||
{observer, load},
|
|
||||||
lager,
|
|
||||||
gen_logger,
|
|
||||||
gproc,
|
|
||||||
esockd,
|
|
||||||
mochiweb,
|
|
||||||
emqttd
|
|
||||||
]},
|
|
||||||
{rel, "start_clean", "",
|
|
||||||
[
|
|
||||||
kernel,
|
|
||||||
stdlib
|
|
||||||
]},
|
|
||||||
{boot_rel, "emqttd"},
|
|
||||||
{profile, embedded},
|
|
||||||
{incl_cond, exclude},
|
|
||||||
%{mod_cond, derived},
|
|
||||||
{excl_archive_filters, [".*"]}, %% Do not archive built libs
|
|
||||||
{excl_sys_filters, ["^bin/(?!start_clean.boot)",
|
|
||||||
"^erts.*/bin/(dialyzer|typer)",
|
|
||||||
"^erts.*/(doc|info|include|lib|man|src)"]},
|
|
||||||
{excl_app_filters, ["\.gitignore"]},
|
|
||||||
{app, kernel, [{incl_cond, include}]},
|
|
||||||
{app, stdlib, [{incl_cond, include}]},
|
|
||||||
{app, sasl, [{incl_cond, include}]},
|
|
||||||
{app, asn1, [{incl_cond, include}]},
|
|
||||||
{app, crypto, [{incl_cond, include}]},
|
|
||||||
{app, ssl, [{incl_cond, include}]},
|
|
||||||
{app, xmerl, [{incl_cond, include}]},
|
|
||||||
{app, os_mon, [{incl_cond, include}]},
|
|
||||||
{app, syntax_tools, [{incl_cond, include}]},
|
|
||||||
{app, public_key, [{incl_cond, include}]},
|
|
||||||
{app, mnesia, [{incl_cond, include}]},
|
|
||||||
{app, eldap, [{incl_cond, include}]},
|
|
||||||
{app, inets, [{incl_cond, include}]},
|
|
||||||
{app, compiler, [{incl_cond, include}]},
|
|
||||||
{app, runtime_tools, [{incl_cond, include}]},
|
|
||||||
{app, observer, [{incl_cond, include}]},
|
|
||||||
{app, goldrush, [{incl_cond, include}]},
|
|
||||||
{app, gen_logger, [{incl_cond, include}]},
|
|
||||||
{app, lager, [{incl_cond, include}]},
|
|
||||||
{app, gproc, [{incl_cond, include}]},
|
|
||||||
{app, esockd, [{mod_cond, app}, {incl_cond, include}]},
|
|
||||||
{app, mochiweb, [{mod_cond, app}, {incl_cond, include}]},
|
|
||||||
{app, emqttd, [{mod_cond, app}, {incl_cond, include}, {lib_dir, ".."}]}
|
|
||||||
]}.
|
|
||||||
|
|
||||||
{target_dir, "emqttd"}.
|
|
||||||
|
|
||||||
{overlay_vars, "vars.config"}.
|
|
||||||
|
|
||||||
{overlay, [
|
|
||||||
{mkdir, "log/"},
|
|
||||||
{mkdir, "etc/"},
|
|
||||||
{mkdir, "etc/ssl/"},
|
|
||||||
{mkdir, "data/"},
|
|
||||||
{mkdir, "data/mnesia"},
|
|
||||||
{mkdir, "plugins/"},
|
|
||||||
{copy, "files/erl", "\{\{erts_vsn\}\}/bin/erl"},
|
|
||||||
{template, "files/nodetool", "\{\{erts_vsn\}\}/bin/nodetool"},
|
|
||||||
{template, "files/emqttd", "bin/emqttd"},
|
|
||||||
{template, "files/emqttd_ctl", "bin/emqttd_ctl"},
|
|
||||||
{template, "files/emqttd_top", "bin/emqttd_top"},
|
|
||||||
{template, "files/emqttd.cmd", "bin/emqttd.cmd"},
|
|
||||||
{copy, "files/start_erl.cmd", "bin/start_erl.cmd"},
|
|
||||||
{copy, "files/install_upgrade.escript", "bin/install_upgrade.escript"},
|
|
||||||
{copy, "files/ssl/ssl.crt", "etc/ssl/ssl.crt"},
|
|
||||||
{copy, "files/ssl/ssl.key", "etc/ssl/ssl.key"},
|
|
||||||
{template, "files/emqttd.config.production", "etc/emqttd.config"},
|
|
||||||
{template, "files/emqttd.config.development", "etc/emqttd.config.development"},
|
|
||||||
{template, "files/acl.config", "etc/acl.config"},
|
|
||||||
{template, "files/rewrite.config", "etc/rewrite.config"},
|
|
||||||
{template, "files/clients.config", "etc/clients.config"},
|
|
||||||
{template, "files/vm.args", "etc/vm.args"},
|
|
||||||
{copy, "files/loaded_plugins", "data/loaded_plugins"}
|
|
||||||
]}.
|
|
|
@ -1,18 +0,0 @@
|
||||||
%% -*- mode: erlang;erlang-indent-level: 4;indent-tabs-mode: nil -*-
|
|
||||||
%% ex: ft=erlang ts=4 sw=4 et
|
|
||||||
|
|
||||||
Sys = proplists:get_value(sys, CONFIG),
|
|
||||||
IncludeApps = [App || {app, App, _} <- Sys],
|
|
||||||
|
|
||||||
[DepsDir] = proplists:get_value(lib_dirs, Sys),
|
|
||||||
DepApps = lists:map(fun(AppFile) ->
|
|
||||||
{ok, [{application, Name, Attrs}]}
|
|
||||||
= file:consult(filename:join(DepsDir, AppFile)),
|
|
||||||
Name
|
|
||||||
end, filelib:wildcard("*/ebin/*.app", DepsDir)),
|
|
||||||
AppendApps = DepApps -- IncludeApps,
|
|
||||||
Cond = [{mod_cond, app}, {incl_cond, include}],
|
|
||||||
|
|
||||||
NewSys = lists:append(Sys, [{app, App, Cond} || App <- AppendApps]),
|
|
||||||
|
|
||||||
lists:keyreplace(sys, 1, CONFIG, {sys, NewSys}).
|
|
|
@ -1,28 +0,0 @@
|
||||||
%% -*- mode: erlang;erlang-indent-level: 4;indent-tabs-mode: nil -*-
|
|
||||||
%% ex: ft=erlang ts=4 sw=4 et
|
|
||||||
|
|
||||||
%% Platform-specific installation paths
|
|
||||||
{platform_bin_dir, "./bin"}.
|
|
||||||
{platform_data_dir, "./var/data"}.
|
|
||||||
{platform_etc_dir, "./etc"}.
|
|
||||||
{platform_lib_dir, "./lib"}.
|
|
||||||
{platform_log_dir, "./log"}.
|
|
||||||
|
|
||||||
%%
|
|
||||||
%% etc/emqttd.config
|
|
||||||
%%
|
|
||||||
|
|
||||||
|
|
||||||
%%
|
|
||||||
%% etc/vm.args
|
|
||||||
%%
|
|
||||||
|
|
||||||
%%
|
|
||||||
%% bin/emqttd
|
|
||||||
%%
|
|
||||||
{runner_script_dir, "$(cd ${0%/*} && pwd)"}.
|
|
||||||
{runner_base_dir, "${RUNNER_SCRIPT_DIR%/*}"}.
|
|
||||||
{runner_etc_dir, "$RUNNER_BASE_DIR/etc"}.
|
|
||||||
{runner_log_dir, "$RUNNER_BASE_DIR/log"}.
|
|
||||||
{pipe_dir, "/tmp/$RUNNER_SCRIPT/"}.
|
|
||||||
{runner_user, ""}.
|
|
|
@ -1,11 +1,12 @@
|
||||||
{application, emqttd,
|
{application, emqttd,
|
||||||
[
|
[
|
||||||
{description, "Erlang MQTT Broker"},
|
{description, "Erlang MQTT Broker"},
|
||||||
{vsn, "1.1.3"},
|
{vsn, "2.0"},
|
||||||
{id, "emqttd"},
|
{id, "emqttd"},
|
||||||
{modules, []},
|
{modules, []},
|
||||||
{registered, []},
|
{registered, []},
|
||||||
{applications, [kernel, stdlib, gproc, esockd, mochiweb]},
|
{applications, [kernel, stdlib, gproc, esockd, mochiweb,
|
||||||
|
gen_logger, gen_conf]},
|
||||||
{mod, {emqttd_app, []}},
|
{mod, {emqttd_app, []}},
|
||||||
{env, []}
|
{env, []}
|
||||||
]}.
|
]}.
|
||||||
|
|
142
src/emqttd.erl
142
src/emqttd.erl
|
@ -14,38 +14,66 @@
|
||||||
%% limitations under the License.
|
%% limitations under the License.
|
||||||
%%--------------------------------------------------------------------
|
%%--------------------------------------------------------------------
|
||||||
|
|
||||||
|
%% Facade Module for The EMQTT Broker
|
||||||
|
|
||||||
-module(emqttd).
|
-module(emqttd).
|
||||||
|
|
||||||
-include("emqttd.hrl").
|
-include("emqttd.hrl").
|
||||||
|
|
||||||
-include("emqttd_protocol.hrl").
|
-include("emqttd_protocol.hrl").
|
||||||
|
|
||||||
-export([start/0, env/1, env/2, is_running/1]).
|
-export([start/0, conf/1, conf/2, env/1, env/2, is_running/1]).
|
||||||
|
|
||||||
%% PubSub API
|
%% PubSub API
|
||||||
-export([create/2, lookup/2, publish/1, subscribe/1, subscribe/3,
|
-export([subscribe/1, subscribe/2, subscribe/3, publish/1,
|
||||||
unsubscribe/1, unsubscribe/3]).
|
unsubscribe/1, unsubscribe/2]).
|
||||||
|
|
||||||
|
%% PubSub Management API
|
||||||
|
-export([setqos/3, topics/0, subscriptions/1, subscribers/1,
|
||||||
|
is_subscribed/2, subscriber_down/1]).
|
||||||
|
|
||||||
%% Hooks API
|
%% Hooks API
|
||||||
-export([hook/4, hook/3, unhook/2, run_hooks/3]).
|
-export([hook/4, hook/3, unhook/2, run_hooks/3]).
|
||||||
|
|
||||||
|
%% Adapter
|
||||||
|
-export([adapter/1]).
|
||||||
|
|
||||||
|
%% Debug API
|
||||||
|
-export([dump/0]).
|
||||||
|
|
||||||
|
-type(subscriber() :: pid() | binary()).
|
||||||
|
|
||||||
|
-type(suboption() :: local | {qos, non_neg_integer()} | {share, {'$queue' | binary()}}).
|
||||||
|
|
||||||
|
-type(pubsub_error() :: {error, {already_subscribed, binary()}
|
||||||
|
| {subscription_not_found, binary()}}).
|
||||||
|
|
||||||
|
-export_type([subscriber/0, suboption/0, pubsub_error/0]).
|
||||||
|
|
||||||
-define(APP, ?MODULE).
|
-define(APP, ?MODULE).
|
||||||
|
|
||||||
%%--------------------------------------------------------------------
|
%%--------------------------------------------------------------------
|
||||||
%% Bootstrap, environment, is_running...
|
%% Bootstrap, environment, configuration, is_running...
|
||||||
%%--------------------------------------------------------------------
|
%%--------------------------------------------------------------------
|
||||||
|
|
||||||
%% @doc Start emqttd application.
|
%% @doc Start emqttd application.
|
||||||
-spec(start() -> ok | {error, any()}).
|
-spec(start() -> ok | {error, any()}).
|
||||||
start() -> application:start(?APP).
|
start() -> application:start(?APP).
|
||||||
|
|
||||||
%% @doc Group environment
|
%% @doc Get Config
|
||||||
-spec(env(Group :: atom()) -> list()).
|
-spec(conf(Key :: atom()) -> any()).
|
||||||
env(Group) -> application:get_env(?APP, Group, []).
|
conf(Key) -> gen_conf:value(?APP, Key).
|
||||||
|
|
||||||
|
-spec(conf(Key :: atom(), Default :: any()) -> any()).
|
||||||
|
conf(Key, Default) -> gen_conf:value(?APP, Key, Default).
|
||||||
|
|
||||||
|
%% @doc Environment
|
||||||
|
-spec(env(Key:: atom()) -> any()).
|
||||||
|
env(Key) -> application:get_env(?APP, Key).
|
||||||
|
|
||||||
%% @doc Get environment
|
%% @doc Get environment
|
||||||
-spec(env(Group :: atom(), Name :: atom()) -> undefined | any()).
|
-spec(env(Key:: atom(), Default:: any()) -> undefined | any()).
|
||||||
env(Group, Name) -> proplists:get_value(Name, env(Group)).
|
env(Key, Default) -> application:get_env(?APP, Key, Default).
|
||||||
|
|
||||||
%% @doc Is running?
|
%% @doc Is running?
|
||||||
-spec(is_running(node()) -> boolean()).
|
-spec(is_running(node()) -> boolean()).
|
||||||
|
@ -57,52 +85,60 @@ is_running(Node) ->
|
||||||
end.
|
end.
|
||||||
|
|
||||||
%%--------------------------------------------------------------------
|
%%--------------------------------------------------------------------
|
||||||
%% PubSub APIs that wrap emqttd_server, emqttd_pubsub
|
%% PubSub APIs
|
||||||
%%--------------------------------------------------------------------
|
%%--------------------------------------------------------------------
|
||||||
|
|
||||||
%% @doc Lookup Topic or Subscription
|
%% @doc Subscribe
|
||||||
-spec(lookup(topic, binary()) -> [mqtt_topic()];
|
-spec(subscribe(iodata()) -> ok | {error, any()}).
|
||||||
(subscription, binary()) -> [mqtt_subscription()]).
|
subscribe(Topic) ->
|
||||||
lookup(topic, Topic) when is_binary(Topic) ->
|
subscribe(Topic, self()).
|
||||||
emqttd_pubsub:lookup_topic(Topic);
|
|
||||||
|
|
||||||
lookup(subscription, ClientId) when is_binary(ClientId) ->
|
-spec(subscribe(iodata(), subscriber()) -> ok | {error, any()}).
|
||||||
emqttd_server:lookup_subscription(ClientId).
|
subscribe(Topic, Subscriber) ->
|
||||||
|
subscribe(Topic, Subscriber, []).
|
||||||
|
|
||||||
%% @doc Create a Topic or Subscription
|
-spec(subscribe(iodata(), subscriber(), [suboption()]) -> ok | pubsub_error()).
|
||||||
-spec(create(topic | subscription, binary()) -> ok | {error, any()}).
|
subscribe(Topic, Subscriber, Options) ->
|
||||||
create(topic, Topic) when is_binary(Topic) ->
|
with_pubsub(fun(PS) -> PS:subscribe(iolist_to_binary(Topic), Subscriber, Options) end).
|
||||||
emqttd_pubsub:create_topic(Topic);
|
|
||||||
|
|
||||||
create(subscription, {ClientId, Topic, Qos}) ->
|
|
||||||
Subscription = #mqtt_subscription{subid = ClientId, topic = Topic, qos = ?QOS_I(Qos)},
|
|
||||||
emqttd_backend:add_subscription(Subscription).
|
|
||||||
|
|
||||||
%% @doc Publish MQTT Message
|
%% @doc Publish MQTT Message
|
||||||
-spec(publish(mqtt_message()) -> ok).
|
-spec(publish(mqtt_message()) -> {ok, mqtt_delivery()} | ignore).
|
||||||
publish(Msg) when is_record(Msg, mqtt_message) ->
|
publish(Msg) ->
|
||||||
emqttd_server:publish(Msg), ok.
|
with_pubsub(fun(PS) -> PS:publish(Msg) end).
|
||||||
|
|
||||||
%% @doc Subscribe
|
|
||||||
-spec(subscribe(binary()) -> ok;
|
|
||||||
({binary(), binary(), mqtt_qos()}) -> ok).
|
|
||||||
subscribe(Topic) when is_binary(Topic) ->
|
|
||||||
emqttd_server:subscribe(Topic);
|
|
||||||
subscribe({ClientId, Topic, Qos}) ->
|
|
||||||
subscribe(ClientId, Topic, Qos).
|
|
||||||
|
|
||||||
-spec(subscribe(binary(), binary(), mqtt_qos()) -> {ok, mqtt_qos()}).
|
|
||||||
subscribe(ClientId, Topic, Qos) ->
|
|
||||||
emqttd_server:subscribe(ClientId, Topic, Qos).
|
|
||||||
|
|
||||||
%% @doc Unsubscribe
|
%% @doc Unsubscribe
|
||||||
-spec(unsubscribe(binary()) -> ok).
|
-spec(unsubscribe(iodata()) -> ok | pubsub_error()).
|
||||||
unsubscribe(Topic) when is_binary(Topic) ->
|
unsubscribe(Topic) ->
|
||||||
emqttd_server:unsubscribe(Topic).
|
unsubscribe(Topic, self()).
|
||||||
|
|
||||||
-spec(unsubscribe(binary(), binary(), mqtt_qos()) -> ok).
|
-spec(unsubscribe(iodata(), subscriber()) -> ok | pubsub_error()).
|
||||||
unsubscribe(ClientId, Topic, Qos) ->
|
unsubscribe(Topic, Subscriber) ->
|
||||||
emqttd_server:unsubscribe(ClientId, Topic, Qos).
|
with_pubsub(fun(PS) -> PS:unsubscribe(iolist_to_binary(Topic), Subscriber) end).
|
||||||
|
|
||||||
|
-spec(setqos(binary(), subscriber(), mqtt_qos()) -> ok).
|
||||||
|
setqos(Topic, Subscriber, Qos) ->
|
||||||
|
with_pubsub(fun(PS) -> PS:setqos(iolist_to_binary(Topic), Subscriber, Qos) end).
|
||||||
|
|
||||||
|
-spec(topics() -> [binary()]).
|
||||||
|
topics() -> emqttd_router:topics().
|
||||||
|
|
||||||
|
-spec(subscribers(iodata()) -> list(subscriber())).
|
||||||
|
subscribers(Topic) ->
|
||||||
|
with_pubsub(fun(PS) -> PS:subscribers(iolist_to_binary(Topic)) end).
|
||||||
|
|
||||||
|
-spec(subscriptions(subscriber()) -> [{binary(), suboption()}]).
|
||||||
|
subscriptions(Subscriber) ->
|
||||||
|
with_pubsub(fun(PS) -> PS:subscriptions(Subscriber) end).
|
||||||
|
|
||||||
|
-spec(is_subscribed(iodata(), subscriber()) -> boolean()).
|
||||||
|
is_subscribed(Topic, Subscriber) ->
|
||||||
|
with_pubsub(fun(PS) -> PS:is_subscribed(iolist_to_binary(Topic), Subscriber) end).
|
||||||
|
|
||||||
|
-spec(subscriber_down(subscriber()) -> ok).
|
||||||
|
subscriber_down(Subscriber) ->
|
||||||
|
with_pubsub(fun(PS) -> PS:subscriber_down(Subscriber) end).
|
||||||
|
|
||||||
|
with_pubsub(Fun) -> Fun(env(pubsub_server, emqttd_server)).
|
||||||
|
|
||||||
%%--------------------------------------------------------------------
|
%%--------------------------------------------------------------------
|
||||||
%% Hooks API
|
%% Hooks API
|
||||||
|
@ -124,3 +160,17 @@ unhook(Hook, Function) ->
|
||||||
run_hooks(Hook, Args, Acc) ->
|
run_hooks(Hook, Args, Acc) ->
|
||||||
emqttd_hook:run(Hook, Args, Acc).
|
emqttd_hook:run(Hook, Args, Acc).
|
||||||
|
|
||||||
|
%%--------------------------------------------------------------------
|
||||||
|
%% Adapter
|
||||||
|
%%--------------------------------------------------------------------
|
||||||
|
|
||||||
|
adapter(server) -> env(pubsub_server, emqttd_server);
|
||||||
|
adapter(pubsub) -> env(pubsub_adapter, emqttd_pubsub);
|
||||||
|
adapter(bridge) -> env(bridge_adapter, emqttd_bridge).
|
||||||
|
|
||||||
|
%%--------------------------------------------------------------------
|
||||||
|
%% Debug
|
||||||
|
%%--------------------------------------------------------------------
|
||||||
|
|
||||||
|
dump() -> with_pubsub(fun(PS) -> lists:append([PS:dump(), emqttd_router:dump()]) end).
|
||||||
|
|
||||||
|
|
|
@ -23,7 +23,7 @@
|
||||||
-define(SERVER, ?MODULE).
|
-define(SERVER, ?MODULE).
|
||||||
|
|
||||||
%% API Function Exports
|
%% API Function Exports
|
||||||
-export([start_link/0, start_link/1,
|
-export([start_link/0,
|
||||||
auth/2, % authentication
|
auth/2, % authentication
|
||||||
check_acl/3, % acl check
|
check_acl/3, % acl check
|
||||||
reload_acl/0, % reload acl
|
reload_acl/0, % reload acl
|
||||||
|
@ -48,11 +48,8 @@
|
||||||
|
|
||||||
%% @doc Start access control server.
|
%% @doc Start access control server.
|
||||||
-spec(start_link() -> {ok, pid()} | ignore | {error, any()}).
|
-spec(start_link() -> {ok, pid()} | ignore | {error, any()}).
|
||||||
start_link() -> start_link(emqttd:env(access)).
|
start_link() ->
|
||||||
|
gen_server:start_link({local, ?SERVER}, ?MODULE, [], []).
|
||||||
-spec(start_link(Opts :: list()) -> {ok, pid()} | ignore | {error, any()}).
|
|
||||||
start_link(Opts) ->
|
|
||||||
gen_server:start_link({local, ?SERVER}, ?MODULE, [Opts], []).
|
|
||||||
|
|
||||||
%% @doc Authenticate MQTT Client.
|
%% @doc Authenticate MQTT Client.
|
||||||
-spec(auth(Client :: mqtt_client(), Password :: password()) -> ok | {error, any()}).
|
-spec(auth(Client :: mqtt_client(), Password :: password()) -> ok | {error, any()}).
|
||||||
|
@ -73,7 +70,7 @@ auth(Client, Password, [{Mod, State, _Seq} | Mods]) ->
|
||||||
Client :: mqtt_client(),
|
Client :: mqtt_client(),
|
||||||
PubSub :: pubsub(),
|
PubSub :: pubsub(),
|
||||||
Topic :: binary()).
|
Topic :: binary()).
|
||||||
check_acl(Client, PubSub, Topic) when ?IS_PUBSUB(PubSub) ->
|
check_acl(Client, PubSub, Topic) when ?PUBSUB(PubSub) ->
|
||||||
case lookup_mods(acl) of
|
case lookup_mods(acl) of
|
||||||
[] -> allow;
|
[] -> allow;
|
||||||
AclMods -> check_acl(Client, PubSub, Topic, AclMods)
|
AclMods -> check_acl(Client, PubSub, Topic, AclMods)
|
||||||
|
@ -125,17 +122,14 @@ stop() -> gen_server:call(?MODULE, stop).
|
||||||
%% gen_server callbacks
|
%% gen_server callbacks
|
||||||
%%--------------------------------------------------------------------
|
%%--------------------------------------------------------------------
|
||||||
|
|
||||||
init([Opts]) ->
|
init([]) ->
|
||||||
ets:new(?ACCESS_CONTROL_TAB, [set, named_table, protected, {read_concurrency, true}]),
|
ets:new(?ACCESS_CONTROL_TAB, [set, named_table, protected, {read_concurrency, true}]),
|
||||||
ets:insert(?ACCESS_CONTROL_TAB, {auth_modules, init_mods(auth, proplists:get_value(auth, Opts))}),
|
ets:insert(?ACCESS_CONTROL_TAB, {auth_modules, init_mods(gen_conf:list(emqttd, auth))}),
|
||||||
ets:insert(?ACCESS_CONTROL_TAB, {acl_modules, init_mods(acl, proplists:get_value(acl, Opts))}),
|
ets:insert(?ACCESS_CONTROL_TAB, {acl_modules, init_mods(gen_conf:list(emqttd, acl))}),
|
||||||
{ok, #state{}}.
|
{ok, #state{}}.
|
||||||
|
|
||||||
init_mods(auth, AuthMods) ->
|
init_mods(Mods) ->
|
||||||
[init_mod(authmod(Name), Opts) || {Name, Opts} <- AuthMods];
|
[init_mod(mod_name(Type, Name), Opts) || {Type, Name, Opts} <- Mods].
|
||||||
|
|
||||||
init_mods(acl, AclMods) ->
|
|
||||||
[init_mod(aclmod(Name), Opts) || {Name, Opts} <- AclMods].
|
|
||||||
|
|
||||||
init_mod(Mod, Opts) ->
|
init_mod(Mod, Opts) ->
|
||||||
{ok, State} = Mod:init(Opts), {Mod, State, 0}.
|
{ok, State} = Mod:init(Opts), {Mod, State, 0}.
|
||||||
|
@ -191,15 +185,14 @@ code_change(_OldVsn, State, _Extra) ->
|
||||||
%% Internal functions
|
%% Internal functions
|
||||||
%%--------------------------------------------------------------------
|
%%--------------------------------------------------------------------
|
||||||
|
|
||||||
authmod(Name) when is_atom(Name) ->
|
mod_name(auth, Name) -> mod(emqttd_auth_, Name);
|
||||||
mod(emqttd_auth_, Name).
|
|
||||||
|
|
||||||
aclmod(Name) when is_atom(Name) ->
|
mod_name(acl, Name) -> mod(emqttd_acl_, Name).
|
||||||
mod(emqttd_acl_, Name).
|
|
||||||
|
|
||||||
mod(Prefix, Name) ->
|
mod(Prefix, Name) ->
|
||||||
list_to_atom(lists:concat([Prefix, Name])).
|
list_to_atom(lists:concat([Prefix, Name])).
|
||||||
|
|
||||||
if_existed(false, Fun) -> Fun();
|
if_existed(false, Fun) -> Fun();
|
||||||
|
|
||||||
if_existed(_Mod, _Fun) -> {error, already_existed}.
|
if_existed(_Mod, _Fun) -> {error, already_existed}.
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,35 @@
|
||||||
|
%%--------------------------------------------------------------------
|
||||||
|
%% Copyright (c) 2012-2016 Feng Lee <feng@emqtt.io>.
|
||||||
|
%%
|
||||||
|
%% Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
%% you may not use this file except in compliance with the License.
|
||||||
|
%% You may obtain a copy of the License at
|
||||||
|
%%
|
||||||
|
%% http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
%%
|
||||||
|
%% Unless required by applicable law or agreed to in writing, software
|
||||||
|
%% distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
%% See the License for the specific language governing permissions and
|
||||||
|
%% limitations under the License.
|
||||||
|
%%--------------------------------------------------------------------
|
||||||
|
|
||||||
|
-module(emqttd_acl_anonymous).
|
||||||
|
|
||||||
|
-behaviour(emqttd_acl_mod).
|
||||||
|
|
||||||
|
%% ACL callbacks
|
||||||
|
-export([init/1, check_acl/2, reload_acl/1, description/0]).
|
||||||
|
|
||||||
|
init(Opts) ->
|
||||||
|
{ok, Opts}.
|
||||||
|
|
||||||
|
check_acl(_Who, _State) ->
|
||||||
|
allow.
|
||||||
|
|
||||||
|
reload_acl(_State) ->
|
||||||
|
ok.
|
||||||
|
|
||||||
|
description() ->
|
||||||
|
"Anonymous ACL".
|
||||||
|
|
|
@ -27,7 +27,7 @@
|
||||||
|
|
||||||
-define(ACL_RULE_TAB, mqtt_acl_rule).
|
-define(ACL_RULE_TAB, mqtt_acl_rule).
|
||||||
|
|
||||||
-record(state, {acl_file, nomatch = allow}).
|
-record(state, {config, nomatch = allow}).
|
||||||
|
|
||||||
%%--------------------------------------------------------------------
|
%%--------------------------------------------------------------------
|
||||||
%% API
|
%% API
|
||||||
|
@ -46,16 +46,20 @@ all_rules() ->
|
||||||
%%--------------------------------------------------------------------
|
%%--------------------------------------------------------------------
|
||||||
|
|
||||||
%% @doc Init internal ACL
|
%% @doc Init internal ACL
|
||||||
-spec(init(AclOpts :: list()) -> {ok, State :: any()}).
|
-spec(init(Opts :: list()) -> {ok, State :: any()}).
|
||||||
init(AclOpts) ->
|
init(Opts) ->
|
||||||
ets:new(?ACL_RULE_TAB, [set, public, named_table, {read_concurrency, true}]),
|
ets:new(?ACL_RULE_TAB, [set, public, named_table, {read_concurrency, true}]),
|
||||||
AclFile = proplists:get_value(file, AclOpts),
|
case proplists:get_value(config, Opts) of
|
||||||
Default = proplists:get_value(nomatch, AclOpts, allow),
|
undefined ->
|
||||||
State = #state{acl_file = AclFile, nomatch = Default},
|
{ok, #state{}};
|
||||||
|
File ->
|
||||||
|
Default = proplists:get_value(nomatch, Opts, allow),
|
||||||
|
State = #state{config = File, nomatch = Default},
|
||||||
true = load_rules_from_file(State),
|
true = load_rules_from_file(State),
|
||||||
{ok, State}.
|
{ok, State}
|
||||||
|
end.
|
||||||
|
|
||||||
load_rules_from_file(#state{acl_file = AclFile}) ->
|
load_rules_from_file(#state{config = AclFile}) ->
|
||||||
{ok, Terms} = file:consult(AclFile),
|
{ok, Terms} = file:consult(AclFile),
|
||||||
Rules = [emqttd_access_rule:compile(Term) || Term <- Terms],
|
Rules = [emqttd_access_rule:compile(Term) || Term <- Terms],
|
||||||
lists:foreach(fun(PubSub) ->
|
lists:foreach(fun(PubSub) ->
|
||||||
|
@ -83,6 +87,8 @@ filter(_PubSub, {_AllowDeny, _Who, _, _Topics}) ->
|
||||||
PubSub :: pubsub(),
|
PubSub :: pubsub(),
|
||||||
Topic :: binary(),
|
Topic :: binary(),
|
||||||
State :: #state{}).
|
State :: #state{}).
|
||||||
|
check_acl(_Who, #state{config = undefined}) ->
|
||||||
|
allow;
|
||||||
check_acl({Client, PubSub, Topic}, #state{nomatch = Default}) ->
|
check_acl({Client, PubSub, Topic}, #state{nomatch = Default}) ->
|
||||||
case match(Client, Topic, lookup(PubSub)) of
|
case match(Client, Topic, lookup(PubSub)) of
|
||||||
{matched, allow} -> allow;
|
{matched, allow} -> allow;
|
||||||
|
@ -107,6 +113,8 @@ match(Client, Topic, [Rule|Rules]) ->
|
||||||
|
|
||||||
%% @doc Reload ACL
|
%% @doc Reload ACL
|
||||||
-spec(reload_acl(State :: #state{}) -> ok | {error, Reason :: any()}).
|
-spec(reload_acl(State :: #state{}) -> ok | {error, Reason :: any()}).
|
||||||
|
reload_acl(#state{config = undefined}) ->
|
||||||
|
ok;
|
||||||
reload_acl(State) ->
|
reload_acl(State) ->
|
||||||
case catch load_rules_from_file(State) of
|
case catch load_rules_from_file(State) of
|
||||||
{'EXIT', Error} -> {error, Error};
|
{'EXIT', Error} -> {error, Error};
|
||||||
|
@ -115,5 +123,6 @@ reload_acl(State) ->
|
||||||
|
|
||||||
%% @doc ACL Module Description
|
%% @doc ACL Module Description
|
||||||
-spec(description() -> string()).
|
-spec(description() -> string()).
|
||||||
description() -> "Internal ACL with etc/acl.config".
|
description() ->
|
||||||
|
"Internal ACL with etc/acl.conf".
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
%%--------------------------------------------------------------------
|
%--------------------------------------------------------------------
|
||||||
%% Copyright (c) 2012-2016 Feng Lee <feng@emqtt.io>.
|
%% Copyright (c) 2012-2016 Feng Lee <feng@emqtt.io>.
|
||||||
%%
|
%%
|
||||||
%% Licensed under the Apache License, Version 2.0 (the "License");
|
%% Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
@ -26,12 +26,8 @@
|
||||||
-export([start_listener/1, stop_listener/1, is_mod_enabled/1]).
|
-export([start_listener/1, stop_listener/1, is_mod_enabled/1]).
|
||||||
|
|
||||||
%% MQTT SockOpts
|
%% MQTT SockOpts
|
||||||
-define(MQTT_SOCKOPTS, [
|
-define(MQTT_SOCKOPTS, [binary, {packet, raw}, {reuseaddr, true},
|
||||||
binary,
|
{backlog, 512}, {nodelay, true}]).
|
||||||
{packet, raw},
|
|
||||||
{reuseaddr, true},
|
|
||||||
{backlog, 512},
|
|
||||||
{nodelay, true}]).
|
|
||||||
|
|
||||||
-type listener() :: {atom(), esockd:listen_on(), [esockd:option()]}.
|
-type listener() :: {atom(), esockd:listen_on(), [esockd:option()]}.
|
||||||
|
|
||||||
|
@ -46,6 +42,7 @@
|
||||||
Reason :: term()).
|
Reason :: term()).
|
||||||
start(_StartType, _StartArgs) ->
|
start(_StartType, _StartArgs) ->
|
||||||
print_banner(),
|
print_banner(),
|
||||||
|
gen_conf:init(emqttd),
|
||||||
emqttd_mnesia:start(),
|
emqttd_mnesia:start(),
|
||||||
{ok, Sup} = emqttd_sup:start_link(),
|
{ok, Sup} = emqttd_sup:start_link(),
|
||||||
start_servers(Sup),
|
start_servers(Sup),
|
||||||
|
@ -80,6 +77,7 @@ print_vsn() ->
|
||||||
start_servers(Sup) ->
|
start_servers(Sup) ->
|
||||||
Servers = [{"emqttd ctl", emqttd_ctl},
|
Servers = [{"emqttd ctl", emqttd_ctl},
|
||||||
{"emqttd hook", emqttd_hook},
|
{"emqttd hook", emqttd_hook},
|
||||||
|
{"emqttd router", emqttd_router},
|
||||||
{"emqttd pubsub", {supervisor, emqttd_pubsub_sup}},
|
{"emqttd pubsub", {supervisor, emqttd_pubsub_sup}},
|
||||||
{"emqttd stats", emqttd_stats},
|
{"emqttd stats", emqttd_stats},
|
||||||
{"emqttd metrics", emqttd_metrics},
|
{"emqttd metrics", emqttd_metrics},
|
||||||
|
@ -93,7 +91,7 @@ start_servers(Sup) ->
|
||||||
{"emqttd broker", emqttd_broker},
|
{"emqttd broker", emqttd_broker},
|
||||||
{"emqttd alarm", emqttd_alarm},
|
{"emqttd alarm", emqttd_alarm},
|
||||||
{"emqttd mod supervisor", emqttd_mod_sup},
|
{"emqttd mod supervisor", emqttd_mod_sup},
|
||||||
{"emqttd bridge supervisor", {supervisor, emqttd_bridge_sup}},
|
{"emqttd bridge supervisor", {supervisor, emqttd_bridge_sup_sup}},
|
||||||
{"emqttd access control", emqttd_access_control},
|
{"emqttd access control", emqttd_access_control},
|
||||||
{"emqttd system monitor", {supervisor, emqttd_sysmon_sup}}],
|
{"emqttd system monitor", {supervisor, emqttd_sysmon_sup}}],
|
||||||
[start_server(Sup, Server) || Server <- Servers].
|
[start_server(Sup, Server) || Server <- Servers].
|
||||||
|
@ -101,17 +99,17 @@ start_servers(Sup) ->
|
||||||
start_server(_Sup, {Name, F}) when is_function(F) ->
|
start_server(_Sup, {Name, F}) when is_function(F) ->
|
||||||
?PRINT("~s is starting...", [Name]),
|
?PRINT("~s is starting...", [Name]),
|
||||||
F(),
|
F(),
|
||||||
?PRINT_MSG("[done]~n");
|
?PRINT_MSG("[ok]~n");
|
||||||
|
|
||||||
start_server(Sup, {Name, Server}) ->
|
start_server(Sup, {Name, Server}) ->
|
||||||
?PRINT("~s is starting...", [Name]),
|
?PRINT("~s is starting...", [Name]),
|
||||||
start_child(Sup, Server),
|
start_child(Sup, Server),
|
||||||
?PRINT_MSG("[done]~n");
|
?PRINT_MSG("[ok]~n");
|
||||||
|
|
||||||
start_server(Sup, {Name, Server, Opts}) ->
|
start_server(Sup, {Name, Server, Opts}) ->
|
||||||
?PRINT("~s is starting...", [ Name]),
|
?PRINT("~s is starting...", [ Name]),
|
||||||
start_child(Sup, Server, Opts),
|
start_child(Sup, Server, Opts),
|
||||||
?PRINT_MSG("[done]~n").
|
?PRINT_MSG("[ok]~n").
|
||||||
|
|
||||||
start_child(Sup, {supervisor, Module}) ->
|
start_child(Sup, {supervisor, Module}) ->
|
||||||
supervisor:start_child(Sup, supervisor_spec(Module));
|
supervisor:start_child(Sup, supervisor_spec(Module));
|
||||||
|
@ -149,9 +147,9 @@ worker_spec(M, F, A) ->
|
||||||
|
|
||||||
%% @doc load all modules
|
%% @doc load all modules
|
||||||
load_all_mods() ->
|
load_all_mods() ->
|
||||||
lists:foreach(fun load_mod/1, emqttd:env(modules)).
|
lists:foreach(fun load_mod/1, gen_conf:list(emqttd, module)).
|
||||||
|
|
||||||
load_mod({Name, Opts}) ->
|
load_mod({module, Name, Opts}) ->
|
||||||
Mod = list_to_atom("emqttd_mod_" ++ atom_to_list(Name)),
|
Mod = list_to_atom("emqttd_mod_" ++ atom_to_list(Name)),
|
||||||
case catch Mod:load(Opts) of
|
case catch Mod:load(Opts) of
|
||||||
ok -> lager:info("Load module ~s successfully", [Name]);
|
ok -> lager:info("Load module ~s successfully", [Name]);
|
||||||
|
@ -161,7 +159,7 @@ load_mod({Name, Opts}) ->
|
||||||
|
|
||||||
%% @doc Is module enabled?
|
%% @doc Is module enabled?
|
||||||
-spec(is_mod_enabled(Name :: atom()) -> boolean()).
|
-spec(is_mod_enabled(Name :: atom()) -> boolean()).
|
||||||
is_mod_enabled(Name) -> emqttd:env(modules, Name) =/= undefined.
|
is_mod_enabled(Name) -> lists:keyfind(Name, 2, gen_conf:list(emqttd, module)).
|
||||||
|
|
||||||
%%--------------------------------------------------------------------
|
%%--------------------------------------------------------------------
|
||||||
%% Start Listeners
|
%% Start Listeners
|
||||||
|
@ -169,25 +167,27 @@ is_mod_enabled(Name) -> emqttd:env(modules, Name) =/= undefined.
|
||||||
|
|
||||||
%% @doc Start Listeners of the broker.
|
%% @doc Start Listeners of the broker.
|
||||||
-spec(start_listeners() -> any()).
|
-spec(start_listeners() -> any()).
|
||||||
start_listeners() -> lists:foreach(fun start_listener/1, emqttd:env(listeners)).
|
start_listeners() -> lists:foreach(fun start_listener/1, gen_conf:list(emqttd, listener)).
|
||||||
|
|
||||||
%% Start mqtt listener
|
%% Start mqtt listener
|
||||||
-spec(start_listener(listener()) -> any()).
|
-spec(start_listener(listener()) -> any()).
|
||||||
start_listener({mqtt, ListenOn, Opts}) -> start_listener(mqtt, ListenOn, Opts);
|
start_listener({listener, mqtt, ListenOn, Opts}) ->
|
||||||
|
start_listener(mqtt, ListenOn, Opts);
|
||||||
|
|
||||||
%% Start mqtt(SSL) listener
|
%% Start mqtt(SSL) listener
|
||||||
start_listener({mqtts, ListenOn, Opts}) -> start_listener(mqtts, ListenOn, Opts);
|
start_listener({listener, mqtts, ListenOn, Opts}) ->
|
||||||
|
start_listener(mqtts, ListenOn, Opts);
|
||||||
|
|
||||||
%% Start http listener
|
%% Start http listener
|
||||||
start_listener({http, ListenOn, Opts}) ->
|
start_listener({listener, http, ListenOn, Opts}) ->
|
||||||
mochiweb:start_http(http, ListenOn, Opts, {emqttd_http, handle_request, []});
|
mochiweb:start_http(http, ListenOn, Opts, {emqttd_http, handle_request, []});
|
||||||
|
|
||||||
%% Start https listener
|
%% Start https listener
|
||||||
start_listener({https, ListenOn, Opts}) ->
|
start_listener({listener, https, ListenOn, Opts}) ->
|
||||||
mochiweb:start_http(https, ListenOn, Opts, {emqttd_http, handle_request, []}).
|
mochiweb:start_http(https, ListenOn, Opts, {emqttd_http, handle_request, []}).
|
||||||
|
|
||||||
start_listener(Protocol, ListenOn, Opts) ->
|
start_listener(Protocol, ListenOn, Opts) ->
|
||||||
MFArgs = {emqttd_client, start_link, [emqttd:env(mqtt)]},
|
MFArgs = {emqttd_client, start_link, [emqttd_conf:mqtt()]},
|
||||||
{ok, _} = esockd:open(Protocol, ListenOn, merge_sockopts(Opts), MFArgs).
|
{ok, _} = esockd:open(Protocol, ListenOn, merge_sockopts(Opts), MFArgs).
|
||||||
|
|
||||||
merge_sockopts(Options) ->
|
merge_sockopts(Options) ->
|
||||||
|
@ -200,8 +200,22 @@ merge_sockopts(Options) ->
|
||||||
%%--------------------------------------------------------------------
|
%%--------------------------------------------------------------------
|
||||||
|
|
||||||
%% @doc Stop Listeners
|
%% @doc Stop Listeners
|
||||||
stop_listeners() -> lists:foreach(fun stop_listener/1, emqttd:env(listeners)).
|
stop_listeners() -> lists:foreach(fun stop_listener/1, gen_conf:list(listener)).
|
||||||
|
|
||||||
%% @private
|
%% @private
|
||||||
stop_listener({Protocol, ListenOn, _Opts}) -> esockd:close(Protocol, ListenOn).
|
stop_listener({listener, Protocol, ListenOn, _Opts}) -> esockd:close(Protocol, ListenOn).
|
||||||
|
|
||||||
|
-ifdef(TEST).
|
||||||
|
-include_lib("eunit/include/eunit.hrl").
|
||||||
|
merge_sockopts_test_() ->
|
||||||
|
Opts = [{acceptors, 16}, {max_clients, 512}],
|
||||||
|
?_assert(merge_sockopts(Opts) == [{sockopts, ?MQTT_SOCKOPTS} | Opts]).
|
||||||
|
|
||||||
|
load_all_mods_test_() ->
|
||||||
|
?_assert(load_all_mods() == ok).
|
||||||
|
|
||||||
|
is_mod_enabled_test_() ->
|
||||||
|
?_assert(is_mod_enabled(presence) == {module, presence, [{qos, 0}]}),
|
||||||
|
?_assert(is_mod_enabled(test) == false).
|
||||||
|
|
||||||
|
-endif.
|
||||||
|
|
|
@ -69,7 +69,8 @@ init(Opts) ->
|
||||||
{ram_copies, [node()]},
|
{ram_copies, [node()]},
|
||||||
{attributes, record_info(fields, ?AUTH_CLIENTID_TAB)}]),
|
{attributes, record_info(fields, ?AUTH_CLIENTID_TAB)}]),
|
||||||
mnesia:add_table_copy(?AUTH_CLIENTID_TAB, node(), ram_copies),
|
mnesia:add_table_copy(?AUTH_CLIENTID_TAB, node(), ram_copies),
|
||||||
load(proplists:get_value(file, Opts)),
|
Clients = load_client_from(proplists:get_value(config, Opts)),
|
||||||
|
mnesia:transaction(fun() -> [mnesia:write(C) || C<- Clients] end),
|
||||||
{ok, Opts}.
|
{ok, Opts}.
|
||||||
|
|
||||||
check(#mqtt_client{client_id = undefined}, _Password, _Opts) ->
|
check(#mqtt_client{client_id = undefined}, _Password, _Opts) ->
|
||||||
|
@ -93,32 +94,19 @@ description() -> "ClientId authentication module".
|
||||||
%% Internal functions
|
%% Internal functions
|
||||||
%%--------------------------------------------------------------------
|
%%--------------------------------------------------------------------
|
||||||
|
|
||||||
load(undefined) ->
|
load_client_from(undefined) ->
|
||||||
ok;
|
ok;
|
||||||
|
|
||||||
load(File) ->
|
load_client_from(File) ->
|
||||||
{ok, Fd} = file:open(File, [read]),
|
{ok, Clients} = file:consult(File),
|
||||||
load(Fd, file:read_line(Fd), []).
|
[client(Client) || Client <- Clients].
|
||||||
|
|
||||||
load(Fd, {ok, Line}, Clients) when is_list(Line) ->
|
client(ClientId) when is_list(ClientId) ->
|
||||||
Clients1 =
|
#mqtt_auth_clientid{client_id = list_to_binary(ClientId)};
|
||||||
case string:tokens(Line, " ") of
|
|
||||||
[ClientIdS] ->
|
|
||||||
ClientId = list_to_binary(string:strip(ClientIdS, right, $\n)),
|
|
||||||
[#mqtt_auth_clientid{client_id = ClientId} | Clients];
|
|
||||||
[ClientId, IpAddr0] ->
|
|
||||||
IpAddr = string:strip(IpAddr0, right, $\n),
|
|
||||||
[#mqtt_auth_clientid{client_id = list_to_binary(ClientId),
|
|
||||||
ipaddr = esockd_cidr:parse(IpAddr, true)} | Clients];
|
|
||||||
BadLine ->
|
|
||||||
lager:error("BadLine in clients.config: ~s", [BadLine]),
|
|
||||||
Clients
|
|
||||||
end,
|
|
||||||
load(Fd, file:read_line(Fd), Clients1);
|
|
||||||
|
|
||||||
load(Fd, eof, Clients) ->
|
client({ClientId, IpAddr}) when is_list(ClientId) ->
|
||||||
mnesia:transaction(fun() -> [mnesia:write(C) || C<- Clients] end),
|
#mqtt_auth_clientid{client_id = iolist_to_binary(ClientId),
|
||||||
file:close(Fd).
|
ipaddr = esockd_cidr:parse(IpAddr, true)}.
|
||||||
|
|
||||||
check_clientid_only(ClientId, IpAddr) ->
|
check_clientid_only(ClientId, IpAddr) ->
|
||||||
case mnesia:dirty_read(?AUTH_CLIENTID_TAB, ClientId) of
|
case mnesia:dirty_read(?AUTH_CLIENTID_TAB, ClientId) of
|
||||||
|
|
|
@ -1,77 +0,0 @@
|
||||||
%%--------------------------------------------------------------------
|
|
||||||
%% Copyright (c) 2012-2016 Feng Lee <feng@emqtt.io>.
|
|
||||||
%%
|
|
||||||
%% Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
%% you may not use this file except in compliance with the License.
|
|
||||||
%% You may obtain a copy of the License at
|
|
||||||
%%
|
|
||||||
%% http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
%%
|
|
||||||
%% Unless required by applicable law or agreed to in writing, software
|
|
||||||
%% distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
%% See the License for the specific language governing permissions and
|
|
||||||
%% limitations under the License.
|
|
||||||
%%--------------------------------------------------------------------
|
|
||||||
|
|
||||||
%% @doc LDAP Authentication Module
|
|
||||||
-module(emqttd_auth_ldap).
|
|
||||||
|
|
||||||
-include("emqttd.hrl").
|
|
||||||
|
|
||||||
-import(proplists, [get_value/2, get_value/3]).
|
|
||||||
|
|
||||||
-behaviour(emqttd_auth_mod).
|
|
||||||
|
|
||||||
-export([init/1, check/3, description/0]).
|
|
||||||
|
|
||||||
-record(state, {servers, user_dn, options}).
|
|
||||||
|
|
||||||
init(Opts) ->
|
|
||||||
Servers = get_value(servers, Opts, ["localhost"]),
|
|
||||||
Port = get_value(port, Opts, 389),
|
|
||||||
Timeout = get_value(timeout, Opts, 30),
|
|
||||||
UserDn = get_value(user_dn, Opts),
|
|
||||||
LdapOpts =
|
|
||||||
case get_value(ssl, Opts, false) of
|
|
||||||
true ->
|
|
||||||
SslOpts = get_value(sslopts, Opts),
|
|
||||||
[{port, Port}, {timeout, Timeout}, {sslopts, SslOpts}];
|
|
||||||
false ->
|
|
||||||
[{port, Port}, {timeout, Timeout}]
|
|
||||||
end,
|
|
||||||
{ok, #state{servers = Servers, user_dn = UserDn, options = LdapOpts}}.
|
|
||||||
|
|
||||||
check(#mqtt_client{username = undefined}, _Password, _State) ->
|
|
||||||
{error, username_undefined};
|
|
||||||
check(_Client, undefined, _State) ->
|
|
||||||
{error, password_undefined};
|
|
||||||
check(_Client, <<>>, _State) ->
|
|
||||||
{error, password_undefined};
|
|
||||||
check(#mqtt_client{username = Username}, Password,
|
|
||||||
#state{servers = Servers, user_dn = UserDn, options = Options}) ->
|
|
||||||
case eldap:open(Servers, Options) of
|
|
||||||
{ok, LDAP} ->
|
|
||||||
UserDn1 = fill(binary_to_list(Username), UserDn),
|
|
||||||
ldap_bind(LDAP, UserDn1, binary_to_list(Password));
|
|
||||||
{error, Reason} ->
|
|
||||||
{error, Reason}
|
|
||||||
end.
|
|
||||||
|
|
||||||
ldap_bind(LDAP, UserDn, Password) ->
|
|
||||||
case catch eldap:simple_bind(LDAP, UserDn, Password) of
|
|
||||||
ok ->
|
|
||||||
ok;
|
|
||||||
{error, invalidCredentials} ->
|
|
||||||
{error, invalid_credentials};
|
|
||||||
{error, Error} ->
|
|
||||||
{error, Error};
|
|
||||||
{'EXIT', Reason} ->
|
|
||||||
{error, Reason}
|
|
||||||
end.
|
|
||||||
|
|
||||||
fill(Username, UserDn) ->
|
|
||||||
re:replace(UserDn, "\\$u", Username, [global, {return, list}]).
|
|
||||||
|
|
||||||
description() -> "LDAP Authentication Module".
|
|
||||||
|
|
|
@ -68,7 +68,7 @@ if_enabled(Fun) ->
|
||||||
end.
|
end.
|
||||||
|
|
||||||
hint() ->
|
hint() ->
|
||||||
?PRINT_MSG("Please enable '{username, []}' authentication in etc/emqttd.config first.~n").
|
?PRINT_MSG("Please enable '{auth, username, []}' in etc/emqttd.conf first.~n").
|
||||||
|
|
||||||
%%--------------------------------------------------------------------
|
%%--------------------------------------------------------------------
|
||||||
%% API
|
%% API
|
||||||
|
@ -81,7 +81,13 @@ is_enabled() ->
|
||||||
-spec(add_user(binary(), binary()) -> ok | {error, any()}).
|
-spec(add_user(binary(), binary()) -> ok | {error, any()}).
|
||||||
add_user(Username, Password) ->
|
add_user(Username, Password) ->
|
||||||
User = #?AUTH_USERNAME_TAB{username = Username, password = hash(Password)},
|
User = #?AUTH_USERNAME_TAB{username = Username, password = hash(Password)},
|
||||||
ret(mnesia:transaction(fun mnesia:write/1, [User])).
|
ret(mnesia:transaction(fun insert_user/1, [User])).
|
||||||
|
|
||||||
|
insert_user(User = #?AUTH_USERNAME_TAB{username = Username}) ->
|
||||||
|
case mnesia:read(?AUTH_USERNAME_TAB, Username) of
|
||||||
|
[] -> mnesia:write(User);
|
||||||
|
[_|_] -> mnesia:abort(existed)
|
||||||
|
end.
|
||||||
|
|
||||||
add_default_user(Username, Password) when is_atom(Username) ->
|
add_default_user(Username, Password) when is_atom(Username) ->
|
||||||
add_default_user(atom_to_list(Username), Password);
|
add_default_user(atom_to_list(Username), Password);
|
||||||
|
@ -110,16 +116,20 @@ all_users() -> mnesia:dirty_all_keys(?AUTH_USERNAME_TAB).
|
||||||
%% emqttd_auth_mod callbacks
|
%% emqttd_auth_mod callbacks
|
||||||
%%--------------------------------------------------------------------
|
%%--------------------------------------------------------------------
|
||||||
|
|
||||||
init(DefautUsers) ->
|
init(Opts) ->
|
||||||
mnesia:create_table(?AUTH_USERNAME_TAB, [
|
mnesia:create_table(?AUTH_USERNAME_TAB, [
|
||||||
{disc_copies, [node()]},
|
{disc_copies, [node()]},
|
||||||
{attributes, record_info(fields, ?AUTH_USERNAME_TAB)}]),
|
{attributes, record_info(fields, ?AUTH_USERNAME_TAB)}]),
|
||||||
mnesia:add_table_copy(?AUTH_USERNAME_TAB, node(), disc_copies),
|
mnesia:add_table_copy(?AUTH_USERNAME_TAB, node(), disc_copies),
|
||||||
|
case proplists:get_value(passwd, Opts) of
|
||||||
|
undefined -> ok;
|
||||||
|
File -> {ok, DefaultUsers} = file:consult(File),
|
||||||
lists:foreach(fun({Username, Password}) ->
|
lists:foreach(fun({Username, Password}) ->
|
||||||
add_default_user(Username, Password)
|
add_default_user(Username, Password)
|
||||||
end, DefautUsers),
|
end, DefaultUsers)
|
||||||
|
end,
|
||||||
emqttd_ctl:register_cmd(users, {?MODULE, cli}, []),
|
emqttd_ctl:register_cmd(users, {?MODULE, cli}, []),
|
||||||
{ok, []}.
|
{ok, Opts}.
|
||||||
|
|
||||||
check(#mqtt_client{username = undefined}, _Password, _Opts) ->
|
check(#mqtt_client{username = undefined}, _Password, _Opts) ->
|
||||||
{error, username_undefined};
|
{error, username_undefined};
|
||||||
|
|
|
@ -1,153 +0,0 @@
|
||||||
%%--------------------------------------------------------------------
|
|
||||||
%% Copyright (c) 2012-2016 Feng Lee <feng@emqtt.io>.
|
|
||||||
%%
|
|
||||||
%% Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
%% you may not use this file except in compliance with the License.
|
|
||||||
%% You may obtain a copy of the License at
|
|
||||||
%%
|
|
||||||
%% http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
%%
|
|
||||||
%% Unless required by applicable law or agreed to in writing, software
|
|
||||||
%% distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
%% See the License for the specific language governing permissions and
|
|
||||||
%% limitations under the License.
|
|
||||||
%%--------------------------------------------------------------------
|
|
||||||
|
|
||||||
-module(emqttd_backend).
|
|
||||||
|
|
||||||
-include("emqttd.hrl").
|
|
||||||
|
|
||||||
-include_lib("stdlib/include/ms_transform.hrl").
|
|
||||||
|
|
||||||
%% Mnesia Callbacks
|
|
||||||
-export([mnesia/1]).
|
|
||||||
|
|
||||||
-boot_mnesia({mnesia, [boot]}).
|
|
||||||
-copy_mnesia({mnesia, [copy]}).
|
|
||||||
|
|
||||||
%% Retained Message API
|
|
||||||
-export([retain_message/1, read_messages/1, match_messages/1, delete_message/1,
|
|
||||||
expire_messages/1, retained_count/0]).
|
|
||||||
|
|
||||||
%% Static Subscription API
|
|
||||||
-export([add_subscription/1, lookup_subscriptions/1, del_subscriptions/1,
|
|
||||||
del_subscription/2]).
|
|
||||||
|
|
||||||
-record(retained_message, {topic, msg}).
|
|
||||||
|
|
||||||
%%--------------------------------------------------------------------
|
|
||||||
%% Mnesia callbacks
|
|
||||||
%%--------------------------------------------------------------------
|
|
||||||
|
|
||||||
mnesia(boot) ->
|
|
||||||
ok = emqttd_mnesia:create_table(retained_message, [
|
|
||||||
{type, ordered_set},
|
|
||||||
{disc_copies, [node()]},
|
|
||||||
{record_name, retained_message},
|
|
||||||
{attributes, record_info(fields, retained_message)},
|
|
||||||
{storage_properties, [{ets, [compressed]},
|
|
||||||
{dets, [{auto_save, 1000}]}]}]),
|
|
||||||
ok = emqttd_mnesia:create_table(backend_subscription, [
|
|
||||||
{type, bag},
|
|
||||||
{disc_copies, [node()]},
|
|
||||||
{record_name, mqtt_subscription},
|
|
||||||
{attributes, record_info(fields, mqtt_subscription)},
|
|
||||||
{storage_properties, [{ets, [compressed]},
|
|
||||||
{dets, [{auto_save, 5000}]}]}]);
|
|
||||||
|
|
||||||
mnesia(copy) ->
|
|
||||||
ok = emqttd_mnesia:copy_table(retained_message),
|
|
||||||
ok = emqttd_mnesia:copy_table(backend_subscription).
|
|
||||||
|
|
||||||
%%--------------------------------------------------------------------
|
|
||||||
%% Retained Message
|
|
||||||
%%--------------------------------------------------------------------
|
|
||||||
|
|
||||||
-spec(retain_message(mqtt_message()) -> ok).
|
|
||||||
retain_message(Msg = #mqtt_message{topic = Topic}) ->
|
|
||||||
mnesia:dirty_write(#retained_message{topic = Topic, msg = Msg}).
|
|
||||||
|
|
||||||
-spec(read_messages(binary()) -> [mqtt_message()]).
|
|
||||||
read_messages(Topic) ->
|
|
||||||
[Msg || #retained_message{msg = Msg} <- mnesia:dirty_read(retained_message, Topic)].
|
|
||||||
|
|
||||||
-spec(match_messages(binary()) -> [mqtt_message()]).
|
|
||||||
match_messages(Filter) ->
|
|
||||||
%% TODO: optimize later...
|
|
||||||
Fun = fun(#retained_message{topic = Name, msg = Msg}, Acc) ->
|
|
||||||
case emqttd_topic:match(Name, Filter) of
|
|
||||||
true -> [Msg|Acc];
|
|
||||||
false -> Acc
|
|
||||||
end
|
|
||||||
end,
|
|
||||||
mnesia:async_dirty(fun mnesia:foldl/3, [Fun, [], retained_message]).
|
|
||||||
|
|
||||||
-spec(delete_message(binary()) -> ok).
|
|
||||||
delete_message(Topic) ->
|
|
||||||
mnesia:dirty_delete(retained_message, Topic).
|
|
||||||
|
|
||||||
-spec(expire_messages(pos_integer()) -> any()).
|
|
||||||
expire_messages(Time) when is_integer(Time) ->
|
|
||||||
mnesia:transaction(
|
|
||||||
fun() ->
|
|
||||||
Match = ets:fun2ms(
|
|
||||||
fun(#retained_message{topic = Topic, msg = #mqtt_message{timestamp = {MegaSecs, Secs, _}}})
|
|
||||||
when Time > (MegaSecs * 1000000 + Secs) -> Topic
|
|
||||||
end),
|
|
||||||
Topics = mnesia:select(retained_message, Match, write),
|
|
||||||
lists:foreach(fun(<<"$SYS/", _/binary>>) -> ok; %% ignore $SYS/# messages
|
|
||||||
(Topic) -> mnesia:delete({retained_message, Topic})
|
|
||||||
end, Topics)
|
|
||||||
end).
|
|
||||||
|
|
||||||
-spec(retained_count() -> non_neg_integer()).
|
|
||||||
retained_count() ->
|
|
||||||
mnesia:table_info(retained_message, size).
|
|
||||||
|
|
||||||
%%--------------------------------------------------------------------
|
|
||||||
%% Static Subscriptions
|
|
||||||
%%--------------------------------------------------------------------
|
|
||||||
|
|
||||||
%% @doc Add a static subscription manually.
|
|
||||||
-spec(add_subscription(mqtt_subscription()) -> ok | {error, already_existed}).
|
|
||||||
add_subscription(Subscription = #mqtt_subscription{subid = SubId, topic = Topic}) ->
|
|
||||||
Pattern = match_pattern(SubId, Topic),
|
|
||||||
return(mnesia:transaction(fun() ->
|
|
||||||
case mnesia:match_object(backend_subscription, Pattern, write) of
|
|
||||||
[] ->
|
|
||||||
mnesia:write(backend_subscription, Subscription, write);
|
|
||||||
[Subscription] ->
|
|
||||||
mnesia:abort(already_existed);
|
|
||||||
[Subscription1] -> %% QoS is different
|
|
||||||
mnesia:delete_object(backend_subscription, Subscription1, write),
|
|
||||||
mnesia:write(backend_subscription, Subscription, write)
|
|
||||||
end
|
|
||||||
end)).
|
|
||||||
|
|
||||||
%% @doc Lookup static subscriptions.
|
|
||||||
-spec(lookup_subscriptions(binary()) -> list(mqtt_subscription())).
|
|
||||||
lookup_subscriptions(ClientId) when is_binary(ClientId) ->
|
|
||||||
mnesia:dirty_read(backend_subscription, ClientId).
|
|
||||||
|
|
||||||
%% @doc Delete static subscriptions by ClientId manually.
|
|
||||||
-spec(del_subscriptions(binary()) -> ok).
|
|
||||||
del_subscriptions(ClientId) when is_binary(ClientId) ->
|
|
||||||
return(mnesia:transaction(fun mnesia:delete/1, [{backend_subscription, ClientId}])).
|
|
||||||
|
|
||||||
%% @doc Delete a static subscription manually.
|
|
||||||
-spec(del_subscription(binary(), binary()) -> ok).
|
|
||||||
del_subscription(ClientId, Topic) when is_binary(ClientId) andalso is_binary(Topic) ->
|
|
||||||
return(mnesia:transaction(fun del_subscription_/1, [match_pattern(ClientId, Topic)])).
|
|
||||||
|
|
||||||
del_subscription_(Pattern) ->
|
|
||||||
lists:foreach(fun(Subscription) ->
|
|
||||||
mnesia:delete_object(backend_subscription, Subscription, write)
|
|
||||||
end, mnesia:match_object(backend_subscription, Pattern, write)).
|
|
||||||
|
|
||||||
match_pattern(SubId, Topic) ->
|
|
||||||
#mqtt_subscription{subid = SubId, topic = Topic, qos = '_'}.
|
|
||||||
|
|
||||||
return({atomic, ok}) -> ok;
|
|
||||||
return({aborted, Reason}) -> {error, Reason}.
|
|
||||||
|
|
|
@ -18,56 +18,25 @@
|
||||||
|
|
||||||
-behavior(supervisor).
|
-behavior(supervisor).
|
||||||
|
|
||||||
-export([start_link/0, bridges/0, start_bridge/2, start_bridge/3, stop_bridge/2]).
|
-export([start_link/3]).
|
||||||
|
|
||||||
-export([init/1]).
|
-export([init/1]).
|
||||||
|
|
||||||
-define(BRIDGE_ID(Node, Topic), {bridge, Node, Topic}).
|
|
||||||
|
|
||||||
%%--------------------------------------------------------------------
|
%%--------------------------------------------------------------------
|
||||||
%% API
|
%% API
|
||||||
%%--------------------------------------------------------------------
|
%%--------------------------------------------------------------------
|
||||||
|
|
||||||
%% @doc Start bridge supervisor
|
%% @doc Start bridge supervisor
|
||||||
start_link() ->
|
-spec(start_link(atom(), binary(), [emqttd_bridge:option()]) -> {ok, pid()} | {error, any()}).
|
||||||
supervisor:start_link({local, ?MODULE}, ?MODULE, []).
|
start_link(Node, Topic, Options) ->
|
||||||
|
supervisor:start_link(?MODULE, [Node, Topic, Options]).
|
||||||
%% @doc List all bridges
|
|
||||||
-spec(bridges() -> [{tuple(), pid()}]).
|
|
||||||
bridges() ->
|
|
||||||
[{{Node, Topic}, Pid} || {?BRIDGE_ID(Node, Topic), Pid, worker, _}
|
|
||||||
<- supervisor:which_children(?MODULE)].
|
|
||||||
|
|
||||||
%% @doc Start a bridge
|
|
||||||
-spec(start_bridge(atom(), binary()) -> {ok, pid()} | {error, any()}).
|
|
||||||
start_bridge(Node, Topic) when is_atom(Node) andalso is_binary(Topic) ->
|
|
||||||
start_bridge(Node, Topic, []).
|
|
||||||
|
|
||||||
-spec(start_bridge(atom(), binary(), [emqttd_bridge:option()]) -> {ok, pid()} | {error, any()}).
|
|
||||||
start_bridge(Node, _Topic, _Options) when Node =:= node() ->
|
|
||||||
{error, bridge_to_self};
|
|
||||||
start_bridge(Node, Topic, Options) when is_atom(Node) andalso is_binary(Topic) ->
|
|
||||||
Options1 = emqttd_opts:merge(emqttd_broker:env(bridge), Options),
|
|
||||||
supervisor:start_child(?MODULE, bridge_spec(Node, Topic, Options1)).
|
|
||||||
|
|
||||||
%% @doc Stop a bridge
|
|
||||||
-spec(stop_bridge(atom(), binary()) -> {ok, pid()} | ok).
|
|
||||||
stop_bridge(Node, Topic) when is_atom(Node) andalso is_binary(Topic) ->
|
|
||||||
ChildId = ?BRIDGE_ID(Node, Topic),
|
|
||||||
case supervisor:terminate_child(?MODULE, ChildId) of
|
|
||||||
ok -> supervisor:delete_child(?MODULE, ChildId);
|
|
||||||
Error -> Error
|
|
||||||
end.
|
|
||||||
|
|
||||||
%%--------------------------------------------------------------------
|
%%--------------------------------------------------------------------
|
||||||
%% Supervisor callbacks
|
%% Supervisor callbacks
|
||||||
%%--------------------------------------------------------------------
|
%%--------------------------------------------------------------------
|
||||||
|
|
||||||
init([]) ->
|
init([Node, Topic, Options]) ->
|
||||||
{ok, {{one_for_one, 10, 100}, []}}.
|
{ok, {{one_for_all, 10, 100},
|
||||||
|
[{bridge, {emqttd_bridge, start_link, [Node, Topic, Options]},
|
||||||
bridge_spec(Node, Topic, Options) ->
|
transient, 10000, worker, [emqttd_bridge]}]}}.
|
||||||
ChildId = ?BRIDGE_ID(Node, Topic),
|
|
||||||
{ChildId, {emqttd_bridge, start_link, [Node, Topic, Options]},
|
|
||||||
transient, 10000, worker, [emqttd_bridge]}.
|
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,76 @@
|
||||||
|
%%--------------------------------------------------------------------
|
||||||
|
%% Copyright (c) 2012-2016 Feng Lee <feng@emqtt.io>.
|
||||||
|
%%
|
||||||
|
%% Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
%% you may not use this file except in compliance with the License.
|
||||||
|
%% You may obtain a copy of the License at
|
||||||
|
%%
|
||||||
|
%% http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
%%
|
||||||
|
%% Unless required by applicable law or agreed to in writing, software
|
||||||
|
%% distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
%% See the License for the specific language governing permissions and
|
||||||
|
%% limitations under the License.
|
||||||
|
%%--------------------------------------------------------------------
|
||||||
|
|
||||||
|
-module(emqttd_bridge_sup_sup).
|
||||||
|
|
||||||
|
-behavior(supervisor).
|
||||||
|
|
||||||
|
-export([start_link/0, bridges/0, start_bridge/2, start_bridge/3, stop_bridge/2]).
|
||||||
|
|
||||||
|
-export([init/1]).
|
||||||
|
|
||||||
|
-define(CHILD_ID(Node, Topic), {bridge_sup, Node, Topic}).
|
||||||
|
|
||||||
|
start_link() ->
|
||||||
|
supervisor:start_link({local, ?MODULE}, ?MODULE, []).
|
||||||
|
|
||||||
|
%%--------------------------------------------------------------------
|
||||||
|
%% API
|
||||||
|
%%--------------------------------------------------------------------
|
||||||
|
|
||||||
|
%% @doc List all bridges
|
||||||
|
-spec(bridges() -> [{node(), binary(), pid()}]).
|
||||||
|
bridges() ->
|
||||||
|
[{Node, Topic, Pid} || {?CHILD_ID(Node, Topic), Pid, supervisor, _}
|
||||||
|
<- supervisor:which_children(?MODULE)].
|
||||||
|
|
||||||
|
%% @doc Start a bridge
|
||||||
|
-spec(start_bridge(atom(), binary()) -> {ok, pid()} | {error, any()}).
|
||||||
|
start_bridge(Node, Topic) when is_atom(Node) andalso is_binary(Topic) ->
|
||||||
|
start_bridge(Node, Topic, []).
|
||||||
|
|
||||||
|
-spec(start_bridge(atom(), binary(), [emqttd_bridge:option()]) -> {ok, pid()} | {error, any()}).
|
||||||
|
start_bridge(Node, _Topic, _Options) when Node =:= node() ->
|
||||||
|
{error, bridge_to_self};
|
||||||
|
start_bridge(Node, Topic, Options) when is_atom(Node) andalso is_binary(Topic) ->
|
||||||
|
Options1 = emqttd_opts:merge(emqttd_conf:bridge(), Options),
|
||||||
|
supervisor:start_child(?MODULE, bridge_spec(Node, Topic, Options1)).
|
||||||
|
|
||||||
|
%% @doc Stop a bridge
|
||||||
|
-spec(stop_bridge(atom(), binary()) -> {ok, pid()} | ok).
|
||||||
|
stop_bridge(Node, Topic) when is_atom(Node) andalso is_binary(Topic) ->
|
||||||
|
ChildId = ?CHILD_ID(Node, Topic),
|
||||||
|
case supervisor:terminate_child(?MODULE, ChildId) of
|
||||||
|
ok -> supervisor:delete_child(?MODULE, ChildId);
|
||||||
|
Error -> Error
|
||||||
|
end.
|
||||||
|
|
||||||
|
%%--------------------------------------------------------------------
|
||||||
|
%% Supervisor callbacks
|
||||||
|
%%--------------------------------------------------------------------
|
||||||
|
|
||||||
|
init([]) ->
|
||||||
|
{ok, {{one_for_one, 10, 100}, []}}.
|
||||||
|
|
||||||
|
bridge_spec(Node, Topic, Options) ->
|
||||||
|
SupMod = sup_mod(emqttd:adapter(bridge)),
|
||||||
|
{?CHILD_ID(Node, Topic),
|
||||||
|
{SupMod, start_link, [Node, Topic, Options]},
|
||||||
|
permanent, infinity, supervisor, [SupMod]}.
|
||||||
|
|
||||||
|
sup_mod(Adaper) ->
|
||||||
|
list_to_atom(atom_to_list(Adaper) ++ "_sup").
|
||||||
|
|
|
@ -29,7 +29,7 @@
|
||||||
-export([subscribe/1, notify/2]).
|
-export([subscribe/1, notify/2]).
|
||||||
|
|
||||||
%% Broker API
|
%% Broker API
|
||||||
-export([env/1, version/0, uptime/0, datetime/0, sysdescr/0]).
|
-export([version/0, uptime/0, datetime/0, sysdescr/0]).
|
||||||
|
|
||||||
%% Tick API
|
%% Tick API
|
||||||
-export([start_tick/1, stop_tick/1]).
|
-export([start_tick/1, stop_tick/1]).
|
||||||
|
@ -71,10 +71,6 @@ subscribe(EventType) ->
|
||||||
notify(EventType, Event) ->
|
notify(EventType, Event) ->
|
||||||
gproc:send({p, l, {broker, EventType}}, {notify, EventType, self(), Event}).
|
gproc:send({p, l, {broker, EventType}}, {notify, EventType, self(), Event}).
|
||||||
|
|
||||||
%% @doc Get broker env
|
|
||||||
env(Name) ->
|
|
||||||
proplists:get_value(Name, emqttd:env(broker)).
|
|
||||||
|
|
||||||
%% @doc Get broker version
|
%% @doc Get broker version
|
||||||
-spec(version() -> string()).
|
-spec(version() -> string()).
|
||||||
version() ->
|
version() ->
|
||||||
|
@ -99,7 +95,7 @@ datetime() ->
|
||||||
|
|
||||||
%% @doc Start a tick timer
|
%% @doc Start a tick timer
|
||||||
start_tick(Msg) ->
|
start_tick(Msg) ->
|
||||||
start_tick(timer:seconds(env(sys_interval)), Msg).
|
start_tick(timer:seconds(emqttd:conf(broker_sys_interval, 60)), Msg).
|
||||||
|
|
||||||
start_tick(0, _Msg) ->
|
start_tick(0, _Msg) ->
|
||||||
undefined;
|
undefined;
|
||||||
|
@ -119,9 +115,6 @@ stop_tick(TRef) ->
|
||||||
init([]) ->
|
init([]) ->
|
||||||
emqttd_time:seed(),
|
emqttd_time:seed(),
|
||||||
ets:new(?BROKER_TAB, [set, public, named_table]),
|
ets:new(?BROKER_TAB, [set, public, named_table]),
|
||||||
% Create $SYS Topics
|
|
||||||
emqttd:create(topic, <<"$SYS/brokers">>),
|
|
||||||
[ok = create_topic(Topic) || Topic <- ?SYSTOP_BROKERS],
|
|
||||||
% Tick
|
% Tick
|
||||||
{ok, #state{started_at = os:timestamp(),
|
{ok, #state{started_at = os:timestamp(),
|
||||||
heartbeat = start_tick(1000, heartbeat),
|
heartbeat = start_tick(1000, heartbeat),
|
||||||
|
@ -164,9 +157,6 @@ code_change(_OldVsn, State, _Extra) ->
|
||||||
%% Internal functions
|
%% Internal functions
|
||||||
%%--------------------------------------------------------------------
|
%%--------------------------------------------------------------------
|
||||||
|
|
||||||
create_topic(Topic) ->
|
|
||||||
emqttd:create(topic, emqttd_topic:systop(Topic)).
|
|
||||||
|
|
||||||
retain(brokers) ->
|
retain(brokers) ->
|
||||||
Payload = list_to_binary(string:join([atom_to_list(N) ||
|
Payload = list_to_binary(string:join([atom_to_list(N) ||
|
||||||
N <- emqttd_mnesia:running_nodes()], ",")),
|
N <- emqttd_mnesia:running_nodes()], ",")),
|
||||||
|
|
|
@ -170,24 +170,20 @@ if_client(ClientId, Fun) ->
|
||||||
%%--------------------------------------------------------------------
|
%%--------------------------------------------------------------------
|
||||||
%% @doc Sessions Command
|
%% @doc Sessions Command
|
||||||
sessions(["list"]) ->
|
sessions(["list"]) ->
|
||||||
[sessions(["list", Type]) || Type <- ["persistent", "transient"]];
|
dump(mqtt_local_session);
|
||||||
|
|
||||||
|
%% performance issue?
|
||||||
sessions(["list", "persistent"]) ->
|
sessions(["list", "persistent"]) ->
|
||||||
dump(mqtt_persistent_session);
|
lists:foreach(fun print/1, ets:match_object(mqtt_local_session, {'_', false, '_', '_'}));
|
||||||
|
|
||||||
|
%% performance issue?
|
||||||
sessions(["list", "transient"]) ->
|
sessions(["list", "transient"]) ->
|
||||||
dump(mqtt_transient_session);
|
lists:foreach(fun print/1, ets:match_object(mqtt_local_session, {'_', true, '_', '_'}));
|
||||||
|
|
||||||
sessions(["show", ClientId]) ->
|
sessions(["show", ClientId]) ->
|
||||||
MP = {{bin(ClientId), '_'}, '_'},
|
case ets:lookup(mqtt_local_session, bin(ClientId)) of
|
||||||
case {ets:match_object(mqtt_transient_session, MP),
|
[] -> ?PRINT_MSG("Not Found.~n");
|
||||||
ets:match_object(mqtt_persistent_session, MP)} of
|
[SessInfo] -> print(SessInfo)
|
||||||
{[], []} ->
|
|
||||||
?PRINT_MSG("Not Found.~n");
|
|
||||||
{[SessInfo], _} ->
|
|
||||||
print(SessInfo);
|
|
||||||
{_, [SessInfo]} ->
|
|
||||||
print(SessInfo)
|
|
||||||
end;
|
end;
|
||||||
|
|
||||||
sessions(_) ->
|
sessions(_) ->
|
||||||
|
@ -199,10 +195,10 @@ sessions(_) ->
|
||||||
%%--------------------------------------------------------------------
|
%%--------------------------------------------------------------------
|
||||||
%% @doc Routes Command
|
%% @doc Routes Command
|
||||||
routes(["list"]) ->
|
routes(["list"]) ->
|
||||||
if_could_print(route, fun print/1);
|
if_could_print(mqtt_route, fun print/1);
|
||||||
|
|
||||||
routes(["show", Topic]) ->
|
routes(["show", Topic]) ->
|
||||||
print(mnesia:dirty_read(route, bin(Topic)));
|
print(mnesia:dirty_read(mqtt_route, bin(Topic)));
|
||||||
|
|
||||||
routes(_) ->
|
routes(_) ->
|
||||||
?USAGE([{"routes list", "List all routes"},
|
?USAGE([{"routes list", "List all routes"},
|
||||||
|
@ -211,54 +207,58 @@ routes(_) ->
|
||||||
%%--------------------------------------------------------------------
|
%%--------------------------------------------------------------------
|
||||||
%% @doc Topics Command
|
%% @doc Topics Command
|
||||||
topics(["list"]) ->
|
topics(["list"]) ->
|
||||||
if_could_print(topic, fun print/1);
|
lists:foreach(fun(Topic) -> ?PRINT("~s~n", [Topic]) end, emqttd:topics());
|
||||||
|
|
||||||
topics(["show", Topic]) ->
|
topics(["show", Topic]) ->
|
||||||
print(mnesia:dirty_read(topic, bin(Topic)));
|
print(mnesia:dirty_read(mqtt_route, bin(Topic)));
|
||||||
|
|
||||||
topics(_) ->
|
topics(_) ->
|
||||||
?USAGE([{"topics list", "List all topics"},
|
?USAGE([{"topics list", "List all topics"},
|
||||||
{"topics show <Topic>", "Show a topic"}]).
|
{"topics show <Topic>", "Show a topic"}]).
|
||||||
|
|
||||||
subscriptions(["list"]) ->
|
subscriptions(["list"]) ->
|
||||||
if_could_print(subscription, fun print/1);
|
lists:foreach(fun(Subscription) ->
|
||||||
|
print(subscription, Subscription)
|
||||||
subscriptions(["list", "static"]) ->
|
end, []); %%emqttd:subscriptions());
|
||||||
if_could_print(backend_subscription, fun print/1);
|
|
||||||
|
|
||||||
subscriptions(["show", ClientId]) ->
|
subscriptions(["show", ClientId]) ->
|
||||||
case mnesia:dirty_read(subscription, bin(ClientId)) of
|
case ets:lookup(mqtt_subscription, bin(ClientId)) of
|
||||||
[] -> ?PRINT_MSG("Not Found.~n");
|
[] -> ?PRINT_MSG("Not Found.~n");
|
||||||
Records -> print(Records)
|
Records -> [print(subscription, Subscription) || Subscription <- Records]
|
||||||
end;
|
end;
|
||||||
|
|
||||||
subscriptions(["add", ClientId, Topic, QoS]) ->
|
%%
|
||||||
Add = fun(IntQos) ->
|
%% subscriptions(["add", ClientId, Topic, QoS]) ->
|
||||||
Subscription = #mqtt_subscription{subid = bin(ClientId),
|
%% Add = fun(IntQos) ->
|
||||||
topic = bin(Topic),
|
%% Subscription = #mqtt_subscription{subid = bin(ClientId),
|
||||||
qos = IntQos},
|
%% topic = bin(Topic),
|
||||||
case emqttd_backend:add_subscription(Subscription) of
|
%% qos = IntQos},
|
||||||
ok ->
|
%% case emqttd_backend:add_subscription(Subscription) of
|
||||||
?PRINT_MSG("ok~n");
|
%% ok ->
|
||||||
{error, already_existed} ->
|
%% ?PRINT_MSG("ok~n");
|
||||||
?PRINT_MSG("Error: already existed~n");
|
%% {error, already_existed} ->
|
||||||
{error, Reason} ->
|
%% ?PRINT_MSG("Error: already existed~n");
|
||||||
?PRINT("Error: ~p~n", [Reason])
|
%% {error, Reason} ->
|
||||||
end
|
%% ?PRINT("Error: ~p~n", [Reason])
|
||||||
end,
|
%% end
|
||||||
if_valid_qos(QoS, Add);
|
%% end,
|
||||||
|
%% if_valid_qos(QoS, Add);
|
||||||
|
%%
|
||||||
|
|
||||||
subscriptions(["del", ClientId]) ->
|
%%
|
||||||
Ok = emqttd_backend:del_subscriptions(bin(ClientId)),
|
%% subscriptions(["del", ClientId]) ->
|
||||||
?PRINT("~p~n", [Ok]);
|
%% Ok = emqttd_backend:del_subscriptions(bin(ClientId)),
|
||||||
|
%% ?PRINT("~p~n", [Ok]);
|
||||||
|
%%
|
||||||
|
|
||||||
subscriptions(["del", ClientId, Topic]) ->
|
%%
|
||||||
Ok = emqttd_backend:del_subscription(bin(ClientId), bin(Topic)),
|
%% subscriptions(["del", ClientId, Topic]) ->
|
||||||
?PRINT("~p~n", [Ok]);
|
%% Ok = emqttd_backend:del_subscription(bin(ClientId), bin(Topic)),
|
||||||
|
%% ?PRINT("~p~n", [Ok]);
|
||||||
|
%%
|
||||||
|
|
||||||
subscriptions(_) ->
|
subscriptions(_) ->
|
||||||
?USAGE([{"subscriptions list", "List all subscriptions"},
|
?USAGE([{"subscriptions list", "List all subscriptions"},
|
||||||
{"subscriptions list static", "List all static subscriptions"},
|
|
||||||
{"subscriptions show <ClientId>", "Show subscriptions of a client"},
|
{"subscriptions show <ClientId>", "Show subscriptions of a client"},
|
||||||
{"subscriptions add <ClientId> <Topic> <QoS>", "Add a static subscription manually"},
|
{"subscriptions add <ClientId> <Topic> <QoS>", "Add a static subscription manually"},
|
||||||
{"subscriptions del <ClientId>", "Delete static subscriptions manually"},
|
{"subscriptions del <ClientId>", "Delete static subscriptions manually"},
|
||||||
|
@ -310,7 +310,7 @@ plugins(_) ->
|
||||||
bridges(["list"]) ->
|
bridges(["list"]) ->
|
||||||
foreach(fun({{Node, Topic}, _Pid}) ->
|
foreach(fun({{Node, Topic}, _Pid}) ->
|
||||||
?PRINT("bridge: ~s--~s-->~s~n", [node(), Topic, Node])
|
?PRINT("bridge: ~s--~s-->~s~n", [node(), Topic, Node])
|
||||||
end, emqttd_bridge_sup:bridges());
|
end, emqttd_bridge_sup_sup:bridges());
|
||||||
|
|
||||||
bridges(["options"]) ->
|
bridges(["options"]) ->
|
||||||
?PRINT_MSG("Options:~n"),
|
?PRINT_MSG("Options:~n"),
|
||||||
|
@ -322,20 +322,20 @@ bridges(["options"]) ->
|
||||||
?PRINT_MSG(" qos=2,prefix=abc/,suffix=/yxz,queue=1000~n");
|
?PRINT_MSG(" qos=2,prefix=abc/,suffix=/yxz,queue=1000~n");
|
||||||
|
|
||||||
bridges(["start", SNode, Topic]) ->
|
bridges(["start", SNode, Topic]) ->
|
||||||
case emqttd_bridge_sup:start_bridge(list_to_atom(SNode), list_to_binary(Topic)) of
|
case emqttd_bridge_sup_sup:start_bridge(list_to_atom(SNode), list_to_binary(Topic)) of
|
||||||
{ok, _} -> ?PRINT_MSG("bridge is started.~n");
|
{ok, _} -> ?PRINT_MSG("bridge is started.~n");
|
||||||
{error, Error} -> ?PRINT("error: ~p~n", [Error])
|
{error, Error} -> ?PRINT("error: ~p~n", [Error])
|
||||||
end;
|
end;
|
||||||
|
|
||||||
bridges(["start", SNode, Topic, OptStr]) ->
|
bridges(["start", SNode, Topic, OptStr]) ->
|
||||||
Opts = parse_opts(bridge, OptStr),
|
Opts = parse_opts(bridge, OptStr),
|
||||||
case emqttd_bridge_sup:start_bridge(list_to_atom(SNode), list_to_binary(Topic), Opts) of
|
case emqttd_bridge_sup_sup:start_bridge(list_to_atom(SNode), list_to_binary(Topic), Opts) of
|
||||||
{ok, _} -> ?PRINT_MSG("bridge is started.~n");
|
{ok, _} -> ?PRINT_MSG("bridge is started.~n");
|
||||||
{error, Error} -> ?PRINT("error: ~p~n", [Error])
|
{error, Error} -> ?PRINT("error: ~p~n", [Error])
|
||||||
end;
|
end;
|
||||||
|
|
||||||
bridges(["stop", SNode, Topic]) ->
|
bridges(["stop", SNode, Topic]) ->
|
||||||
case emqttd_bridge_sup:stop_bridge(list_to_atom(SNode), list_to_binary(Topic)) of
|
case emqttd_bridge_sup_sup:stop_bridge(list_to_atom(SNode), list_to_binary(Topic)) of
|
||||||
ok -> ?PRINT_MSG("bridge is stopped.~n");
|
ok -> ?PRINT_MSG("bridge is stopped.~n");
|
||||||
{error, Error} -> ?PRINT("error: ~p~n", [Error])
|
{error, Error} -> ?PRINT("error: ~p~n", [Error])
|
||||||
end;
|
end;
|
||||||
|
@ -491,13 +491,13 @@ print(Routes = [#mqtt_route{topic = Topic} | _]) ->
|
||||||
Nodes = [atom_to_list(Node) || #mqtt_route{node = Node} <- Routes],
|
Nodes = [atom_to_list(Node) || #mqtt_route{node = Node} <- Routes],
|
||||||
?PRINT("~s -> ~s~n", [Topic, string:join(Nodes, ",")]);
|
?PRINT("~s -> ~s~n", [Topic, string:join(Nodes, ",")]);
|
||||||
|
|
||||||
print(Subscriptions = [#mqtt_subscription{subid = ClientId} | _]) ->
|
%% print(Subscriptions = [#mqtt_subscription{subid = ClientId} | _]) ->
|
||||||
TopicTable = [io_lib:format("~s:~w", [Topic, Qos])
|
%% TopicTable = [io_lib:format("~s:~w", [Topic, Qos])
|
||||||
|| #mqtt_subscription{topic = Topic, qos = Qos} <- Subscriptions],
|
%% || #mqtt_subscription{topic = Topic, qos = Qos} <- Subscriptions],
|
||||||
?PRINT("~s -> ~s~n", [ClientId, string:join(TopicTable, ",")]);
|
%% ?PRINT("~s -> ~s~n", [ClientId, string:join(TopicTable, ",")]);
|
||||||
|
|
||||||
print(Topics = [#mqtt_topic{}|_]) ->
|
%% print(Topics = [#mqtt_topic{}|_]) ->
|
||||||
foreach(fun print/1, Topics);
|
%% foreach(fun print/1, Topics);
|
||||||
|
|
||||||
print(#mqtt_plugin{name = Name, version = Ver, descr = Descr, active = Active}) ->
|
print(#mqtt_plugin{name = Name, version = Ver, descr = Descr, active = Active}) ->
|
||||||
?PRINT("Plugin(~s, version=~s, description=~s, active=~s)~n",
|
?PRINT("Plugin(~s, version=~s, description=~s, active=~s)~n",
|
||||||
|
@ -509,15 +509,14 @@ print(#mqtt_client{client_id = ClientId, clean_sess = CleanSess, username = User
|
||||||
[ClientId, CleanSess, Username, emqttd_net:format(Peername),
|
[ClientId, CleanSess, Username, emqttd_net:format(Peername),
|
||||||
emqttd_time:now_to_secs(ConnectedAt)]);
|
emqttd_time:now_to_secs(ConnectedAt)]);
|
||||||
|
|
||||||
print(#mqtt_topic{topic = Topic, flags = Flags}) ->
|
%% print(#mqtt_topic{topic = Topic, flags = Flags}) ->
|
||||||
?PRINT("~s: ~s~n", [Topic, string:join([atom_to_list(F) || F <- Flags], ",")]);
|
%% ?PRINT("~s: ~s~n", [Topic, string:join([atom_to_list(F) || F <- Flags], ",")]);
|
||||||
|
|
||||||
print(#mqtt_route{topic = Topic, node = Node}) ->
|
print(#mqtt_route{topic = Topic, node = Node}) ->
|
||||||
?PRINT("~s -> ~s~n", [Topic, Node]);
|
?PRINT("~s -> ~s~n", [Topic, Node]);
|
||||||
|
|
||||||
print({{ClientId, _ClientPid}, SessInfo}) ->
|
print({ClientId, _ClientPid, CleanSess, SessInfo}) ->
|
||||||
InfoKeys = [clean_sess,
|
InfoKeys = [max_inflight,
|
||||||
max_inflight,
|
|
||||||
inflight_queue,
|
inflight_queue,
|
||||||
message_queue,
|
message_queue,
|
||||||
message_dropped,
|
message_dropped,
|
||||||
|
@ -529,7 +528,12 @@ print({{ClientId, _ClientPid}, SessInfo}) ->
|
||||||
"message_queue=~w, message_dropped=~w, "
|
"message_queue=~w, message_dropped=~w, "
|
||||||
"awaiting_rel=~w, awaiting_ack=~w, awaiting_comp=~w, "
|
"awaiting_rel=~w, awaiting_ack=~w, awaiting_comp=~w, "
|
||||||
"created_at=~w)~n",
|
"created_at=~w)~n",
|
||||||
[ClientId | [format(Key, get_value(Key, SessInfo)) || Key <- InfoKeys]]).
|
[ClientId, CleanSess | [format(Key, get_value(Key, SessInfo)) || Key <- InfoKeys]]).
|
||||||
|
|
||||||
|
print(subscription, {Sub, Topic, Opts}) when is_pid(Sub) ->
|
||||||
|
?PRINT("~p -> ~s: ~p~n", [Sub, Topic, Opts]);
|
||||||
|
print(subscription, {Sub, Topic, Opts}) ->
|
||||||
|
?PRINT("~s -> ~s: ~p~n", [Sub, Topic, Opts]).
|
||||||
|
|
||||||
format(created_at, Val) ->
|
format(created_at, Val) ->
|
||||||
emqttd_time:now_to_secs(Val);
|
emqttd_time:now_to_secs(Val);
|
||||||
|
|
|
@ -39,7 +39,7 @@
|
||||||
%% Client State
|
%% Client State
|
||||||
-record(client_state, {connection, connname, peername, peerhost, peerport,
|
-record(client_state, {connection, connname, peername, peerhost, peerport,
|
||||||
await_recv, conn_state, rate_limit, parser_fun,
|
await_recv, conn_state, rate_limit, parser_fun,
|
||||||
proto_state, packet_opts, keepalive}).
|
proto_state, packet_opts, keepalive, mountpoint}).
|
||||||
|
|
||||||
-define(INFO_KEYS, [peername, peerhost, peerport, await_recv, conn_state]).
|
-define(INFO_KEYS, [peername, peerhost, peerport, await_recv, conn_state]).
|
||||||
|
|
||||||
|
@ -87,16 +87,20 @@ init([OriginConn, MqttEnv]) ->
|
||||||
end,
|
end,
|
||||||
ConnName = esockd_net:format(PeerName),
|
ConnName = esockd_net:format(PeerName),
|
||||||
Self = self(),
|
Self = self(),
|
||||||
SendFun = fun(Data) ->
|
|
||||||
|
%% Send Packet...
|
||||||
|
SendFun = fun(Packet) ->
|
||||||
|
Data = emqttd_serializer:serialize(Packet),
|
||||||
|
?LOG(debug, "SEND ~p", [Data], #client_state{connname = ConnName}),
|
||||||
|
emqttd_metrics:inc('bytes/sent', size(Data)),
|
||||||
try Connection:async_send(Data) of
|
try Connection:async_send(Data) of
|
||||||
true -> ok
|
true -> ok
|
||||||
catch
|
catch
|
||||||
error:Error -> Self ! {shutdown, Error}
|
error:Error -> Self ! {shutdown, Error}
|
||||||
end
|
end
|
||||||
end,
|
end,
|
||||||
PktOpts = proplists:get_value(packet, MqttEnv),
|
ParserFun = emqttd_parser:new(MqttEnv),
|
||||||
ParserFun = emqttd_parser:new(PktOpts),
|
ProtoState = emqttd_protocol:init(PeerName, SendFun, MqttEnv),
|
||||||
ProtoState = emqttd_protocol:init(PeerName, SendFun, PktOpts),
|
|
||||||
RateLimit = proplists:get_value(rate_limit, Connection:opts()),
|
RateLimit = proplists:get_value(rate_limit, Connection:opts()),
|
||||||
State = run_socket(#client_state{connection = Connection,
|
State = run_socket(#client_state{connection = Connection,
|
||||||
connname = ConnName,
|
connname = ConnName,
|
||||||
|
@ -108,9 +112,8 @@ init([OriginConn, MqttEnv]) ->
|
||||||
rate_limit = RateLimit,
|
rate_limit = RateLimit,
|
||||||
parser_fun = ParserFun,
|
parser_fun = ParserFun,
|
||||||
proto_state = ProtoState,
|
proto_state = ProtoState,
|
||||||
packet_opts = PktOpts}),
|
packet_opts = MqttEnv}),
|
||||||
ClientOpts = proplists:get_value(client, MqttEnv),
|
IdleTimout = proplists:get_value(client_idle_timeout, MqttEnv, 30),
|
||||||
IdleTimout = proplists:get_value(idle_timeout, ClientOpts, 10),
|
|
||||||
gen_server:enter_loop(?MODULE, [], State, timer:seconds(IdleTimout)).
|
gen_server:enter_loop(?MODULE, [], State, timer:seconds(IdleTimout)).
|
||||||
|
|
||||||
handle_call(session, _From, State = #client_state{proto_state = ProtoState}) ->
|
handle_call(session, _From, State = #client_state{proto_state = ProtoState}) ->
|
||||||
|
|
|
@ -82,6 +82,5 @@ remove(Node) ->
|
||||||
end.
|
end.
|
||||||
|
|
||||||
%% @doc Cluster status
|
%% @doc Cluster status
|
||||||
status() ->
|
status() -> emqttd_mnesia:cluster_status().
|
||||||
emqttd_mnesia:cluster_status().
|
|
||||||
|
|
||||||
|
|
|
@ -26,7 +26,7 @@
|
||||||
%% API Exports
|
%% API Exports
|
||||||
-export([start_link/3]).
|
-export([start_link/3]).
|
||||||
|
|
||||||
-export([lookup/1, lookup_proc/1, register/1, unregister/1]).
|
-export([lookup/1, lookup_proc/1, reg/1, unreg/1]).
|
||||||
|
|
||||||
%% gen_server Function Exports
|
%% gen_server Function Exports
|
||||||
-export([init/1, handle_call/3, handle_cast/2, handle_info/2,
|
-export([init/1, handle_call/3, handle_cast/2, handle_info/2,
|
||||||
|
@ -44,23 +44,17 @@
|
||||||
%%--------------------------------------------------------------------
|
%%--------------------------------------------------------------------
|
||||||
|
|
||||||
%% @doc Start Client Manager
|
%% @doc Start Client Manager
|
||||||
-spec(start_link(Pool, Id, StatsFun) -> {ok, pid()} | ignore | {error, any()} when
|
-spec(start_link(atom(), pos_integer(), fun()) -> {ok, pid()} | ignore | {error, any()}).
|
||||||
Pool :: atom(),
|
|
||||||
Id :: pos_integer(),
|
|
||||||
StatsFun :: fun()).
|
|
||||||
start_link(Pool, Id, StatsFun) ->
|
start_link(Pool, Id, StatsFun) ->
|
||||||
gen_server2:start_link(?MODULE, [Pool, Id, StatsFun], []).
|
gen_server2:start_link(?MODULE, [Pool, Id, StatsFun], []).
|
||||||
|
|
||||||
%% @doc Lookup Client by ClientId
|
%% @doc Lookup Client by ClientId
|
||||||
-spec(lookup(ClientId :: binary()) -> mqtt_client() | undefined).
|
-spec(lookup(binary()) -> mqtt_client() | undefined).
|
||||||
lookup(ClientId) when is_binary(ClientId) ->
|
lookup(ClientId) when is_binary(ClientId) ->
|
||||||
case ets:lookup(mqtt_client, ClientId) of
|
case ets:lookup(mqtt_client, ClientId) of [Client] -> Client; [] -> undefined end.
|
||||||
[Client] -> Client;
|
|
||||||
[] -> undefined
|
|
||||||
end.
|
|
||||||
|
|
||||||
%% @doc Lookup client pid by clientId
|
%% @doc Lookup client pid by clientId
|
||||||
-spec(lookup_proc(ClientId :: binary()) -> pid() | undefined).
|
-spec(lookup_proc(binary()) -> pid() | undefined).
|
||||||
lookup_proc(ClientId) when is_binary(ClientId) ->
|
lookup_proc(ClientId) when is_binary(ClientId) ->
|
||||||
try ets:lookup_element(mqtt_client, ClientId, #mqtt_client.client_pid)
|
try ets:lookup_element(mqtt_client, ClientId, #mqtt_client.client_pid)
|
||||||
catch
|
catch
|
||||||
|
@ -68,14 +62,14 @@ lookup_proc(ClientId) when is_binary(ClientId) ->
|
||||||
end.
|
end.
|
||||||
|
|
||||||
%% @doc Register ClientId with Pid.
|
%% @doc Register ClientId with Pid.
|
||||||
-spec(register(Client :: mqtt_client()) -> ok).
|
-spec(reg(mqtt_client()) -> ok).
|
||||||
register(Client = #mqtt_client{client_id = ClientId}) ->
|
reg(Client = #mqtt_client{client_id = ClientId}) ->
|
||||||
gen_server2:call(pick(ClientId), {register, Client}, 120000).
|
gen_server2:call(pick(ClientId), {reg, Client}, 120000).
|
||||||
|
|
||||||
%% @doc Unregister clientId with pid.
|
%% @doc Unregister clientId with pid.
|
||||||
-spec(unregister(ClientId :: binary()) -> ok).
|
-spec(unreg(binary()) -> ok).
|
||||||
unregister(ClientId) when is_binary(ClientId) ->
|
unreg(ClientId) when is_binary(ClientId) ->
|
||||||
gen_server2:cast(pick(ClientId), {unregister, ClientId, self()}).
|
gen_server2:cast(pick(ClientId), {unreg, ClientId, self()}).
|
||||||
|
|
||||||
pick(ClientId) -> gproc_pool:pick_worker(?POOL, ClientId).
|
pick(ClientId) -> gproc_pool:pick_worker(?POOL, ClientId).
|
||||||
|
|
||||||
|
@ -88,21 +82,15 @@ init([Pool, Id, StatsFun]) ->
|
||||||
{ok, #state{pool = Pool, id = Id, statsfun = StatsFun, monitors = dict:new()}}.
|
{ok, #state{pool = Pool, id = Id, statsfun = StatsFun, monitors = dict:new()}}.
|
||||||
|
|
||||||
prioritise_call(Req, _From, _Len, _State) ->
|
prioritise_call(Req, _From, _Len, _State) ->
|
||||||
case Req of
|
case Req of {reg, _Client} -> 2; _ -> 1 end.
|
||||||
{register, _Client} -> 2;
|
|
||||||
_ -> 1
|
|
||||||
end.
|
|
||||||
|
|
||||||
prioritise_cast(Msg, _Len, _State) ->
|
prioritise_cast(Msg, _Len, _State) ->
|
||||||
case Msg of
|
case Msg of {unreg, _ClientId, _Pid} -> 9; _ -> 1 end.
|
||||||
{unregister, _ClientId, _Pid} -> 9;
|
|
||||||
_ -> 1
|
|
||||||
end.
|
|
||||||
|
|
||||||
prioritise_info(_Msg, _Len, _State) ->
|
prioritise_info(_Msg, _Len, _State) ->
|
||||||
3.
|
3.
|
||||||
|
|
||||||
handle_call({register, Client = #mqtt_client{client_id = ClientId,
|
handle_call({reg, Client = #mqtt_client{client_id = ClientId,
|
||||||
client_pid = Pid}}, _From, State) ->
|
client_pid = Pid}}, _From, State) ->
|
||||||
case lookup_proc(ClientId) of
|
case lookup_proc(ClientId) of
|
||||||
Pid ->
|
Pid ->
|
||||||
|
@ -115,7 +103,7 @@ handle_call({register, Client = #mqtt_client{client_id = ClientId,
|
||||||
handle_call(Req, _From, State) ->
|
handle_call(Req, _From, State) ->
|
||||||
?UNEXPECTED_REQ(Req, State).
|
?UNEXPECTED_REQ(Req, State).
|
||||||
|
|
||||||
handle_cast({unregister, ClientId, Pid}, State) ->
|
handle_cast({unreg, ClientId, Pid}, State) ->
|
||||||
case lookup_proc(ClientId) of
|
case lookup_proc(ClientId) of
|
||||||
Pid ->
|
Pid ->
|
||||||
ets:delete(mqtt_client, ClientId),
|
ets:delete(mqtt_client, ClientId),
|
||||||
|
|
|
@ -0,0 +1,99 @@
|
||||||
|
%%--------------------------------------------------------------------
|
||||||
|
%% Copyright (c) 2012-2016 Feng Lee <feng@emqtt.io>.
|
||||||
|
%%
|
||||||
|
%% Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
%% you may not use this file except in compliance with the License.
|
||||||
|
%% You may obtain a copy of the License at
|
||||||
|
%%
|
||||||
|
%% http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
%%
|
||||||
|
%% Unless required by applicable law or agreed to in writing, software
|
||||||
|
%% distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
%% See the License for the specific language governing permissions and
|
||||||
|
%% limitations under the License.
|
||||||
|
%%--------------------------------------------------------------------
|
||||||
|
|
||||||
|
-module(emqttd_conf).
|
||||||
|
|
||||||
|
-export([mqtt/0, retained/0, session/0, queue/0, bridge/0, pubsub/0]).
|
||||||
|
|
||||||
|
mqtt() ->
|
||||||
|
[
|
||||||
|
%% Max ClientId Length Allowed.
|
||||||
|
{max_clientid_len, emqttd:conf(mqtt_max_clientid_len, 512)},
|
||||||
|
%% Max Packet Size Allowed, 64K by default.
|
||||||
|
{max_packet_size, emqttd:conf(mqtt_max_packet_size, 65536)},
|
||||||
|
%% Client Idle Timeout.
|
||||||
|
{client_idle_timeout, emqttd:conf(mqtt_client_idle_timeout, 30)}
|
||||||
|
].
|
||||||
|
|
||||||
|
retained() ->
|
||||||
|
[
|
||||||
|
%% Expired after seconds, never expired if 0
|
||||||
|
{expired_after, emqttd:conf(retained_expired_after, 0)},
|
||||||
|
%% Max number of retained messages
|
||||||
|
{max_message_num, emqttd:conf(retained_max_message_num, 100000)},
|
||||||
|
%% Max Payload Size of retained message
|
||||||
|
{max_playload_size, emqttd:conf(retained_max_playload_size, 65536)}
|
||||||
|
].
|
||||||
|
|
||||||
|
session() ->
|
||||||
|
[
|
||||||
|
%% Max number of QoS 1 and 2 messages that can be “inflight” at one time.
|
||||||
|
%% 0 means no limit
|
||||||
|
{max_inflight, emqttd:conf(session_max_inflight, 100)},
|
||||||
|
|
||||||
|
%% Retry interval for redelivering QoS1/2 messages.
|
||||||
|
{unack_retry_interval, emqttd:conf(session_unack_retry_interval, 60)},
|
||||||
|
|
||||||
|
%% Awaiting PUBREL Timeout
|
||||||
|
{await_rel_timeout, emqttd:conf(session_await_rel_timeout, 20)},
|
||||||
|
|
||||||
|
%% Max Packets that Awaiting PUBREL, 0 means no limit
|
||||||
|
{max_awaiting_rel, emqttd:conf(session_max_awaiting_rel, 0)},
|
||||||
|
|
||||||
|
%% Statistics Collection Interval(seconds)
|
||||||
|
{collect_interval, emqttd:conf(session_collect_interval, 0)},
|
||||||
|
|
||||||
|
%% Expired after 2 day (unit: minute)
|
||||||
|
{expired_after, emqttd:conf(session_expired_after, 2880)}
|
||||||
|
].
|
||||||
|
|
||||||
|
queue() ->
|
||||||
|
[
|
||||||
|
%% Type: simple | priority
|
||||||
|
{type, emqttd:conf(queue_type, simple)},
|
||||||
|
|
||||||
|
%% Topic Priority: 0~255, Default is 0
|
||||||
|
{priority, emqttd:conf(queue_priority, [])},
|
||||||
|
|
||||||
|
%% Max queue length. Enqueued messages when persistent client disconnected,
|
||||||
|
%% or inflight window is full.
|
||||||
|
{max_length, emqttd:conf(queue_max_length, infinity)},
|
||||||
|
|
||||||
|
%% Low-water mark of queued messages
|
||||||
|
{low_watermark, emqttd:conf(queue_low_watermark, 0.2)},
|
||||||
|
|
||||||
|
%% High-water mark of queued messages
|
||||||
|
{high_watermark, emqttd:conf(queue_high_watermark, 0.6)},
|
||||||
|
|
||||||
|
%% Queue Qos0 messages?
|
||||||
|
{queue_qos0, emqttd:conf(queue_qos0, true)}
|
||||||
|
].
|
||||||
|
|
||||||
|
bridge() ->
|
||||||
|
[
|
||||||
|
%% TODO: Bridge Queue Size
|
||||||
|
{max_queue_len, emqttd:conf(bridge_max_queue_len, 10000)},
|
||||||
|
|
||||||
|
%% Ping Interval of bridge node
|
||||||
|
{ping_down_interval, emqttd:conf(bridge_ping_down_interval, 1)}
|
||||||
|
].
|
||||||
|
|
||||||
|
pubsub() ->
|
||||||
|
[
|
||||||
|
%% PubSub and Router. Default should be scheduler numbers.
|
||||||
|
{pool_size, emqttd:conf(pubsub_pool_size, 8)}
|
||||||
|
].
|
||||||
|
|
|
@ -133,3 +133,20 @@ noreply(State) ->
|
||||||
next_seq(State = #state{seq = Seq}) ->
|
next_seq(State = #state{seq = Seq}) ->
|
||||||
State#state{seq = Seq + 1}.
|
State#state{seq = Seq + 1}.
|
||||||
|
|
||||||
|
-ifdef(TEST).
|
||||||
|
-include_lib("eunit/include/eunit.hrl").
|
||||||
|
register_cmd_test_() ->
|
||||||
|
{setup,
|
||||||
|
fun() ->
|
||||||
|
{ok, InitState} = emqttd_ctl:init([]),
|
||||||
|
InitState
|
||||||
|
end,
|
||||||
|
fun(State) ->
|
||||||
|
ok = emqttd_ctl:terminate(shutdown, State)
|
||||||
|
end,
|
||||||
|
fun(State = #state{seq = Seq}) ->
|
||||||
|
emqttd_ctl:handle_cast({register_cmd, test0, {?MODULE, test0}, []}, State),
|
||||||
|
[?_assertMatch([{{0,test0},{?MODULE, test0}, []}], ets:lookup(?CMD_TAB, {Seq,test0}))]
|
||||||
|
end
|
||||||
|
}.
|
||||||
|
-endif.
|
||||||
|
|
|
@ -29,7 +29,7 @@
|
||||||
%% @end
|
%% @end
|
||||||
-module(emqttd_guid).
|
-module(emqttd_guid).
|
||||||
|
|
||||||
-export([gen/0, new/0, timestamp/1]).
|
-export([gen/0, new/0, timestamp/1, to_hexstr/1, from_hexstr/1, to_base62/1, from_base62/1]).
|
||||||
|
|
||||||
-define(MAX_SEQ, 16#FFFF).
|
-define(MAX_SEQ, 16#FFFF).
|
||||||
|
|
||||||
|
@ -120,3 +120,15 @@ npid() ->
|
||||||
PidByte3:8, PidByte4:8>>,
|
PidByte3:8, PidByte4:8>>,
|
||||||
NPid.
|
NPid.
|
||||||
|
|
||||||
|
to_hexstr(<<I:128>>) ->
|
||||||
|
list_to_binary(integer_to_list(I, 16)).
|
||||||
|
|
||||||
|
from_hexstr(S) ->
|
||||||
|
I = list_to_integer(binary_to_list(S), 16), <<I:128>>.
|
||||||
|
|
||||||
|
to_base62(<<I:128>>) ->
|
||||||
|
emqttd_base62:encode(I).
|
||||||
|
|
||||||
|
from_base62(S) ->
|
||||||
|
I = emqttd_base62:decode(S), <<I:128>>.
|
||||||
|
|
||||||
|
|
|
@ -28,29 +28,21 @@
|
||||||
|
|
||||||
-export([format/1]).
|
-export([format/1]).
|
||||||
|
|
||||||
%% @doc Make a message
|
-type(msg_from() :: atom() | {binary(), undefined | binary()}).
|
||||||
-spec(make(From, Topic, Payload) -> mqtt_message() when
|
|
||||||
From :: atom() | binary(),
|
|
||||||
Topic :: binary(),
|
|
||||||
Payload :: binary()).
|
|
||||||
make(From, Topic, Payload) ->
|
|
||||||
#mqtt_message{topic = Topic,
|
|
||||||
from = From,
|
|
||||||
payload = Payload,
|
|
||||||
timestamp = os:timestamp()}.
|
|
||||||
|
|
||||||
-spec(make(From, Qos, Topic, Payload) -> mqtt_message() when
|
%% @doc Make a message
|
||||||
From :: atom() | binary(),
|
-spec(make(msg_from(), binary(), binary()) -> mqtt_message()).
|
||||||
Qos :: mqtt_qos() | mqtt_qos_name(),
|
make(From, Topic, Payload) ->
|
||||||
Topic :: binary(),
|
make(From, ?QOS_0, Topic, Payload).
|
||||||
Payload :: binary()).
|
|
||||||
|
-spec(make(msg_from(), mqtt_qos(), binary(), binary()) -> mqtt_message()).
|
||||||
make(From, Qos, Topic, Payload) ->
|
make(From, Qos, Topic, Payload) ->
|
||||||
#mqtt_message{msgid = msgid(?QOS_I(Qos)),
|
#mqtt_message{id = msgid(),
|
||||||
topic = Topic,
|
|
||||||
from = From,
|
from = From,
|
||||||
qos = ?QOS_I(Qos),
|
qos = ?QOS_I(Qos),
|
||||||
|
topic = Topic,
|
||||||
payload = Payload,
|
payload = Payload,
|
||||||
timestamp = os:timestamp()}.
|
timestamp = emqttd_time:now_to_secs()}.
|
||||||
|
|
||||||
%% @doc Message from Packet
|
%% @doc Message from Packet
|
||||||
-spec(from_packet(mqtt_packet()) -> mqtt_message()).
|
-spec(from_packet(mqtt_packet()) -> mqtt_message()).
|
||||||
|
@ -61,14 +53,14 @@ from_packet(#mqtt_packet{header = #mqtt_packet_header{type = ?PUBLISH,
|
||||||
variable = #mqtt_packet_publish{topic_name = Topic,
|
variable = #mqtt_packet_publish{topic_name = Topic,
|
||||||
packet_id = PacketId},
|
packet_id = PacketId},
|
||||||
payload = Payload}) ->
|
payload = Payload}) ->
|
||||||
#mqtt_message{msgid = msgid(Qos),
|
#mqtt_message{id = msgid(),
|
||||||
pktid = PacketId,
|
pktid = PacketId,
|
||||||
qos = Qos,
|
qos = Qos,
|
||||||
retain = Retain,
|
retain = Retain,
|
||||||
dup = Dup,
|
dup = Dup,
|
||||||
topic = Topic,
|
topic = Topic,
|
||||||
payload = Payload,
|
payload = Payload,
|
||||||
timestamp = os:timestamp()};
|
timestamp = emqttd_time:now_to_secs()};
|
||||||
|
|
||||||
from_packet(#mqtt_packet_connect{will_flag = false}) ->
|
from_packet(#mqtt_packet_connect{will_flag = false}) ->
|
||||||
undefined;
|
undefined;
|
||||||
|
@ -79,15 +71,14 @@ from_packet(#mqtt_packet_connect{client_id = ClientId,
|
||||||
will_qos = Qos,
|
will_qos = Qos,
|
||||||
will_topic = Topic,
|
will_topic = Topic,
|
||||||
will_msg = Msg}) ->
|
will_msg = Msg}) ->
|
||||||
#mqtt_message{msgid = msgid(Qos),
|
#mqtt_message{id = msgid(),
|
||||||
topic = Topic,
|
topic = Topic,
|
||||||
from = ClientId,
|
from = {ClientId, Username},
|
||||||
sender = Username,
|
|
||||||
retain = Retain,
|
retain = Retain,
|
||||||
qos = Qos,
|
qos = Qos,
|
||||||
dup = false,
|
dup = false,
|
||||||
payload = Msg,
|
payload = Msg,
|
||||||
timestamp = os:timestamp()}.
|
timestamp = emqttd_time:now_to_secs()}.
|
||||||
|
|
||||||
from_packet(ClientId, Packet) ->
|
from_packet(ClientId, Packet) ->
|
||||||
Msg = from_packet(Packet),
|
Msg = from_packet(Packet),
|
||||||
|
@ -95,12 +86,9 @@ from_packet(ClientId, Packet) ->
|
||||||
|
|
||||||
from_packet(Username, ClientId, Packet) ->
|
from_packet(Username, ClientId, Packet) ->
|
||||||
Msg = from_packet(Packet),
|
Msg = from_packet(Packet),
|
||||||
Msg#mqtt_message{from = ClientId, sender = Username}.
|
Msg#mqtt_message{from = {ClientId, Username}}.
|
||||||
|
|
||||||
msgid(?QOS_0) ->
|
msgid() -> emqttd_guid:gen().
|
||||||
undefined;
|
|
||||||
msgid(Qos) when Qos =:= ?QOS_1 orelse Qos =:= ?QOS_2 ->
|
|
||||||
emqttd_guid:gen().
|
|
||||||
|
|
||||||
%% @doc Message to packet
|
%% @doc Message to packet
|
||||||
-spec(to_packet(mqtt_message()) -> mqtt_packet()).
|
-spec(to_packet(mqtt_message()) -> mqtt_packet()).
|
||||||
|
@ -150,10 +138,16 @@ unset_flag(retain, Msg = #mqtt_message{retain = true}) ->
|
||||||
unset_flag(Flag, Msg) when Flag =:= dup orelse Flag =:= retain -> Msg.
|
unset_flag(Flag, Msg) when Flag =:= dup orelse Flag =:= retain -> Msg.
|
||||||
|
|
||||||
%% @doc Format MQTT Message
|
%% @doc Format MQTT Message
|
||||||
format(#mqtt_message{msgid = MsgId, pktid = PktId, from = From, sender = Sender,
|
format(#mqtt_message{id = MsgId, pktid = PktId, from = {ClientId, Username},
|
||||||
qos = Qos, retain = Retain, dup = Dup, topic =Topic}) ->
|
qos = Qos, retain = Retain, dup = Dup, topic =Topic}) ->
|
||||||
io_lib:format("Message(Q~p, R~p, D~p, MsgId=~p, PktId=~p, From=~s, Sender=~s, Topic=~s)",
|
io_lib:format("Message(Q~p, R~p, D~p, MsgId=~p, PktId=~p, From=~s/~s, Topic=~s)",
|
||||||
[i(Qos), i(Retain), i(Dup), MsgId, PktId, From, Sender, Topic]).
|
[i(Qos), i(Retain), i(Dup), MsgId, PktId, Username, ClientId, Topic]);
|
||||||
|
|
||||||
|
%% TODO:...
|
||||||
|
format(#mqtt_message{id = MsgId, pktid = PktId, from = From,
|
||||||
|
qos = Qos, retain = Retain, dup = Dup, topic =Topic}) ->
|
||||||
|
io_lib:format("Message(Q~p, R~p, D~p, MsgId=~p, PktId=~p, From=~s, Topic=~s)",
|
||||||
|
[i(Qos), i(Retain), i(Dup), MsgId, PktId, From, Topic]).
|
||||||
|
|
||||||
i(true) -> 1;
|
i(true) -> 1;
|
||||||
i(false) -> 0;
|
i(false) -> 0;
|
||||||
|
|
|
@ -243,7 +243,7 @@ init([]) ->
|
||||||
% Init metrics
|
% Init metrics
|
||||||
[create_metric(Metric) || Metric <- Metrics],
|
[create_metric(Metric) || Metric <- Metrics],
|
||||||
% $SYS Topics for metrics
|
% $SYS Topics for metrics
|
||||||
[ok = emqttd:create(topic, metric_topic(Topic)) || {_, Topic} <- Metrics],
|
% [ok = emqttd:create(topic, metric_topic(Topic)) || {_, Topic} <- Metrics],
|
||||||
% Tick to publish metrics
|
% Tick to publish metrics
|
||||||
{ok, #state{tick_tref = emqttd_broker:start_tick(tick)}, hibernate}.
|
{ok, #state{tick_tref = emqttd_broker:start_tick(tick)}, hibernate}.
|
||||||
|
|
||||||
|
|
|
@ -45,12 +45,13 @@ on_client_connected(ConnAck, Client = #mqtt_client{client_id = ClientId,
|
||||||
emqttd:publish(emqttd_message:set_flag(sys, Msg)),
|
emqttd:publish(emqttd_message:set_flag(sys, Msg)),
|
||||||
{ok, Client}.
|
{ok, Client}.
|
||||||
|
|
||||||
on_client_disconnected(Reason, ClientId, Opts) ->
|
on_client_disconnected(Reason, #mqtt_client{client_id = ClientId}, Opts) ->
|
||||||
Json = mochijson2:encode([{clientid, ClientId},
|
Json = mochijson2:encode([{clientid, ClientId},
|
||||||
{reason, reason(Reason)},
|
{reason, reason(Reason)},
|
||||||
{ts, emqttd_time:now_to_secs()}]),
|
{ts, emqttd_time:now_to_secs()}]),
|
||||||
Msg = message(qos(Opts), topic(disconnected, ClientId), Json),
|
Msg = message(qos(Opts), topic(disconnected, ClientId), Json),
|
||||||
emqttd:publish(emqttd_message:set_flag(sys, Msg)).
|
emqttd:publish(emqttd_message:set_flag(sys, Msg)),
|
||||||
|
ok.
|
||||||
|
|
||||||
unload(_Opts) ->
|
unload(_Opts) ->
|
||||||
emqttd:unhook('client.connected', fun ?MODULE:on_client_connected/3),
|
emqttd:unhook('client.connected', fun ?MODULE:on_client_connected/3),
|
||||||
|
|
|
@ -30,20 +30,23 @@
|
||||||
%%--------------------------------------------------------------------
|
%%--------------------------------------------------------------------
|
||||||
|
|
||||||
load(Opts) ->
|
load(Opts) ->
|
||||||
File = proplists:get_value(file, Opts),
|
case proplists:get_value(config, Opts) of
|
||||||
{ok, Terms} = file:consult(File),
|
undefined ->
|
||||||
Sections = compile(Terms),
|
ok;
|
||||||
|
File ->
|
||||||
|
{ok, Terms} = file:consult(File), Sections = compile(Terms),
|
||||||
emqttd:hook('client.subscribe', fun ?MODULE:rewrite_subscribe/3, [Sections]),
|
emqttd:hook('client.subscribe', fun ?MODULE:rewrite_subscribe/3, [Sections]),
|
||||||
emqttd:hook('client.unsubscribe', fun ?MODULE:rewrite_unsubscribe/3, [Sections]),
|
emqttd:hook('client.unsubscribe', fun ?MODULE:rewrite_unsubscribe/3, [Sections]),
|
||||||
emqttd:hook('message.publish', fun ?MODULE:rewrite_publish/2, [Sections]).
|
emqttd:hook('message.publish', fun ?MODULE:rewrite_publish/2, [Sections])
|
||||||
|
end.
|
||||||
|
|
||||||
rewrite_subscribe(_ClientId, TopicTable, Sections) ->
|
rewrite_subscribe({_ClientId, _Username}, {Topic, Opts}, Sections) ->
|
||||||
lager:info("Rewrite subscribe: ~p", [TopicTable]),
|
lager:info("Rewrite subscribe: ~p", [{Topic, Opts}]),
|
||||||
{ok, [{match_topic(Topic, Sections), Qos} || {Topic, Qos} <- TopicTable]}.
|
{ok, {match_topic(Topic, Sections), Opts}}.
|
||||||
|
|
||||||
rewrite_unsubscribe(_ClientId, Topics, Sections) ->
|
rewrite_unsubscribe({_ClientId, _Username}, {Topic, Opts}, Sections) ->
|
||||||
lager:info("Rewrite unsubscribe: ~p", [Topics]),
|
lager:info("Rewrite unsubscribe: ~p", [{Topic, Opts}]),
|
||||||
{ok, [match_topic(Topic, Sections) || Topic <- Topics]}.
|
{ok, {match_topic(Topic, Sections), Opts}}.
|
||||||
|
|
||||||
rewrite_publish(Message=#mqtt_message{topic = Topic}, Sections) ->
|
rewrite_publish(Message=#mqtt_message{topic = Topic}, Sections) ->
|
||||||
%%TODO: this will not work if the client is always online.
|
%%TODO: this will not work if the client is always online.
|
||||||
|
|
|
@ -25,32 +25,22 @@
|
||||||
|
|
||||||
-export([load/1, on_client_connected/3, unload/1]).
|
-export([load/1, on_client_connected/3, unload/1]).
|
||||||
|
|
||||||
-record(state, {topics, backend = false}).
|
|
||||||
|
|
||||||
load(Opts) ->
|
load(Opts) ->
|
||||||
Topics = [{iolist_to_binary(Topic), QoS} || {Topic, QoS} <- Opts, ?IS_QOS(QoS)],
|
Topics = [{iolist_to_binary(Topic), QoS} || {Topic, QoS} <- Opts, ?IS_QOS(QoS)],
|
||||||
State = #state{topics = Topics, backend = lists:member(backend, Opts)},
|
emqttd:hook('client.connected', fun ?MODULE:on_client_connected/3, [Topics]).
|
||||||
emqttd:hook('client.connected', fun ?MODULE:on_client_connected/3, [State]).
|
|
||||||
|
|
||||||
on_client_connected(?CONNACK_ACCEPT, Client = #mqtt_client{client_id = ClientId,
|
on_client_connected(?CONNACK_ACCEPT, Client = #mqtt_client{client_id = ClientId,
|
||||||
client_pid = ClientPid,
|
client_pid = ClientPid,
|
||||||
username = Username},
|
username = Username}, Topics) ->
|
||||||
#state{topics = Topics, backend = Backend}) ->
|
|
||||||
|
|
||||||
Replace = fun(Topic) -> rep(<<"$u">>, Username, rep(<<"$c">>, ClientId, Topic)) end,
|
Replace = fun(Topic) -> rep(<<"$u">>, Username, rep(<<"$c">>, ClientId, Topic)) end,
|
||||||
TopicTable = [{Replace(Topic), Qos} || {Topic, Qos} <- with_backend(Backend, ClientId, Topics)],
|
TopicTable = [{Replace(Topic), Qos} || {Topic, Qos} <- Topics],
|
||||||
emqttd_client:subscribe(ClientPid, TopicTable),
|
emqttd_client:subscribe(ClientPid, TopicTable),
|
||||||
{ok, Client};
|
{ok, Client};
|
||||||
|
|
||||||
on_client_connected(_ConnAck, _Client, _State) ->
|
on_client_connected(_ConnAck, _Client, _State) ->
|
||||||
ok.
|
ok.
|
||||||
|
|
||||||
with_backend(false, _ClientId, TopicTable) ->
|
|
||||||
TopicTable;
|
|
||||||
with_backend(true, ClientId, TopicTable) ->
|
|
||||||
Fun = fun(#mqtt_subscription{topic = Topic, qos = Qos}) -> {Topic, Qos} end,
|
|
||||||
emqttd_opts:merge([Fun(Sub) || Sub <- emqttd_backend:lookup_subscriptions(ClientId)], TopicTable).
|
|
||||||
|
|
||||||
unload(_Opts) ->
|
unload(_Opts) ->
|
||||||
emqttd:unhook('client.connected', fun ?MODULE:on_client_connected/3).
|
emqttd:unhook('client.connected', fun ?MODULE:on_client_connected/3).
|
||||||
|
|
||||||
|
|
|
@ -27,14 +27,18 @@
|
||||||
%% @doc Load all plugins when the broker started.
|
%% @doc Load all plugins when the broker started.
|
||||||
-spec(load() -> list() | {error, any()}).
|
-spec(load() -> list() | {error, any()}).
|
||||||
load() ->
|
load() ->
|
||||||
case env(loaded_file) of
|
case emqttd:conf(plugins_loaded_file) of
|
||||||
{ok, File} ->
|
{ok, File} ->
|
||||||
|
ensure_file(File),
|
||||||
with_loaded_file(File, fun(Names) -> load_plugins(Names, false) end);
|
with_loaded_file(File, fun(Names) -> load_plugins(Names, false) end);
|
||||||
undefined ->
|
undefined ->
|
||||||
%% No plugins available
|
%% No plugins available
|
||||||
ignore
|
ignore
|
||||||
end.
|
end.
|
||||||
|
|
||||||
|
ensure_file(File) ->
|
||||||
|
case filelib:is_file(File) of false -> write_loaded([]); true -> ok end.
|
||||||
|
|
||||||
with_loaded_file(File, SuccFun) ->
|
with_loaded_file(File, SuccFun) ->
|
||||||
case read_loaded(File) of
|
case read_loaded(File) of
|
||||||
{ok, Names} ->
|
{ok, Names} ->
|
||||||
|
@ -56,7 +60,7 @@ load_plugins(Names, Persistent) ->
|
||||||
%% @doc Unload all plugins before broker stopped.
|
%% @doc Unload all plugins before broker stopped.
|
||||||
-spec(unload() -> list() | {error, any()}).
|
-spec(unload() -> list() | {error, any()}).
|
||||||
unload() ->
|
unload() ->
|
||||||
case env(loaded_file) of
|
case emqttd:conf(plugins_loaded_file) of
|
||||||
{ok, File} ->
|
{ok, File} ->
|
||||||
with_loaded_file(File, fun stop_plugins/1);
|
with_loaded_file(File, fun stop_plugins/1);
|
||||||
undefined ->
|
undefined ->
|
||||||
|
@ -70,10 +74,10 @@ stop_plugins(Names) ->
|
||||||
%% @doc List all available plugins
|
%% @doc List all available plugins
|
||||||
-spec(list() -> [mqtt_plugin()]).
|
-spec(list() -> [mqtt_plugin()]).
|
||||||
list() ->
|
list() ->
|
||||||
case env(plugins_dir) of
|
case emqttd:conf(plugins_etc_dir) of
|
||||||
{ok, PluginsDir} ->
|
{ok, PluginsEtc} ->
|
||||||
AppFiles = filelib:wildcard("*/ebin/*.app", PluginsDir),
|
CfgFiles = filelib:wildcard("*.conf", PluginsEtc),
|
||||||
Plugins = [plugin(PluginsDir, AppFile) || AppFile <- AppFiles],
|
Plugins = [plugin(CfgFile) || CfgFile <- CfgFiles],
|
||||||
StartedApps = names(started_app),
|
StartedApps = names(started_app),
|
||||||
lists:map(fun(Plugin = #mqtt_plugin{name = Name}) ->
|
lists:map(fun(Plugin = #mqtt_plugin{name = Name}) ->
|
||||||
case lists:member(Name, StartedApps) of
|
case lists:member(Name, StartedApps) of
|
||||||
|
@ -85,21 +89,12 @@ list() ->
|
||||||
[]
|
[]
|
||||||
end.
|
end.
|
||||||
|
|
||||||
plugin(PluginsDir, AppFile0) ->
|
plugin(CfgFile) ->
|
||||||
AppFile = filename:join(PluginsDir, AppFile0),
|
[AppName | _] = string:tokens(CfgFile, "."),
|
||||||
{ok, [{application, Name, Attrs}]} = file:consult(AppFile),
|
{ok, Attrs} = application:get_all_key(list_to_atom(AppName)),
|
||||||
CfgFile = filename:join([PluginsDir, Name, "etc/plugin.config"]),
|
|
||||||
AppsEnv1 =
|
|
||||||
case filelib:is_file(CfgFile) of
|
|
||||||
true ->
|
|
||||||
{ok, [AppsEnv]} = file:consult(CfgFile),
|
|
||||||
AppsEnv;
|
|
||||||
false ->
|
|
||||||
[]
|
|
||||||
end,
|
|
||||||
Ver = proplists:get_value(vsn, Attrs, "0"),
|
Ver = proplists:get_value(vsn, Attrs, "0"),
|
||||||
Descr = proplists:get_value(description, Attrs, ""),
|
Descr = proplists:get_value(description, Attrs, ""),
|
||||||
#mqtt_plugin{name = Name, version = Ver, config = AppsEnv1, descr = Descr}.
|
#mqtt_plugin{name = list_to_atom(AppName), version = Ver, descr = Descr}.
|
||||||
|
|
||||||
%% @doc Load a Plugin
|
%% @doc Load a Plugin
|
||||||
-spec(load(atom()) -> ok | {error, any()}).
|
-spec(load(atom()) -> ok | {error, any()}).
|
||||||
|
@ -118,31 +113,24 @@ load(PluginName) when is_atom(PluginName) ->
|
||||||
end
|
end
|
||||||
end.
|
end.
|
||||||
|
|
||||||
load_plugin(#mqtt_plugin{name = Name, config = Config}, Persistent) ->
|
load_plugin(#mqtt_plugin{name = Name}, Persistent) ->
|
||||||
case load_app(Name, Config) of
|
case load_app(Name) of
|
||||||
ok ->
|
ok ->
|
||||||
start_app(Name, fun(App) -> plugin_loaded(App, Persistent) end);
|
start_app(Name, fun(App) -> plugin_loaded(App, Persistent) end);
|
||||||
{error, Error} ->
|
{error, Error} ->
|
||||||
{error, Error}
|
{error, Error}
|
||||||
end.
|
end.
|
||||||
|
|
||||||
load_app(App, Config) ->
|
load_app(App) ->
|
||||||
case application:load(App) of
|
case application:load(App) of
|
||||||
ok ->
|
ok ->
|
||||||
set_config(Config);
|
ok;
|
||||||
{error, {already_loaded, App}} ->
|
{error, {already_loaded, App}} ->
|
||||||
set_config(Config);
|
ok;
|
||||||
{error, Error} ->
|
{error, Error} ->
|
||||||
{error, Error}
|
{error, Error}
|
||||||
end.
|
end.
|
||||||
|
|
||||||
%% This trick is awesome:)
|
|
||||||
set_config([]) ->
|
|
||||||
ok;
|
|
||||||
set_config([{AppName, Envs} | Config]) ->
|
|
||||||
[application:set_env(AppName, Par, Val) || {Par, Val} <- Envs],
|
|
||||||
set_config(Config).
|
|
||||||
|
|
||||||
start_app(App, SuccFun) ->
|
start_app(App, SuccFun) ->
|
||||||
case application:ensure_all_started(App) of
|
case application:ensure_all_started(App) of
|
||||||
{ok, Started} ->
|
{ok, Started} ->
|
||||||
|
@ -238,14 +226,15 @@ plugin_unloaded(Name, true) ->
|
||||||
end.
|
end.
|
||||||
|
|
||||||
read_loaded() ->
|
read_loaded() ->
|
||||||
{ok, File} = env(loaded_file),
|
case emqttd:conf(plugins_loaded_file) of
|
||||||
read_loaded(File).
|
{ok, File} -> read_loaded(File);
|
||||||
|
undefined -> {error, not_found}
|
||||||
|
end.
|
||||||
|
|
||||||
read_loaded(File) ->
|
read_loaded(File) -> file:consult(File).
|
||||||
file:consult(File).
|
|
||||||
|
|
||||||
write_loaded(AppNames) ->
|
write_loaded(AppNames) ->
|
||||||
{ok, File} = env(loaded_file),
|
{ok, File} = emqttd:conf(plugins_loaded_file),
|
||||||
case file:open(File, [binary, write]) of
|
case file:open(File, [binary, write]) of
|
||||||
{ok, Fd} ->
|
{ok, Fd} ->
|
||||||
lists:foreach(fun(Name) ->
|
lists:foreach(fun(Name) ->
|
||||||
|
@ -256,16 +245,3 @@ write_loaded(AppNames) ->
|
||||||
{error, Error}
|
{error, Error}
|
||||||
end.
|
end.
|
||||||
|
|
||||||
env(Name) ->
|
|
||||||
case application:get_env(emqttd, plugins) of
|
|
||||||
{ok, PluginsEnv} ->
|
|
||||||
case proplists:get_value(Name, PluginsEnv) of
|
|
||||||
undefined ->
|
|
||||||
undefined;
|
|
||||||
Val ->
|
|
||||||
{ok, Val}
|
|
||||||
end;
|
|
||||||
undefined ->
|
|
||||||
undefined
|
|
||||||
end.
|
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,50 @@
|
||||||
|
%%--------------------------------------------------------------------
|
||||||
|
%% Copyright (c) 2016 Feng Lee <feng@emqtt.io>. All Rights Reserved.
|
||||||
|
%%
|
||||||
|
%% Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
%% you may not use this file except in compliance with the License.
|
||||||
|
%% You may obtain a copy of the License at
|
||||||
|
%%
|
||||||
|
%% http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
%%
|
||||||
|
%% Unless required by applicable law or agreed to in writing, software
|
||||||
|
%% distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
%% See the License for the specific language governing permissions and
|
||||||
|
%% limitations under the License.
|
||||||
|
%%--------------------------------------------------------------------
|
||||||
|
|
||||||
|
-module(emqttd_pmon).
|
||||||
|
|
||||||
|
-author("Feng Lee <feng@emqtt.io>").
|
||||||
|
|
||||||
|
-type(pmon() :: {?MODULE, map()}).
|
||||||
|
|
||||||
|
-export([new/0, monitor/2, demonitor/2, erase/2]).
|
||||||
|
|
||||||
|
new() -> {?MODULE, [maps:new()]}.
|
||||||
|
|
||||||
|
-spec(monitor(pid(), pmon()) -> pmon()).
|
||||||
|
monitor(Pid, PM = {?MODULE, [M]}) ->
|
||||||
|
case maps:is_key(Pid, M) of
|
||||||
|
true ->
|
||||||
|
PM;
|
||||||
|
false ->
|
||||||
|
Ref = erlang:monitor(process, Pid),
|
||||||
|
{?MODULE, [maps:put(Pid, Ref, M)]}
|
||||||
|
end.
|
||||||
|
|
||||||
|
-spec(demonitor(pid(), pmon()) -> pmon()).
|
||||||
|
demonitor(Pid, PM = {?MODULE, [M]}) ->
|
||||||
|
case maps:find(Pid, M) of
|
||||||
|
{ok, Ref} ->
|
||||||
|
erlang:demonitor(Ref, [flush]),
|
||||||
|
{?MODULE, [maps:remove(Pid, M)]};
|
||||||
|
error ->
|
||||||
|
PM
|
||||||
|
end.
|
||||||
|
|
||||||
|
-spec(erase(pid(), pmon()) -> pmon()).
|
||||||
|
erase(Pid, {?MODULE, [M]}) ->
|
||||||
|
{?MODULE, [maps:remove(Pid, M)]}.
|
||||||
|
|
|
@ -41,10 +41,10 @@ start_link(Pool, Type, MFA) ->
|
||||||
|
|
||||||
-spec(start_link(atom(), atom(), pos_integer(), mfa()) -> {ok, pid()} | {error, any()}).
|
-spec(start_link(atom(), atom(), pos_integer(), mfa()) -> {ok, pid()} | {error, any()}).
|
||||||
start_link(Pool, Type, Size, MFA) ->
|
start_link(Pool, Type, Size, MFA) ->
|
||||||
supervisor:start_link({local, sup_name(Pool)}, ?MODULE, [Pool, Type, Size, MFA]).
|
supervisor:start_link(?MODULE, [Pool, Type, Size, MFA]).
|
||||||
|
|
||||||
sup_name(Pool) when is_atom(Pool) ->
|
%% sup_name(Pool) when is_atom(Pool) ->
|
||||||
list_to_atom(atom_to_list(Pool) ++ "_pool_sup").
|
%% list_to_atom(atom_to_list(Pool) ++ "_pool_sup").
|
||||||
|
|
||||||
init([Pool, Type, Size, {M, F, Args}]) ->
|
init([Pool, Type, Size, {M, F, Args}]) ->
|
||||||
ensure_pool(Pool, Type, [{size, Size}]),
|
ensure_pool(Pool, Type, [{size, Size}]),
|
||||||
|
|
|
@ -146,10 +146,10 @@ process(Packet = ?CONNECT_PACKET(Var), State0) ->
|
||||||
State2 = maybe_set_clientid(State1),
|
State2 = maybe_set_clientid(State1),
|
||||||
|
|
||||||
%% Start session
|
%% Start session
|
||||||
case emqttd_sm:start_session(CleanSess, clientid(State2)) of
|
case emqttd_sm:start_session(CleanSess, {clientid(State2), Username}) of
|
||||||
{ok, Session, SP} ->
|
{ok, Session, SP} ->
|
||||||
%% Register the client
|
%% Register the client
|
||||||
emqttd_cm:register(client(State2)),
|
emqttd_cm:reg(client(State2)),
|
||||||
%% Start keepalive
|
%% Start keepalive
|
||||||
start_keepalive(KeepAlive),
|
start_keepalive(KeepAlive),
|
||||||
%% ACCEPT
|
%% ACCEPT
|
||||||
|
@ -247,19 +247,16 @@ with_puback(Type, Packet = ?PUBLISH_PACKET(_Qos, PacketId),
|
||||||
end.
|
end.
|
||||||
|
|
||||||
-spec(send(mqtt_message() | mqtt_packet(), proto_state()) -> {ok, proto_state()}).
|
-spec(send(mqtt_message() | mqtt_packet(), proto_state()) -> {ok, proto_state()}).
|
||||||
send(Msg, State = #proto_state{client_id = ClientId})
|
send(Msg, State = #proto_state{client_id = ClientId, username = Username})
|
||||||
when is_record(Msg, mqtt_message) ->
|
when is_record(Msg, mqtt_message) ->
|
||||||
emqttd:run_hooks('message.delivered', [ClientId], Msg),
|
emqttd:run_hooks('message.delivered', [{ClientId, Username}], Msg),
|
||||||
send(emqttd_message:to_packet(Msg), State);
|
send(emqttd_message:to_packet(Msg), State);
|
||||||
|
|
||||||
send(Packet, State = #proto_state{sendfun = SendFun})
|
send(Packet, State = #proto_state{sendfun = SendFun})
|
||||||
when is_record(Packet, mqtt_packet) ->
|
when is_record(Packet, mqtt_packet) ->
|
||||||
trace(send, Packet, State),
|
trace(send, Packet, State),
|
||||||
emqttd_metrics:sent(Packet),
|
emqttd_metrics:sent(Packet),
|
||||||
Data = emqttd_serializer:serialize(Packet),
|
SendFun(Packet),
|
||||||
?LOG(debug, "SEND ~p", [Data], State),
|
|
||||||
emqttd_metrics:inc('bytes/sent', size(Data)),
|
|
||||||
SendFun(Data),
|
|
||||||
{ok, State}.
|
{ok, State}.
|
||||||
|
|
||||||
trace(recv, Packet, ProtoState) ->
|
trace(recv, Packet, ProtoState) ->
|
||||||
|
@ -277,15 +274,16 @@ shutdown(_Error, #proto_state{client_id = undefined}) ->
|
||||||
|
|
||||||
shutdown(conflict, #proto_state{client_id = _ClientId}) ->
|
shutdown(conflict, #proto_state{client_id = _ClientId}) ->
|
||||||
%% let it down
|
%% let it down
|
||||||
%% emqttd_cm:unregister(ClientId);
|
%% emqttd_cm:unreg(ClientId);
|
||||||
ignore;
|
ignore;
|
||||||
|
|
||||||
shutdown(Error, State = #proto_state{client_id = ClientId, will_msg = WillMsg}) ->
|
shutdown(Error, State = #proto_state{will_msg = WillMsg}) ->
|
||||||
?LOG(info, "Shutdown for ~p", [Error], State),
|
?LOG(info, "Shutdown for ~p", [Error], State),
|
||||||
send_willmsg(ClientId, WillMsg),
|
Client = client(State),
|
||||||
emqttd:run_hooks('client.disconnected', [Error], ClientId),
|
send_willmsg(Client, WillMsg),
|
||||||
|
emqttd:run_hooks('client.disconnected', [Error], Client),
|
||||||
%% let it down
|
%% let it down
|
||||||
%% emqttd_cm:unregister(ClientId).
|
%% emqttd_cm:unreg(ClientId).
|
||||||
ok.
|
ok.
|
||||||
|
|
||||||
willmsg(Packet) when is_record(Packet, mqtt_packet_connect) ->
|
willmsg(Packet) when is_record(Packet, mqtt_packet_connect) ->
|
||||||
|
@ -301,10 +299,10 @@ maybe_set_clientid(State = #proto_state{client_id = NullId})
|
||||||
maybe_set_clientid(State) ->
|
maybe_set_clientid(State) ->
|
||||||
State.
|
State.
|
||||||
|
|
||||||
send_willmsg(_ClientId, undefined) ->
|
send_willmsg(_Client, undefined) ->
|
||||||
ignore;
|
ignore;
|
||||||
send_willmsg(ClientId, WillMsg) ->
|
send_willmsg(#mqtt_client{client_id = ClientId, username = Username}, WillMsg) ->
|
||||||
emqttd:publish(WillMsg#mqtt_message{from = ClientId}).
|
emqttd:publish(WillMsg#mqtt_message{from = {ClientId, Username}}).
|
||||||
|
|
||||||
start_keepalive(0) -> ignore;
|
start_keepalive(0) -> ignore;
|
||||||
|
|
||||||
|
@ -397,13 +395,16 @@ validate_qos(_) ->
|
||||||
|
|
||||||
%% PUBLISH ACL is cached in process dictionary.
|
%% PUBLISH ACL is cached in process dictionary.
|
||||||
check_acl(publish, Topic, Client) ->
|
check_acl(publish, Topic, Client) ->
|
||||||
case get({acl, publish, Topic}) of
|
IfCache = emqttd:conf(cache_acl, true),
|
||||||
undefined ->
|
case {IfCache, get({acl, publish, Topic})} of
|
||||||
|
{true, undefined} ->
|
||||||
AllowDeny = emqttd_access_control:check_acl(Client, publish, Topic),
|
AllowDeny = emqttd_access_control:check_acl(Client, publish, Topic),
|
||||||
put({acl, publish, Topic}, AllowDeny),
|
put({acl, publish, Topic}, AllowDeny),
|
||||||
AllowDeny;
|
AllowDeny;
|
||||||
AllowDeny ->
|
{true, AllowDeny} ->
|
||||||
AllowDeny
|
AllowDeny;
|
||||||
|
{false, _} ->
|
||||||
|
emqttd_access_control:check_acl(Client, publish, Topic)
|
||||||
end;
|
end;
|
||||||
|
|
||||||
check_acl(subscribe, Topic, Client) ->
|
check_acl(subscribe, Topic, Client) ->
|
||||||
|
|
|
@ -20,125 +20,83 @@
|
||||||
|
|
||||||
-include("emqttd.hrl").
|
-include("emqttd.hrl").
|
||||||
|
|
||||||
-include("emqttd_protocol.hrl").
|
|
||||||
|
|
||||||
-include("emqttd_internal.hrl").
|
-include("emqttd_internal.hrl").
|
||||||
|
|
||||||
%% Mnesia Callbacks
|
|
||||||
-export([mnesia/1]).
|
|
||||||
|
|
||||||
-boot_mnesia({mnesia, [boot]}).
|
|
||||||
-copy_mnesia({mnesia, [copy]}).
|
|
||||||
|
|
||||||
%% API Exports
|
%% API Exports
|
||||||
-export([start_link/3, create_topic/1, lookup_topic/1]).
|
-export([start_link/3, subscribe/2, unsubscribe/2, publish/2,
|
||||||
|
|
||||||
-export([subscribe/2, unsubscribe/2, publish/2, dispatch/2,
|
|
||||||
async_subscribe/2, async_unsubscribe/2]).
|
async_subscribe/2, async_unsubscribe/2]).
|
||||||
|
|
||||||
|
-export([subscribers/1]).
|
||||||
|
|
||||||
%% gen_server.
|
%% gen_server.
|
||||||
-export([init/1, handle_call/3, handle_cast/2, handle_info/2,
|
-export([init/1, handle_call/3, handle_cast/2, handle_info/2,
|
||||||
terminate/2, code_change/3]).
|
terminate/2, code_change/3]).
|
||||||
|
|
||||||
-record(state, {pool, id, env}).
|
-record(state, {pool, id, env}).
|
||||||
|
|
||||||
%%--------------------------------------------------------------------
|
-define(PUBSUB, ?MODULE).
|
||||||
%% Mnesia callbacks
|
|
||||||
%%--------------------------------------------------------------------
|
|
||||||
|
|
||||||
mnesia(boot) ->
|
-spec(start_link(atom(), pos_integer(), [tuple()]) -> {ok, pid()} | ignore | {error, any()}).
|
||||||
ok = emqttd_mnesia:create_table(topic, [
|
|
||||||
{ram_copies, [node()]},
|
|
||||||
{record_name, mqtt_topic},
|
|
||||||
{attributes, record_info(fields, mqtt_topic)}]);
|
|
||||||
|
|
||||||
mnesia(copy) ->
|
|
||||||
ok = emqttd_mnesia:copy_table(topic).
|
|
||||||
|
|
||||||
%%--------------------------------------------------------------------
|
|
||||||
%% Start PubSub
|
|
||||||
%%--------------------------------------------------------------------
|
|
||||||
|
|
||||||
%% @doc Start one pubsub
|
|
||||||
-spec(start_link(Pool, Id, Env) -> {ok, pid()} | ignore | {error, any()} when
|
|
||||||
Pool :: atom(),
|
|
||||||
Id :: pos_integer(),
|
|
||||||
Env :: list(tuple())).
|
|
||||||
start_link(Pool, Id, Env) ->
|
start_link(Pool, Id, Env) ->
|
||||||
gen_server2:start_link({local, ?PROC_NAME(?MODULE, Id)}, ?MODULE, [Pool, Id, Env], []).
|
gen_server2:start_link({local, ?PROC_NAME(?MODULE, Id)}, ?MODULE, [Pool, Id, Env], []).
|
||||||
|
|
||||||
%% @doc Create a Topic.
|
-spec(subscribe(binary(), emqttd:subscriber()) -> ok).
|
||||||
-spec(create_topic(binary()) -> ok | {error, any()}).
|
subscribe(Topic, Subscriber) ->
|
||||||
create_topic(Topic) when is_binary(Topic) ->
|
call(pick(Topic), {subscribe, Topic, Subscriber}).
|
||||||
case mnesia:transaction(fun add_topic_/2, [Topic, [static]]) of
|
|
||||||
{atomic, ok} -> ok;
|
|
||||||
{aborted, Error} -> {error, Error}
|
|
||||||
end.
|
|
||||||
|
|
||||||
%% @doc Lookup a Topic.
|
-spec(async_subscribe(binary(), emqttd:subscriber()) -> ok).
|
||||||
-spec(lookup_topic(binary()) -> list(mqtt_topic())).
|
async_subscribe(Topic, Subscriber) ->
|
||||||
lookup_topic(Topic) when is_binary(Topic) ->
|
cast(pick(Topic), {subscribe, Topic, Subscriber}).
|
||||||
mnesia:dirty_read(topic, Topic).
|
|
||||||
|
|
||||||
%%--------------------------------------------------------------------
|
-spec(publish(binary(), any()) -> {ok, mqtt_delivery()} | ignore).
|
||||||
%% PubSub API
|
|
||||||
%%--------------------------------------------------------------------
|
|
||||||
|
|
||||||
%% @doc Subscribe a Topic
|
|
||||||
-spec(subscribe(binary(), pid()) -> ok).
|
|
||||||
subscribe(Topic, SubPid) when is_binary(Topic) ->
|
|
||||||
call(pick(Topic), {subscribe, Topic, SubPid}).
|
|
||||||
|
|
||||||
%% @doc Asynchronous Subscribe
|
|
||||||
-spec(async_subscribe(binary(), pid()) -> ok).
|
|
||||||
async_subscribe(Topic, SubPid) when is_binary(Topic) ->
|
|
||||||
cast(pick(Topic), {subscribe, Topic, SubPid}).
|
|
||||||
|
|
||||||
%% @doc Publish message to Topic.
|
|
||||||
-spec(publish(binary(), any()) -> any()).
|
|
||||||
publish(Topic, Msg) ->
|
publish(Topic, Msg) ->
|
||||||
lists:foreach(
|
route(emqttd_router:match(Topic), delivery(Msg)).
|
||||||
fun(#mqtt_route{topic = To, node = Node}) when Node =:= node() ->
|
|
||||||
?MODULE:dispatch(To, Msg);
|
route([], _Delivery) ->
|
||||||
(#mqtt_route{topic = To, node = Node}) ->
|
ignore;
|
||||||
rpc:cast(Node, ?MODULE, dispatch, [To, Msg])
|
|
||||||
end, emqttd_router:lookup(Topic)).
|
%% Dispatch on the local node
|
||||||
|
route([#mqtt_route{topic = To, node = Node}],
|
||||||
|
Delivery = #mqtt_delivery{flows = Flows}) when Node =:= node() ->
|
||||||
|
dispatch(To, Delivery#mqtt_delivery{flows = [{route, Node, To} | Flows]});
|
||||||
|
|
||||||
|
%% Forward to other nodes
|
||||||
|
route([#mqtt_route{topic = To, node = Node}], Delivery = #mqtt_delivery{flows = Flows}) ->
|
||||||
|
forward(Node, To, Delivery#mqtt_delivery{flows = [{route, Node, To}|Flows]});
|
||||||
|
|
||||||
|
route(Routes, Delivery) ->
|
||||||
|
{ok, lists:foldl(fun(Route, DelAcc) ->
|
||||||
|
{ok, DelAcc1} = route([Route], DelAcc), DelAcc1
|
||||||
|
end, Delivery, Routes)}.
|
||||||
|
|
||||||
|
delivery(Msg) -> #mqtt_delivery{sender = self(), message = Msg, flows = []}.
|
||||||
|
|
||||||
|
%% @doc Forward message to another node...
|
||||||
|
forward(Node, To, Delivery) ->
|
||||||
|
rpc:cast(Node, ?PUBSUB, dispatch, [To, Delivery]), {ok, Delivery}.
|
||||||
|
|
||||||
%% @doc Dispatch Message to Subscribers
|
%% @doc Dispatch Message to Subscribers
|
||||||
-spec(dispatch(binary(), mqtt_message()) -> ok).
|
-spec(dispatch(binary(), mqtt_delivery()) -> mqtt_delivery()).
|
||||||
dispatch(Queue = <<"$queue/", _Q/binary>>, Msg) ->
|
dispatch(Topic, Delivery = #mqtt_delivery{message = Msg, flows = Flows}) ->
|
||||||
case subscribers(Queue) of
|
|
||||||
[] ->
|
|
||||||
dropped(Queue);
|
|
||||||
[SubPid] ->
|
|
||||||
SubPid ! {dispatch, Queue, Msg};
|
|
||||||
SubPids ->
|
|
||||||
Idx = crypto:rand_uniform(1, length(SubPids) + 1),
|
|
||||||
SubPid = lists:nth(Idx, SubPids),
|
|
||||||
SubPid ! {dispatch, Queue, Msg}
|
|
||||||
end;
|
|
||||||
|
|
||||||
dispatch(Topic, Msg) ->
|
|
||||||
case subscribers(Topic) of
|
case subscribers(Topic) of
|
||||||
[] ->
|
[] ->
|
||||||
dropped(Topic);
|
dropped(Topic), {ok, Delivery};
|
||||||
[SubPid] ->
|
[Sub] -> %% optimize?
|
||||||
SubPid ! {dispatch, Topic, Msg};
|
dispatch(Sub, Topic, Msg),
|
||||||
SubPids ->
|
{ok, Delivery#mqtt_delivery{flows = [{dispatch, Topic, 1} | Flows]}};
|
||||||
lists:foreach(fun(SubPid) ->
|
Subscribers ->
|
||||||
SubPid ! {dispatch, Topic, Msg}
|
Flows1 = [{dispatch, Topic, length(Subscribers)} | Flows],
|
||||||
end, SubPids)
|
lists:foreach(fun(Sub) -> dispatch(Sub, Topic, Msg) end, Subscribers),
|
||||||
|
{ok, Delivery#mqtt_delivery{flows = Flows1}}
|
||||||
end.
|
end.
|
||||||
|
|
||||||
%% @private
|
dispatch(Pid, Topic, Msg) when is_pid(Pid) ->
|
||||||
%% @doc Find all subscribers
|
Pid ! {dispatch, Topic, Msg};
|
||||||
|
dispatch(SubId, Topic, Msg) when is_binary(SubId) ->
|
||||||
|
emqttd_sm:dispatch(SubId, Topic, Msg).
|
||||||
|
|
||||||
subscribers(Topic) ->
|
subscribers(Topic) ->
|
||||||
case ets:member(subscriber, Topic) of
|
try ets:lookup_element(mqtt_subscriber, Topic, 2) catch error:badarg -> [] end.
|
||||||
true -> %% faster then lookup?
|
|
||||||
try ets:lookup_element(subscriber, Topic, 2) catch error:badarg -> [] end;
|
|
||||||
false ->
|
|
||||||
[]
|
|
||||||
end.
|
|
||||||
|
|
||||||
%% @private
|
%% @private
|
||||||
%% @doc Ingore $SYS Messages.
|
%% @doc Ingore $SYS Messages.
|
||||||
|
@ -147,50 +105,44 @@ dropped(<<"$SYS/", _/binary>>) ->
|
||||||
dropped(_Topic) ->
|
dropped(_Topic) ->
|
||||||
emqttd_metrics:inc('messages/dropped').
|
emqttd_metrics:inc('messages/dropped').
|
||||||
|
|
||||||
%% @doc Unsubscribe
|
-spec(unsubscribe(binary(), emqttd:subscriber()) -> ok).
|
||||||
-spec(unsubscribe(binary(), pid()) -> ok).
|
unsubscribe(Topic, Subscriber) ->
|
||||||
unsubscribe(Topic, SubPid) when is_binary(Topic) ->
|
call(pick(Topic), {unsubscribe, Topic, Subscriber}).
|
||||||
call(pick(Topic), {unsubscribe, Topic, SubPid}).
|
|
||||||
|
|
||||||
%% @doc Asynchronous Unsubscribe
|
-spec(async_unsubscribe(binary(), emqttd:subscriber()) -> ok).
|
||||||
-spec(async_unsubscribe(binary(), pid()) -> ok).
|
async_unsubscribe(Topic, Subscriber) ->
|
||||||
async_unsubscribe(Topic, SubPid) when is_binary(Topic) ->
|
cast(pick(Topic), {unsubscribe, Topic, Subscriber}).
|
||||||
cast(pick(Topic), {unsubscribe, Topic, SubPid}).
|
|
||||||
|
|
||||||
call(PubSub, Req) when is_pid(PubSub) ->
|
call(Server, Req) ->
|
||||||
gen_server2:call(PubSub, Req, infinity).
|
gen_server2:call(Server, Req, infinity).
|
||||||
|
|
||||||
cast(PubSub, Msg) when is_pid(PubSub) ->
|
cast(Server, Msg) ->
|
||||||
gen_server2:cast(PubSub, Msg).
|
gen_server2:cast(Server, Msg).
|
||||||
|
|
||||||
pick(Topic) ->
|
pick(Topic) ->
|
||||||
gproc_pool:pick_worker(pubsub, Topic).
|
gproc_pool:pick_worker(pubsub, Topic).
|
||||||
|
|
||||||
%%--------------------------------------------------------------------
|
|
||||||
%% gen_server Callbacks
|
|
||||||
%%--------------------------------------------------------------------
|
|
||||||
|
|
||||||
init([Pool, Id, Env]) ->
|
init([Pool, Id, Env]) ->
|
||||||
?GPROC_POOL(join, Pool, Id),
|
?GPROC_POOL(join, Pool, Id),
|
||||||
{ok, #state{pool = Pool, id = Id, env = Env}}.
|
{ok, #state{pool = Pool, id = Id, env = Env}}.
|
||||||
|
|
||||||
handle_call({subscribe, Topic, SubPid}, _From, State) ->
|
handle_call({subscribe, Topic, Subscriber}, _From, State) ->
|
||||||
add_subscriber_(Topic, SubPid),
|
add_subscriber_(Topic, Subscriber),
|
||||||
{reply, ok, setstats(State)};
|
{reply, ok, setstats(State)};
|
||||||
|
|
||||||
handle_call({unsubscribe, Topic, SubPid}, _From, State) ->
|
handle_call({unsubscribe, Topic, Subscriber}, _From, State) ->
|
||||||
del_subscriber_(Topic, SubPid),
|
del_subscriber_(Topic, Subscriber),
|
||||||
{reply, ok, setstats(State)};
|
{reply, ok, setstats(State)};
|
||||||
|
|
||||||
handle_call(Req, _From, State) ->
|
handle_call(Req, _From, State) ->
|
||||||
?UNEXPECTED_REQ(Req, State).
|
?UNEXPECTED_REQ(Req, State).
|
||||||
|
|
||||||
handle_cast({subscribe, Topic, SubPid}, State) ->
|
handle_cast({subscribe, Topic, Subscriber}, State) ->
|
||||||
add_subscriber_(Topic, SubPid),
|
add_subscriber_(Topic, Subscriber),
|
||||||
{noreply, setstats(State)};
|
{noreply, setstats(State)};
|
||||||
|
|
||||||
handle_cast({unsubscribe, Topic, SubPid}, State) ->
|
handle_cast({unsubscribe, Topic, Subscriber}, State) ->
|
||||||
del_subscriber_(Topic, SubPid),
|
del_subscriber_(Topic, Subscriber),
|
||||||
{noreply, setstats(State)};
|
{noreply, setstats(State)};
|
||||||
|
|
||||||
handle_cast(Msg, State) ->
|
handle_cast(Msg, State) ->
|
||||||
|
@ -206,65 +158,20 @@ code_change(_OldVsn, State, _Extra) ->
|
||||||
{ok, State}.
|
{ok, State}.
|
||||||
|
|
||||||
%%--------------------------------------------------------------------
|
%%--------------------------------------------------------------------
|
||||||
%% Internal Functions
|
%% Internel Functions
|
||||||
%%--------------------------------------------------------------------
|
%%--------------------------------------------------------------------
|
||||||
|
|
||||||
add_subscriber_(Topic, SubPid) ->
|
add_subscriber_(Topic, Subscriber) ->
|
||||||
case ets:member(subscriber, Topic) of
|
(not ets:member(mqtt_subscriber, Topic))
|
||||||
false ->
|
andalso emqttd_router:add_route(Topic),
|
||||||
mnesia:transaction(fun add_topic_route_/2, [Topic, node()]),
|
ets:insert(mqtt_subscriber, {Topic, Subscriber}).
|
||||||
setstats(topic);
|
|
||||||
true ->
|
|
||||||
ok
|
|
||||||
end,
|
|
||||||
ets:insert(subscriber, {Topic, SubPid}).
|
|
||||||
|
|
||||||
del_subscriber_(Topic, SubPid) ->
|
del_subscriber_(Topic, Subscriber) ->
|
||||||
ets:delete_object(subscriber, {Topic, SubPid}),
|
ets:delete_object(mqtt_subscriber, {Topic, Subscriber}),
|
||||||
case ets:lookup(subscriber, Topic) of
|
(not ets:member(mqtt_subscriber, Topic))
|
||||||
[] ->
|
andalso emqttd_router:del_route(Topic).
|
||||||
mnesia:transaction(fun del_topic_route_/2, [Topic, node()]),
|
|
||||||
setstats(topic);
|
|
||||||
[_|_] ->
|
|
||||||
ok
|
|
||||||
end.
|
|
||||||
|
|
||||||
add_topic_route_(Topic, Node) ->
|
setstats(State) ->
|
||||||
add_topic_(Topic), emqttd_router:add_route(Topic, Node).
|
emqttd_stats:setstats('subscribers/count', 'subscribers/max',
|
||||||
|
ets:info(mqtt_subscriber, size)), State.
|
||||||
add_topic_(Topic) ->
|
|
||||||
add_topic_(Topic, []).
|
|
||||||
|
|
||||||
add_topic_(Topic, Flags) ->
|
|
||||||
Record = #mqtt_topic{topic = Topic, flags = Flags},
|
|
||||||
case mnesia:wread({topic, Topic}) of
|
|
||||||
[] -> mnesia:write(topic, Record, write);
|
|
||||||
[_] -> ok
|
|
||||||
end.
|
|
||||||
|
|
||||||
del_topic_route_(Topic, Node) ->
|
|
||||||
emqttd_router:del_route(Topic, Node), del_topic_(Topic).
|
|
||||||
|
|
||||||
del_topic_(Topic) ->
|
|
||||||
case emqttd_router:has_route(Topic) of
|
|
||||||
true -> ok;
|
|
||||||
false -> do_del_topic_(Topic)
|
|
||||||
end.
|
|
||||||
|
|
||||||
do_del_topic_(Topic) ->
|
|
||||||
case mnesia:wread({topic, Topic}) of
|
|
||||||
[#mqtt_topic{flags = []}] ->
|
|
||||||
mnesia:delete(topic, Topic, write);
|
|
||||||
_ ->
|
|
||||||
ok
|
|
||||||
end.
|
|
||||||
|
|
||||||
setstats(State) when is_record(State, state) ->
|
|
||||||
setstats(subscriber), State;
|
|
||||||
|
|
||||||
setstats(topic) ->
|
|
||||||
emqttd_stats:setstats('topics/count', 'topics/max', mnesia:table_info(topic, size));
|
|
||||||
|
|
||||||
setstats(subscriber) ->
|
|
||||||
emqttd_stats:setstats('subscribers/count', 'subscribers/max', ets:info(subscriber, size)).
|
|
||||||
|
|
||||||
|
|
|
@ -19,58 +19,66 @@
|
||||||
|
|
||||||
-behaviour(supervisor).
|
-behaviour(supervisor).
|
||||||
|
|
||||||
-include("emqttd.hrl").
|
|
||||||
|
|
||||||
-define(CONCURRENCY_OPTS, [{read_concurrency, true}, {write_concurrency, true}]).
|
|
||||||
|
|
||||||
%% API
|
%% API
|
||||||
-export([start_link/0, pubsub_pool/0]).
|
-export([start_link/0, pubsub_pool/0]).
|
||||||
|
|
||||||
%% Supervisor callbacks
|
%% Supervisor callbacks
|
||||||
-export([init/1]).
|
-export([init/1]).
|
||||||
|
|
||||||
|
-define(CONCURRENCY_OPTS, [{read_concurrency, true}, {write_concurrency, true}]).
|
||||||
|
|
||||||
|
%%--------------------------------------------------------------------
|
||||||
|
%% API
|
||||||
|
%%--------------------------------------------------------------------
|
||||||
|
|
||||||
start_link() ->
|
start_link() ->
|
||||||
supervisor:start_link({local, ?MODULE}, ?MODULE, [emqttd_broker:env(pubsub)]).
|
supervisor:start_link({local, ?MODULE}, ?MODULE, [emqttd_conf:pubsub()]).
|
||||||
|
|
||||||
pubsub_pool() ->
|
pubsub_pool() ->
|
||||||
hd([Pid|| {pubsub_pool, Pid, _, _} <- supervisor:which_children(?MODULE)]).
|
hd([Pid || {pubsub_pool, Pid, _, _} <- supervisor:which_children(?MODULE)]).
|
||||||
|
|
||||||
|
%%--------------------------------------------------------------------
|
||||||
|
%% Supervisor callbacks
|
||||||
|
%%--------------------------------------------------------------------
|
||||||
|
|
||||||
init([Env]) ->
|
init([Env]) ->
|
||||||
|
%% Create ETS Tables
|
||||||
|
[create_tab(Tab) || Tab <- [mqtt_subproperty, mqtt_subscriber, mqtt_subscription]],
|
||||||
|
|
||||||
%% Create ETS Tabs
|
{ok, { {one_for_all, 10, 3600}, [pool_sup(pubsub, Env), pool_sup(server, Env)]} }.
|
||||||
create_tab(subscriber), create_tab(subscribed),
|
|
||||||
|
|
||||||
%% Router
|
%%--------------------------------------------------------------------
|
||||||
Router = {router, {emqttd_router, start_link, []},
|
%% Pool
|
||||||
permanent, 5000, worker, [emqttd_router]},
|
%%--------------------------------------------------------------------
|
||||||
|
|
||||||
%% PubSub Pool Sup
|
|
||||||
PubSubMFA = {emqttd_pubsub, start_link, [Env]},
|
|
||||||
PubSubPoolSup = emqttd_pool_sup:spec(pubsub_pool, [pubsub, hash, pool_size(Env), PubSubMFA]),
|
|
||||||
|
|
||||||
%% Server Pool Sup
|
|
||||||
ServerMFA = {emqttd_server, start_link, [Env]},
|
|
||||||
ServerPoolSup = emqttd_pool_sup:spec(server_pool, [server, hash, pool_size(Env), ServerMFA]),
|
|
||||||
|
|
||||||
{ok, {{one_for_all, 5, 60}, [Router, PubSubPoolSup, ServerPoolSup]}}.
|
|
||||||
|
|
||||||
pool_size(Env) ->
|
pool_size(Env) ->
|
||||||
Schedulers = erlang:system_info(schedulers),
|
Schedulers = erlang:system_info(schedulers),
|
||||||
proplists:get_value(pool_size, Env, Schedulers).
|
proplists:get_value(pool_size, Env, Schedulers).
|
||||||
|
|
||||||
create_tab(subscriber) ->
|
pool_sup(Name, Env) ->
|
||||||
%% subscriber: Topic -> Pid1, Pid2, ..., PidN
|
Pool = list_to_atom(atom_to_list(Name) ++ "_pool"),
|
||||||
%% duplicate_bag: o(1) insert
|
MFA = {emqttd:adapter(Name), start_link, [Env]},
|
||||||
ensure_tab(subscriber, [public, named_table, duplicate_bag | ?CONCURRENCY_OPTS]);
|
emqttd_pool_sup:spec(Pool, [Name, hash, pool_size(Env), MFA]).
|
||||||
|
|
||||||
create_tab(subscribed) ->
|
|
||||||
%% subscribed: Pid -> Topic1, Topic2, ..., TopicN
|
%%--------------------------------------------------------------------
|
||||||
|
%% Create PubSub Tables
|
||||||
|
%%--------------------------------------------------------------------
|
||||||
|
|
||||||
|
create_tab(mqtt_subproperty) ->
|
||||||
|
%% Subproperty: {Topic, Sub} -> [{qos, 1}]
|
||||||
|
ensure_tab(mqtt_subproperty, [public, named_table, set | ?CONCURRENCY_OPTS]);
|
||||||
|
|
||||||
|
create_tab(mqtt_subscriber) ->
|
||||||
|
%% Subscriber: Topic -> Sub1, Sub2, Sub3, ..., SubN
|
||||||
|
%% duplicate_bag: o(1) insert
|
||||||
|
ensure_tab(mqtt_subscriber, [public, named_table, duplicate_bag | ?CONCURRENCY_OPTS]);
|
||||||
|
|
||||||
|
create_tab(mqtt_subscription) ->
|
||||||
|
%% Subscription: Sub -> Topic1, Topic2, Topic3, ..., TopicN
|
||||||
%% bag: o(n) insert
|
%% bag: o(n) insert
|
||||||
ensure_tab(subscribed, [public, named_table, bag | ?CONCURRENCY_OPTS]).
|
ensure_tab(mqtt_subscription, [public, named_table, bag | ?CONCURRENCY_OPTS]).
|
||||||
|
|
||||||
ensure_tab(Tab, Opts) ->
|
ensure_tab(Tab, Opts) ->
|
||||||
case ets:info(Tab, name) of
|
case ets:info(Tab, name) of undefined -> ets:new(Tab, Opts); _ -> ok end.
|
||||||
undefined -> ets:new(Tab, Opts);
|
|
||||||
_ -> ok
|
|
||||||
end.
|
|
||||||
|
|
||||||
|
|
|
@ -23,8 +23,16 @@
|
||||||
|
|
||||||
-include("emqttd_internal.hrl").
|
-include("emqttd_internal.hrl").
|
||||||
|
|
||||||
|
-include_lib("stdlib/include/ms_transform.hrl").
|
||||||
|
|
||||||
|
%% Mnesia Callbacks
|
||||||
|
-export([mnesia/1]).
|
||||||
|
|
||||||
|
-boot_mnesia({mnesia, [boot]}).
|
||||||
|
-copy_mnesia({mnesia, [copy]}).
|
||||||
|
|
||||||
%% API Function Exports
|
%% API Function Exports
|
||||||
-export([retain/1, dispatch/2]).
|
-export([retain/1, read_messages/1, dispatch/2]).
|
||||||
|
|
||||||
%% API Function Exports
|
%% API Function Exports
|
||||||
-export([start_link/0]).
|
-export([start_link/0]).
|
||||||
|
@ -33,8 +41,26 @@
|
||||||
-export([init/1, handle_call/3, handle_cast/2, handle_info/2,
|
-export([init/1, handle_call/3, handle_cast/2, handle_info/2,
|
||||||
terminate/2, code_change/3]).
|
terminate/2, code_change/3]).
|
||||||
|
|
||||||
|
-record(retained_message, {topic, msg}).
|
||||||
|
|
||||||
-record(state, {stats_fun, expired_after, stats_timer, expire_timer}).
|
-record(state, {stats_fun, expired_after, stats_timer, expire_timer}).
|
||||||
|
|
||||||
|
%%--------------------------------------------------------------------
|
||||||
|
%% Mnesia callbacks
|
||||||
|
%%--------------------------------------------------------------------
|
||||||
|
|
||||||
|
mnesia(boot) ->
|
||||||
|
ok = emqttd_mnesia:create_table(retained_message, [
|
||||||
|
{type, ordered_set},
|
||||||
|
{disc_copies, [node()]},
|
||||||
|
{record_name, retained_message},
|
||||||
|
{attributes, record_info(fields, retained_message)},
|
||||||
|
{storage_properties, [{ets, [compressed]},
|
||||||
|
{dets, [{auto_save, 1000}]}]}]);
|
||||||
|
|
||||||
|
mnesia(copy) ->
|
||||||
|
ok = emqttd_mnesia:copy_table(retained_message).
|
||||||
|
|
||||||
%%--------------------------------------------------------------------
|
%%--------------------------------------------------------------------
|
||||||
%% API
|
%% API
|
||||||
%%--------------------------------------------------------------------
|
%%--------------------------------------------------------------------
|
||||||
|
@ -50,14 +76,14 @@ retain(#mqtt_message{retain = false}) -> ignore;
|
||||||
|
|
||||||
%% RETAIN flag set to 1 and payload containing zero bytes
|
%% RETAIN flag set to 1 and payload containing zero bytes
|
||||||
retain(#mqtt_message{retain = true, topic = Topic, payload = <<>>}) ->
|
retain(#mqtt_message{retain = true, topic = Topic, payload = <<>>}) ->
|
||||||
emqttd_backend:delete_message(Topic);
|
delete_message(Topic);
|
||||||
|
|
||||||
retain(Msg = #mqtt_message{topic = Topic, retain = true, payload = Payload}) ->
|
retain(Msg = #mqtt_message{topic = Topic, retain = true, payload = Payload}) ->
|
||||||
TabSize = emqttd_backend:retained_count(),
|
TabSize = retained_count(),
|
||||||
case {TabSize < limit(table), size(Payload) < limit(payload)} of
|
case {TabSize < limit(table), size(Payload) < limit(payload)} of
|
||||||
{true, true} ->
|
{true, true} ->
|
||||||
emqttd_backend:retain_message(Msg),
|
retain_message(Msg),
|
||||||
emqttd_metrics:set('messages/retained', emqttd_backend:retained_count());
|
emqttd_metrics:set('messages/retained', retained_count());
|
||||||
{false, _}->
|
{false, _}->
|
||||||
lager:error("Cannot retain message(topic=~s) for table is full!", [Topic]);
|
lager:error("Cannot retain message(topic=~s) for table is full!", [Topic]);
|
||||||
{_, false}->
|
{_, false}->
|
||||||
|
@ -71,7 +97,7 @@ limit(payload) -> env(max_playload_size).
|
||||||
env(Key) ->
|
env(Key) ->
|
||||||
case get({retained, Key}) of
|
case get({retained, Key}) of
|
||||||
undefined ->
|
undefined ->
|
||||||
Env = emqttd_broker:env(retained),
|
Env = emqttd_conf:retained(),
|
||||||
Val = proplists:get_value(Key, Env),
|
Val = proplists:get_value(Key, Env),
|
||||||
put({retained, Key}, Val), Val;
|
put({retained, Key}, Val), Val;
|
||||||
Val ->
|
Val ->
|
||||||
|
@ -82,8 +108,8 @@ env(Key) ->
|
||||||
-spec(dispatch(Topic :: binary(), CPid :: pid()) -> any()).
|
-spec(dispatch(Topic :: binary(), CPid :: pid()) -> any()).
|
||||||
dispatch(Topic, CPid) when is_binary(Topic) ->
|
dispatch(Topic, CPid) when is_binary(Topic) ->
|
||||||
Msgs = case emqttd_topic:wildcard(Topic) of
|
Msgs = case emqttd_topic:wildcard(Topic) of
|
||||||
false -> emqttd_backend:read_messages(Topic);
|
false -> read_messages(Topic);
|
||||||
true -> emqttd_backend:match_messages(Topic)
|
true -> match_messages(Topic)
|
||||||
end,
|
end,
|
||||||
lists:foreach(fun(Msg) -> CPid ! {dispatch, Topic, Msg} end, lists:reverse(Msgs)).
|
lists:foreach(fun(Msg) -> CPid ! {dispatch, Topic, Msg} end, lists:reverse(Msgs)).
|
||||||
|
|
||||||
|
@ -113,7 +139,7 @@ handle_cast(Msg, State) ->
|
||||||
?UNEXPECTED_MSG(Msg, State).
|
?UNEXPECTED_MSG(Msg, State).
|
||||||
|
|
||||||
handle_info(stats, State = #state{stats_fun = StatsFun}) ->
|
handle_info(stats, State = #state{stats_fun = StatsFun}) ->
|
||||||
StatsFun(emqttd_backend:retained_count()),
|
StatsFun(retained_count()),
|
||||||
{noreply, State, hibernate};
|
{noreply, State, hibernate};
|
||||||
|
|
||||||
handle_info(expire, State = #state{expired_after = Never})
|
handle_info(expire, State = #state{expired_after = Never})
|
||||||
|
@ -121,7 +147,7 @@ handle_info(expire, State = #state{expired_after = Never})
|
||||||
{noreply, State, hibernate};
|
{noreply, State, hibernate};
|
||||||
|
|
||||||
handle_info(expire, State = #state{expired_after = ExpiredAfter}) ->
|
handle_info(expire, State = #state{expired_after = ExpiredAfter}) ->
|
||||||
emqttd_backend:expire_messages(emqttd_time:now_to_secs() - ExpiredAfter),
|
expire_messages(emqttd_time:now_to_secs() - ExpiredAfter),
|
||||||
{noreply, State, hibernate};
|
{noreply, State, hibernate};
|
||||||
|
|
||||||
handle_info(Info, State) ->
|
handle_info(Info, State) ->
|
||||||
|
@ -134,3 +160,47 @@ terminate(_Reason, _State = #state{stats_timer = TRef1, expire_timer = TRef2}) -
|
||||||
code_change(_OldVsn, State, _Extra) ->
|
code_change(_OldVsn, State, _Extra) ->
|
||||||
{ok, State}.
|
{ok, State}.
|
||||||
|
|
||||||
|
%%--------------------------------------------------------------------
|
||||||
|
%% Internal Functions
|
||||||
|
%%--------------------------------------------------------------------
|
||||||
|
|
||||||
|
-spec(retain_message(mqtt_message()) -> ok).
|
||||||
|
retain_message(Msg = #mqtt_message{topic = Topic}) ->
|
||||||
|
mnesia:dirty_write(#retained_message{topic = Topic, msg = Msg}).
|
||||||
|
|
||||||
|
-spec(read_messages(binary()) -> [mqtt_message()]).
|
||||||
|
read_messages(Topic) ->
|
||||||
|
[Msg || #retained_message{msg = Msg} <- mnesia:dirty_read(retained_message, Topic)].
|
||||||
|
|
||||||
|
-spec(match_messages(binary()) -> [mqtt_message()]).
|
||||||
|
match_messages(Filter) ->
|
||||||
|
%% TODO: optimize later...
|
||||||
|
Fun = fun(#retained_message{topic = Name, msg = Msg}, Acc) ->
|
||||||
|
case emqttd_topic:match(Name, Filter) of
|
||||||
|
true -> [Msg|Acc];
|
||||||
|
false -> Acc
|
||||||
|
end
|
||||||
|
end,
|
||||||
|
mnesia:async_dirty(fun mnesia:foldl/3, [Fun, [], retained_message]).
|
||||||
|
|
||||||
|
-spec(delete_message(binary()) -> ok).
|
||||||
|
delete_message(Topic) ->
|
||||||
|
mnesia:dirty_delete(retained_message, Topic).
|
||||||
|
|
||||||
|
-spec(expire_messages(pos_integer()) -> any()).
|
||||||
|
expire_messages(Time) when is_integer(Time) ->
|
||||||
|
mnesia:transaction(
|
||||||
|
fun() ->
|
||||||
|
Match = ets:fun2ms(
|
||||||
|
fun(#retained_message{topic = Topic, msg = #mqtt_message{timestamp = Ts}})
|
||||||
|
when Time > Ts -> Topic
|
||||||
|
end),
|
||||||
|
Topics = mnesia:select(retained_message, Match, write),
|
||||||
|
lists:foreach(fun(<<"$SYS/", _/binary>>) -> ok; %% ignore $SYS/# messages
|
||||||
|
(Topic) -> mnesia:delete({retained_message, Topic})
|
||||||
|
end, Topics)
|
||||||
|
end).
|
||||||
|
|
||||||
|
-spec(retained_count() -> non_neg_integer()).
|
||||||
|
retained_count() -> mnesia:table_info(retained_message, size).
|
||||||
|
|
||||||
|
|
|
@ -16,6 +16,8 @@
|
||||||
|
|
||||||
-module(emqttd_router).
|
-module(emqttd_router).
|
||||||
|
|
||||||
|
-author("Feng Lee <feng@emqtt.io>").
|
||||||
|
|
||||||
-behaviour(gen_server).
|
-behaviour(gen_server).
|
||||||
|
|
||||||
-include("emqttd.hrl").
|
-include("emqttd.hrl").
|
||||||
|
@ -27,55 +29,73 @@
|
||||||
-copy_mnesia({mnesia, [copy]}).
|
-copy_mnesia({mnesia, [copy]}).
|
||||||
|
|
||||||
%% Start/Stop
|
%% Start/Stop
|
||||||
-export([start_link/0, stop/0]).
|
-export([start_link/0, topics/0, local_topics/0, stop/0]).
|
||||||
|
|
||||||
%% Route APIs
|
%% Route APIs
|
||||||
-export([add_route/1, add_route/2, add_routes/1, lookup/1, print/1,
|
-export([add_route/1, add_route/2, add_routes/1, match/1, print/1,
|
||||||
del_route/1, del_route/2, del_routes/1, has_route/1]).
|
del_route/1, del_route/2, del_routes/1, has_route/1]).
|
||||||
|
|
||||||
|
%% Local Route API
|
||||||
|
-export([add_local_route/1, del_local_route/1, match_local/1]).
|
||||||
|
|
||||||
%% gen_server Function Exports
|
%% gen_server Function Exports
|
||||||
-export([init/1, handle_call/3, handle_cast/2, handle_info/2,
|
-export([init/1, handle_call/3, handle_cast/2, handle_info/2,
|
||||||
terminate/2, code_change/3]).
|
terminate/2, code_change/3]).
|
||||||
|
|
||||||
|
-export([dump/0]).
|
||||||
|
|
||||||
-record(state, {stats_timer}).
|
-record(state, {stats_timer}).
|
||||||
|
|
||||||
|
-define(ROUTER, ?MODULE).
|
||||||
|
|
||||||
%%--------------------------------------------------------------------
|
%%--------------------------------------------------------------------
|
||||||
%% Mnesia Bootstrap
|
%% Mnesia Bootstrap
|
||||||
%%--------------------------------------------------------------------
|
%%--------------------------------------------------------------------
|
||||||
|
|
||||||
mnesia(boot) ->
|
mnesia(boot) ->
|
||||||
ok = emqttd_mnesia:create_table(route, [
|
ok = emqttd_mnesia:create_table(mqtt_topic, [
|
||||||
|
{ram_copies, [node()]},
|
||||||
|
{record_name, mqtt_topic},
|
||||||
|
{attributes, record_info(fields, mqtt_topic)}]),
|
||||||
|
ok = emqttd_mnesia:create_table(mqtt_route, [
|
||||||
{type, bag},
|
{type, bag},
|
||||||
{ram_copies, [node()]},
|
{ram_copies, [node()]},
|
||||||
{record_name, mqtt_route},
|
{record_name, mqtt_route},
|
||||||
{attributes, record_info(fields, mqtt_route)}]);
|
{attributes, record_info(fields, mqtt_route)}]);
|
||||||
|
|
||||||
mnesia(copy) ->
|
mnesia(copy) ->
|
||||||
ok = emqttd_mnesia:copy_table(route, ram_copies).
|
ok = emqttd_mnesia:copy_table(topic),
|
||||||
|
ok = emqttd_mnesia:copy_table(mqtt_route, ram_copies).
|
||||||
|
|
||||||
%%--------------------------------------------------------------------
|
%%--------------------------------------------------------------------
|
||||||
%% Start the Router
|
%% Start the Router
|
||||||
%%--------------------------------------------------------------------
|
%%--------------------------------------------------------------------
|
||||||
|
|
||||||
start_link() ->
|
start_link() ->
|
||||||
gen_server:start_link({local, ?MODULE}, ?MODULE, [], []).
|
gen_server:start_link({local, ?ROUTER}, ?MODULE, [], []).
|
||||||
|
|
||||||
%%--------------------------------------------------------------------
|
%%--------------------------------------------------------------------
|
||||||
%% API
|
%% API
|
||||||
%%--------------------------------------------------------------------
|
%%--------------------------------------------------------------------
|
||||||
|
|
||||||
%% @doc Lookup Routes.
|
topics() ->
|
||||||
-spec(lookup(Topic:: binary()) -> [mqtt_route()]).
|
mnesia:dirty_all_keys(mqtt_route).
|
||||||
lookup(Topic) when is_binary(Topic) ->
|
|
||||||
|
local_topics() ->
|
||||||
|
ets:select(mqtt_local_route, [{{'$1', '_'}, [], ['$1']}]).
|
||||||
|
|
||||||
|
%% @doc Match Routes.
|
||||||
|
-spec(match(Topic:: binary()) -> [mqtt_route()]).
|
||||||
|
match(Topic) when is_binary(Topic) ->
|
||||||
Matched = mnesia:async_dirty(fun emqttd_trie:match/1, [Topic]),
|
Matched = mnesia:async_dirty(fun emqttd_trie:match/1, [Topic]),
|
||||||
%% Optimize: route table will be replicated to all nodes.
|
%% Optimize: route table will be replicated to all nodes.
|
||||||
lists:append([ets:lookup(route, To) || To <- [Topic | Matched]]).
|
lists:append([ets:lookup(mqtt_route, To) || To <- [Topic | Matched]]).
|
||||||
|
|
||||||
%% @doc Print Routes.
|
%% @doc Print Routes.
|
||||||
-spec(print(Topic :: binary()) -> [ok]).
|
-spec(print(Topic :: binary()) -> [ok]).
|
||||||
print(Topic) ->
|
print(Topic) ->
|
||||||
[io:format("~s -> ~s~n", [To, Node]) ||
|
[io:format("~s -> ~s~n", [To, Node]) ||
|
||||||
#mqtt_route{topic = To, node = Node} <- lookup(Topic)].
|
#mqtt_route{topic = To, node = Node} <- match(Topic)].
|
||||||
|
|
||||||
%% @doc Add Route
|
%% @doc Add Route
|
||||||
-spec(add_route(binary() | mqtt_route()) -> ok | {error, Reason :: any()}).
|
-spec(add_route(binary() | mqtt_route()) -> ok | {error, Reason :: any()}).
|
||||||
|
@ -99,17 +119,18 @@ add_routes(Routes) ->
|
||||||
|
|
||||||
%% @private
|
%% @private
|
||||||
add_route_(Route = #mqtt_route{topic = Topic}) ->
|
add_route_(Route = #mqtt_route{topic = Topic}) ->
|
||||||
case mnesia:wread({route, Topic}) of
|
case mnesia:wread({mqtt_route, Topic}) of
|
||||||
[] ->
|
[] ->
|
||||||
case emqttd_topic:wildcard(Topic) of
|
case emqttd_topic:wildcard(Topic) of
|
||||||
true -> emqttd_trie:insert(Topic);
|
true -> emqttd_trie:insert(Topic);
|
||||||
false -> ok
|
false -> ok
|
||||||
end,
|
end,
|
||||||
mnesia:write(route, Route, write);
|
mnesia:write(Route),
|
||||||
|
mnesia:write(#mqtt_topic{topic = Topic});
|
||||||
Records ->
|
Records ->
|
||||||
case lists:member(Route, Records) of
|
case lists:member(Route, Records) of
|
||||||
true -> ok;
|
true -> ok;
|
||||||
false -> mnesia:write(route, Route, write)
|
false -> mnesia:write(Route)
|
||||||
end
|
end
|
||||||
end.
|
end.
|
||||||
|
|
||||||
|
@ -134,27 +155,28 @@ del_routes(Routes) ->
|
||||||
end.
|
end.
|
||||||
|
|
||||||
del_route_(Route = #mqtt_route{topic = Topic}) ->
|
del_route_(Route = #mqtt_route{topic = Topic}) ->
|
||||||
case mnesia:wread({route, Topic}) of
|
case mnesia:wread({mqtt_route, Topic}) of
|
||||||
[] ->
|
[] ->
|
||||||
ok;
|
ok;
|
||||||
[Route] ->
|
[Route] ->
|
||||||
%% Remove route and trie
|
%% Remove route and trie
|
||||||
mnesia:delete_object(route, Route, write),
|
mnesia:delete_object(Route),
|
||||||
case emqttd_topic:wildcard(Topic) of
|
case emqttd_topic:wildcard(Topic) of
|
||||||
true -> emqttd_trie:delete(Topic);
|
true -> emqttd_trie:delete(Topic);
|
||||||
false -> ok
|
false -> ok
|
||||||
end;
|
end,
|
||||||
|
mnesia:delete({mqtt_topic, Topic});
|
||||||
_More ->
|
_More ->
|
||||||
%% Remove route only
|
%% Remove route only
|
||||||
mnesia:delete_object(route, Route, write)
|
mnesia:delete_object(Route)
|
||||||
end.
|
end.
|
||||||
|
|
||||||
%% @doc Has Route?
|
%% @doc Has Route?
|
||||||
-spec(has_route(binary()) -> boolean()).
|
-spec(has_route(binary()) -> boolean()).
|
||||||
has_route(Topic) ->
|
has_route(Topic) ->
|
||||||
Routes = case mnesia:is_transaction() of
|
Routes = case mnesia:is_transaction() of
|
||||||
true -> mnesia:read(route, Topic);
|
true -> mnesia:read(mqtt_route, Topic);
|
||||||
false -> mnesia:dirty_read(route, Topic)
|
false -> mnesia:dirty_read(mqtt_route, Topic)
|
||||||
end,
|
end,
|
||||||
length(Routes) > 0.
|
length(Routes) > 0.
|
||||||
|
|
||||||
|
@ -166,7 +188,28 @@ trans(Fun) ->
|
||||||
{aborted, Error} -> {error, Error}
|
{aborted, Error} -> {error, Error}
|
||||||
end.
|
end.
|
||||||
|
|
||||||
stop() -> gen_server:call(?MODULE, stop).
|
%%--------------------------------------------------------------------
|
||||||
|
%% Local Route API
|
||||||
|
%%--------------------------------------------------------------------
|
||||||
|
|
||||||
|
-spec(add_local_route(binary()) -> ok).
|
||||||
|
add_local_route(Topic) ->
|
||||||
|
gen_server:cast(?ROUTER, {add_local_route, Topic}).
|
||||||
|
|
||||||
|
-spec(del_local_route(binary()) -> ok).
|
||||||
|
del_local_route(Topic) ->
|
||||||
|
gen_server:cast(?ROUTER, {del_local_route, Topic}).
|
||||||
|
|
||||||
|
-spec(match_local(binary()) -> [mqtt_route()]).
|
||||||
|
match_local(Name) ->
|
||||||
|
[#mqtt_route{topic = {local, Filter}, node = Node}
|
||||||
|
|| {Filter, Node} <- ets:tab2list(mqtt_local_route),
|
||||||
|
emqttd_topic:match(Name, Filter)].
|
||||||
|
|
||||||
|
dump() ->
|
||||||
|
[{route, ets:tab2list(mqtt_route)}, {local_route, ets:tab2list(mqtt_local_route)}].
|
||||||
|
|
||||||
|
stop() -> gen_server:call(?ROUTER, stop).
|
||||||
|
|
||||||
%%--------------------------------------------------------------------
|
%%--------------------------------------------------------------------
|
||||||
%% gen_server Callbacks
|
%% gen_server Callbacks
|
||||||
|
@ -174,6 +217,7 @@ stop() -> gen_server:call(?MODULE, stop).
|
||||||
|
|
||||||
init([]) ->
|
init([]) ->
|
||||||
mnesia:subscribe(system),
|
mnesia:subscribe(system),
|
||||||
|
ets:new(mqtt_local_route, [set, named_table, protected]),
|
||||||
{ok, TRef} = timer:send_interval(timer:seconds(1), stats),
|
{ok, TRef} = timer:send_interval(timer:seconds(1), stats),
|
||||||
{ok, #state{stats_timer = TRef}}.
|
{ok, #state{stats_timer = TRef}}.
|
||||||
|
|
||||||
|
@ -183,6 +227,15 @@ handle_call(stop, _From, State) ->
|
||||||
handle_call(_Req, _From, State) ->
|
handle_call(_Req, _From, State) ->
|
||||||
{reply, ignore, State}.
|
{reply, ignore, State}.
|
||||||
|
|
||||||
|
handle_cast({add_local_route, Topic}, State) ->
|
||||||
|
%% why node()...?
|
||||||
|
ets:insert(mqtt_local_route, {Topic, node()}),
|
||||||
|
{noreply, State};
|
||||||
|
|
||||||
|
handle_cast({del_local_route, Topic}, State) ->
|
||||||
|
ets:delete(mqtt_local_route, Topic),
|
||||||
|
{noreply, State};
|
||||||
|
|
||||||
handle_cast(_Msg, State) ->
|
handle_cast(_Msg, State) ->
|
||||||
{noreply, State}.
|
{noreply, State}.
|
||||||
|
|
||||||
|
|
|
@ -16,6 +16,8 @@
|
||||||
|
|
||||||
-module(emqttd_server).
|
-module(emqttd_server).
|
||||||
|
|
||||||
|
-author("Feng Lee <feng@emqtt.io>").
|
||||||
|
|
||||||
-behaviour(gen_server2).
|
-behaviour(gen_server2).
|
||||||
|
|
||||||
-include("emqttd.hrl").
|
-include("emqttd.hrl").
|
||||||
|
@ -24,49 +26,31 @@
|
||||||
|
|
||||||
-include("emqttd_internal.hrl").
|
-include("emqttd_internal.hrl").
|
||||||
|
|
||||||
%% Mnesia Callbacks
|
|
||||||
-export([mnesia/1]).
|
|
||||||
|
|
||||||
-boot_mnesia({mnesia, [boot]}).
|
|
||||||
-copy_mnesia({mnesia, [copy]}).
|
|
||||||
|
|
||||||
%% API Exports
|
|
||||||
-export([start_link/3]).
|
-export([start_link/3]).
|
||||||
|
|
||||||
%% PubSub API
|
%% PubSub API.
|
||||||
-export([subscribe/1, subscribe/3, publish/1, unsubscribe/1, unsubscribe/3,
|
-export([subscribe/1, subscribe/2, subscribe/3, publish/1,
|
||||||
lookup_subscription/1, update_subscription/4]).
|
unsubscribe/1, unsubscribe/2]).
|
||||||
|
|
||||||
%% gen_server Function Exports
|
%% Async PubSub API.
|
||||||
|
-export([async_subscribe/1, async_subscribe/2, async_subscribe/3,
|
||||||
|
async_unsubscribe/1, async_unsubscribe/2]).
|
||||||
|
|
||||||
|
%% Management API.
|
||||||
|
-export([setqos/3, subscriptions/1, subscribers/1, is_subscribed/2,
|
||||||
|
subscriber_down/1]).
|
||||||
|
|
||||||
|
%% Debug API
|
||||||
|
-export([dump/0]).
|
||||||
|
|
||||||
|
%% gen_server.
|
||||||
-export([init/1, handle_call/3, handle_cast/2, handle_info/2,
|
-export([init/1, handle_call/3, handle_cast/2, handle_info/2,
|
||||||
terminate/2, code_change/3]).
|
terminate/2, code_change/3]).
|
||||||
|
|
||||||
-record(state, {pool, id, env, monitors}).
|
-record(state, {pool, id, env, submon :: emqttd_pmon:pmon()}).
|
||||||
|
|
||||||
%%--------------------------------------------------------------------
|
%% @doc Start server
|
||||||
%% Mnesia callbacks
|
-spec(start_link(atom(), pos_integer(), list()) -> {ok, pid()} | ignore | {error, any()}).
|
||||||
%%--------------------------------------------------------------------
|
|
||||||
|
|
||||||
mnesia(boot) ->
|
|
||||||
ok = emqttd_mnesia:create_table(subscription, [
|
|
||||||
{type, bag},
|
|
||||||
{ram_copies, [node()]},
|
|
||||||
{local_content, true}, %% subscription table is local
|
|
||||||
{record_name, mqtt_subscription},
|
|
||||||
{attributes, record_info(fields, mqtt_subscription)}]);
|
|
||||||
|
|
||||||
mnesia(copy) ->
|
|
||||||
ok = emqttd_mnesia:copy_table(subscription).
|
|
||||||
|
|
||||||
%%--------------------------------------------------------------------
|
|
||||||
%% Start server
|
|
||||||
%%--------------------------------------------------------------------
|
|
||||||
|
|
||||||
%% @doc Start a Server
|
|
||||||
-spec(start_link(Pool, Id, Env) -> {ok, pid()} | ignore | {error, any()} when
|
|
||||||
Pool :: atom(),
|
|
||||||
Id :: pos_integer(),
|
|
||||||
Env :: list(tuple())).
|
|
||||||
start_link(Pool, Id, Env) ->
|
start_link(Pool, Id, Env) ->
|
||||||
gen_server2:start_link({local, ?PROC_NAME(?MODULE, Id)}, ?MODULE, [Pool, Id, Env], []).
|
gen_server2:start_link({local, ?PROC_NAME(?MODULE, Id)}, ?MODULE, [Pool, Id, Env], []).
|
||||||
|
|
||||||
|
@ -75,30 +59,37 @@ start_link(Pool, Id, Env) ->
|
||||||
%%--------------------------------------------------------------------
|
%%--------------------------------------------------------------------
|
||||||
|
|
||||||
%% @doc Subscribe a Topic
|
%% @doc Subscribe a Topic
|
||||||
-spec(subscribe(binary()) -> ok).
|
-spec(subscribe(binary()) -> ok | emqttd:pubsub_error()).
|
||||||
subscribe(Topic) when is_binary(Topic) ->
|
subscribe(Topic) when is_binary(Topic) ->
|
||||||
From = self(), call(server(From), {subscribe, From, Topic}).
|
subscribe(Topic, self()).
|
||||||
|
|
||||||
%% @doc Subscribe from a MQTT session.
|
-spec(subscribe(binary(), emqttd:subscriber()) -> ok | emqttd:pubsub_error()).
|
||||||
-spec(subscribe(binary(), binary(), mqtt_qos()) -> ok).
|
subscribe(Topic, Subscriber) when is_binary(Topic) ->
|
||||||
subscribe(ClientId, Topic, Qos) ->
|
subscribe(Topic, Subscriber, []).
|
||||||
From = self(), call(server(From), {subscribe, From, ClientId, Topic, ?QOS_I(Qos)}).
|
|
||||||
|
|
||||||
%% @doc Lookup subscriptions.
|
-spec(subscribe(binary(), emqttd:subscriber(), [emqttd:suboption()]) ->
|
||||||
-spec(lookup_subscription(binary()) -> [#mqtt_subscription{}]).
|
ok | emqttd:pubsub_error()).
|
||||||
lookup_subscription(ClientId) ->
|
subscribe(Topic, Subscriber, Options) when is_binary(Topic) ->
|
||||||
mnesia:dirty_read(subscription, ClientId).
|
call(pick(Subscriber), {subscribe, Topic, Subscriber, Options}).
|
||||||
|
|
||||||
%% @doc Update a subscription.
|
%% @doc Subscribe a Topic Asynchronously
|
||||||
-spec(update_subscription(binary(), binary(), mqtt_qos(), mqtt_qos()) -> ok).
|
-spec(async_subscribe(binary()) -> ok).
|
||||||
update_subscription(ClientId, Topic, OldQos, NewQos) ->
|
async_subscribe(Topic) when is_binary(Topic) ->
|
||||||
call(server(self()), {update_subscription, ClientId, Topic, ?QOS_I(OldQos), ?QOS_I(NewQos)}).
|
async_subscribe(Topic, self()).
|
||||||
|
|
||||||
%% @doc Publish a Message
|
-spec(async_subscribe(binary(), emqttd:subscriber()) -> ok).
|
||||||
-spec(publish(Msg :: mqtt_message()) -> any()).
|
async_subscribe(Topic, Subscriber) when is_binary(Topic) ->
|
||||||
|
async_subscribe(Topic, Subscriber, []).
|
||||||
|
|
||||||
|
-spec(async_subscribe(binary(), emqttd:subscriber(), [emqttd:suboption()]) -> ok).
|
||||||
|
async_subscribe(Topic, Subscriber, Options) when is_binary(Topic) ->
|
||||||
|
cast(pick(Subscriber), {subscribe, Topic, Subscriber, Options}).
|
||||||
|
|
||||||
|
%% @doc Publish message to Topic.
|
||||||
|
-spec(publish(mqtt_message()) -> {ok, mqtt_delivery()} | ignore).
|
||||||
publish(Msg = #mqtt_message{from = From}) ->
|
publish(Msg = #mqtt_message{from = From}) ->
|
||||||
trace(publish, From, Msg),
|
trace(publish, From, Msg),
|
||||||
case emqttd:run_hooks('message.publish', [], Msg) of
|
case emqttd_hook:run('message.publish', [], Msg) of
|
||||||
{ok, Msg1 = #mqtt_message{topic = Topic}} ->
|
{ok, Msg1 = #mqtt_message{topic = Topic}} ->
|
||||||
%% Retain message first. Don't create retained topic.
|
%% Retain message first. Don't create retained topic.
|
||||||
Msg2 = case emqttd_retainer:retain(Msg1) of
|
Msg2 = case emqttd_retainer:retain(Msg1) of
|
||||||
|
@ -107,24 +98,70 @@ publish(Msg = #mqtt_message{from = From}) ->
|
||||||
end,
|
end,
|
||||||
emqttd_pubsub:publish(Topic, Msg2);
|
emqttd_pubsub:publish(Topic, Msg2);
|
||||||
{stop, Msg1} ->
|
{stop, Msg1} ->
|
||||||
lager:warning("Stop publishing: ~s", [emqttd_message:format(Msg1)])
|
lager:warning("Stop publishing: ~s", [emqttd_message:format(Msg1)]),
|
||||||
|
ignore
|
||||||
end.
|
end.
|
||||||
|
|
||||||
%% @doc Unsubscribe a Topic
|
trace(publish, From, _Msg) when is_atom(From) ->
|
||||||
-spec(unsubscribe(binary()) -> ok).
|
%% Dont' trace '$SYS' publish
|
||||||
unsubscribe(Topic) when is_binary(Topic) ->
|
ignore;
|
||||||
From = self(), call(server(From), {unsubscribe, From, Topic}).
|
|
||||||
|
|
||||||
%% @doc Unsubscribe a Topic from a MQTT session
|
trace(publish, From, #mqtt_message{topic = Topic, payload = Payload}) ->
|
||||||
-spec(unsubscribe(binary(), binary(), mqtt_qos()) -> ok).
|
lager:info([{client, From}, {topic, Topic}],
|
||||||
unsubscribe(ClientId, Topic, Qos) ->
|
"~s PUBLISH to ~s: ~p", [From, Topic, Payload]).
|
||||||
From = self(), call(server(From), {unsubscribe, From, ClientId, Topic, Qos}).
|
|
||||||
|
%% @doc Unsubscribe
|
||||||
|
-spec(unsubscribe(binary()) -> ok | emqttd:pubsub_error()).
|
||||||
|
unsubscribe(Topic) when is_binary(Topic) ->
|
||||||
|
unsubscribe(Topic, self()).
|
||||||
|
|
||||||
|
%% @doc Unsubscribe
|
||||||
|
-spec(unsubscribe(binary(), emqttd:subscriber()) -> ok | emqttd:pubsub_error()).
|
||||||
|
unsubscribe(Topic, Subscriber) when is_binary(Topic) ->
|
||||||
|
call(pick(Subscriber), {unsubscribe, Topic, Subscriber}).
|
||||||
|
|
||||||
|
%% @doc Async Unsubscribe
|
||||||
|
-spec(async_unsubscribe(binary()) -> ok).
|
||||||
|
async_unsubscribe(Topic) when is_binary(Topic) ->
|
||||||
|
async_unsubscribe(Topic, self()).
|
||||||
|
|
||||||
|
-spec(async_unsubscribe(binary(), emqttd:subscriber()) -> ok).
|
||||||
|
async_unsubscribe(Topic, Subscriber) when is_binary(Topic) ->
|
||||||
|
cast(pick(Subscriber), {unsubscribe, Topic, Subscriber}).
|
||||||
|
|
||||||
|
setqos(Topic, Subscriber, Qos) when is_binary(Topic) ->
|
||||||
|
call(pick(Subscriber), {setqos, Topic, Subscriber, Qos}).
|
||||||
|
|
||||||
|
-spec(subscriptions(emqttd:subscriber()) -> [{binary(), list(emqttd:suboption())}]).
|
||||||
|
subscriptions(Subscriber) ->
|
||||||
|
lists:map(fun({_, Topic}) ->
|
||||||
|
subscription(Topic, Subscriber)
|
||||||
|
end, ets:lookup(mqtt_subscription, Subscriber)).
|
||||||
|
|
||||||
|
subscription(Topic, Subscriber) ->
|
||||||
|
{Topic, Subscriber, ets:lookup_element(mqtt_subproperty, {Topic, Subscriber}, 2)}.
|
||||||
|
|
||||||
|
subscribers(Topic) -> emqttd_pubsub:subscribers(Topic).
|
||||||
|
|
||||||
|
-spec(is_subscribed(binary(), emqttd:subscriber()) -> boolean()).
|
||||||
|
is_subscribed(Topic, Subscriber) when is_binary(Topic) ->
|
||||||
|
ets:member(mqtt_subproperty, {Topic, Subscriber}).
|
||||||
|
|
||||||
|
-spec(subscriber_down(emqttd:subscriber()) -> ok).
|
||||||
|
subscriber_down(Subscriber) ->
|
||||||
|
cast(pick(Subscriber), {subscriber_down, Subscriber}).
|
||||||
|
|
||||||
call(Server, Req) ->
|
call(Server, Req) ->
|
||||||
gen_server2:call(Server, Req, infinity).
|
gen_server2:call(Server, Req, infinity).
|
||||||
|
|
||||||
server(From) ->
|
cast(Server, Msg) when is_pid(Server) ->
|
||||||
gproc_pool:pick_worker(server, From).
|
gen_server2:cast(Server, Msg).
|
||||||
|
|
||||||
|
pick(Subscriber) ->
|
||||||
|
gproc_pool:pick_worker(server, Subscriber).
|
||||||
|
|
||||||
|
dump() ->
|
||||||
|
[{Tab, ets:tab2list(Tab)} || Tab <- [mqtt_subproperty, mqtt_subscription, mqtt_subscriber]].
|
||||||
|
|
||||||
%%--------------------------------------------------------------------
|
%%--------------------------------------------------------------------
|
||||||
%% gen_server Callbacks
|
%% gen_server Callbacks
|
||||||
|
@ -132,58 +169,56 @@ server(From) ->
|
||||||
|
|
||||||
init([Pool, Id, Env]) ->
|
init([Pool, Id, Env]) ->
|
||||||
?GPROC_POOL(join, Pool, Id),
|
?GPROC_POOL(join, Pool, Id),
|
||||||
{ok, #state{pool = Pool, id = Id, env = Env, monitors = dict:new()}}.
|
{ok, #state{pool = Pool, id = Id, env = Env, submon = emqttd_pmon:new()}}.
|
||||||
|
|
||||||
handle_call({subscribe, SubPid, ClientId, Topic, Qos}, _From, State) ->
|
handle_call({subscribe, Topic, Subscriber, Options}, _From, State) ->
|
||||||
pubsub_subscribe_(SubPid, Topic),
|
case do_subscribe_(Topic, Subscriber, Options, State) of
|
||||||
if_subsciption(State, fun() ->
|
{ok, NewState} -> {reply, ok, setstats(NewState)};
|
||||||
add_subscription_(ClientId, Topic, Qos),
|
{error, Error} -> {reply, {error, Error}, State}
|
||||||
set_subscription_stats()
|
end;
|
||||||
end),
|
|
||||||
ok(monitor_subscriber_(ClientId, SubPid, State));
|
|
||||||
|
|
||||||
handle_call({subscribe, SubPid, Topic}, _From, State) ->
|
handle_call({unsubscribe, Topic, Subscriber}, _From, State) ->
|
||||||
pubsub_subscribe_(SubPid, Topic),
|
case do_unsubscribe_(Topic, Subscriber, State) of
|
||||||
ok(monitor_subscriber_(undefined, SubPid, State));
|
{ok, NewState} -> {reply, ok, setstats(NewState), hibernate};
|
||||||
|
{error, Error} -> {reply, {error, Error}, State}
|
||||||
|
end;
|
||||||
|
|
||||||
handle_call({update_subscription, ClientId, Topic, OldQos, NewQos}, _From, State) ->
|
handle_call({setqos, Topic, Subscriber, Qos}, _From, State) ->
|
||||||
if_subsciption(State, fun() ->
|
Key = {Topic, Subscriber},
|
||||||
OldSub = #mqtt_subscription{subid = ClientId, topic = Topic, qos = OldQos},
|
case ets:lookup(mqtt_subproperty, Key) of
|
||||||
NewSub = #mqtt_subscription{subid = ClientId, topic = Topic, qos = NewQos},
|
[{_, Opts}] ->
|
||||||
mnesia:transaction(fun update_subscription_/2, [OldSub, NewSub]),
|
Opts1 = lists:ukeymerge(1, [{qos, Qos}], Opts),
|
||||||
set_subscription_stats()
|
ets:insert(mqtt_subproperty, {Key, Opts1}),
|
||||||
end), ok(State);
|
{reply, ok, State};
|
||||||
|
[] ->
|
||||||
handle_call({unsubscribe, SubPid, ClientId, Topic, Qos}, _From, State) ->
|
{reply, {error, {subscription_not_found, Topic}}, State}
|
||||||
pubsub_unsubscribe_(SubPid, Topic),
|
end;
|
||||||
if_subsciption(State, fun() ->
|
|
||||||
del_subscription_(ClientId, Topic, Qos),
|
|
||||||
set_subscription_stats()
|
|
||||||
end), ok(State);
|
|
||||||
|
|
||||||
handle_call({unsubscribe, SubPid, Topic}, _From, State) ->
|
|
||||||
pubsub_unsubscribe_(SubPid, Topic), ok(State);
|
|
||||||
|
|
||||||
handle_call(Req, _From, State) ->
|
handle_call(Req, _From, State) ->
|
||||||
?UNEXPECTED_REQ(Req, State).
|
?UNEXPECTED_REQ(Req, State).
|
||||||
|
|
||||||
|
handle_cast({subscribe, Topic, Subscriber, Options}, State) ->
|
||||||
|
case do_subscribe_(Topic, Subscriber, Options, State) of
|
||||||
|
{ok, NewState} -> {noreply, setstats(NewState)};
|
||||||
|
{error, _Error} -> {noreply, State}
|
||||||
|
end;
|
||||||
|
|
||||||
|
handle_cast({unsubscribe, Topic, Subscriber}, State) ->
|
||||||
|
case do_unsubscribe_(Topic, Subscriber, State) of
|
||||||
|
{ok, NewState} -> {noreply, setstats(NewState), hibernate};
|
||||||
|
{error, _Error} -> {noreply, State}
|
||||||
|
end;
|
||||||
|
|
||||||
|
handle_cast({subscriber_down, Subscriber}, State) ->
|
||||||
|
subscriber_down_(Subscriber),
|
||||||
|
{noreply, setstats(State)};
|
||||||
|
|
||||||
handle_cast(Msg, State) ->
|
handle_cast(Msg, State) ->
|
||||||
?UNEXPECTED_MSG(Msg, State).
|
?UNEXPECTED_MSG(Msg, State).
|
||||||
|
|
||||||
handle_info({'DOWN', _MRef, process, DownPid, _Reason}, State = #state{monitors = Monitors}) ->
|
handle_info({'DOWN', _MRef, process, DownPid, _Reason}, State = #state{submon = PM}) ->
|
||||||
%% unsubscribe
|
subscriber_down_(DownPid),
|
||||||
lists:foreach(fun({_, Topic}) ->
|
{noreply, setstats(State#state{submon = PM:erase(DownPid)}), hibernate};
|
||||||
emqttd_pubsub:async_unsubscribe(Topic, DownPid)
|
|
||||||
end, ets:lookup(subscribed, DownPid)),
|
|
||||||
ets:delete(subscribed, DownPid),
|
|
||||||
|
|
||||||
%% clean subscriptions
|
|
||||||
case dict:find(DownPid, Monitors) of
|
|
||||||
{ok, {undefined, _}} -> ok;
|
|
||||||
{ok, {ClientId, _}} -> mnesia:dirty_delete(subscription, ClientId);
|
|
||||||
error -> ok
|
|
||||||
end,
|
|
||||||
{noreply, State#state{monitors = dict:erase(DownPid, Monitors)}, hibernate};
|
|
||||||
|
|
||||||
handle_info(Info, State) ->
|
handle_info(Info, State) ->
|
||||||
?UNEXPECTED_INFO(Info, State).
|
?UNEXPECTED_INFO(Info, State).
|
||||||
|
@ -198,81 +233,58 @@ code_change(_OldVsn, State, _Extra) ->
|
||||||
%% Internal Functions
|
%% Internal Functions
|
||||||
%%--------------------------------------------------------------------
|
%%--------------------------------------------------------------------
|
||||||
|
|
||||||
if_subsciption(#state{env = Env}, Fun) ->
|
do_subscribe_(Topic, Subscriber, Options, State) ->
|
||||||
case proplists:get_value(subscription, Env, true) of
|
case ets:lookup(mqtt_subproperty, {Topic, Subscriber}) of
|
||||||
false -> ok;
|
|
||||||
_true -> Fun()
|
|
||||||
end.
|
|
||||||
|
|
||||||
%% @private
|
|
||||||
%% @doc Add a subscription.
|
|
||||||
-spec(add_subscription_(binary(), binary(), mqtt_qos()) -> ok).
|
|
||||||
add_subscription_(ClientId, Topic, Qos) ->
|
|
||||||
add_subscription_(#mqtt_subscription{subid = ClientId, topic = Topic, qos = Qos}).
|
|
||||||
|
|
||||||
-spec(add_subscription_(mqtt_subscription()) -> ok).
|
|
||||||
add_subscription_(Subscription) when is_record(Subscription, mqtt_subscription) ->
|
|
||||||
mnesia:dirty_write(subscription, Subscription).
|
|
||||||
|
|
||||||
update_subscription_(OldSub, NewSub) ->
|
|
||||||
mnesia:delete_object(subscription, OldSub, write),
|
|
||||||
mnesia:write(subscription, NewSub, write).
|
|
||||||
|
|
||||||
%% @private
|
|
||||||
%% @doc Delete a subscription
|
|
||||||
-spec(del_subscription_(binary(), binary(), mqtt_qos()) -> ok).
|
|
||||||
del_subscription_(ClientId, Topic, Qos) ->
|
|
||||||
del_subscription_(#mqtt_subscription{subid = ClientId, topic = Topic, qos = Qos}).
|
|
||||||
|
|
||||||
del_subscription_(Subscription) when is_record(Subscription, mqtt_subscription) ->
|
|
||||||
mnesia:dirty_delete_object(subscription, Subscription).
|
|
||||||
|
|
||||||
%% @private
|
|
||||||
%% @doc Call pubsub to subscribe
|
|
||||||
pubsub_subscribe_(SubPid, Topic) ->
|
|
||||||
case ets:match(subscribed, {SubPid, Topic}) of
|
|
||||||
[] ->
|
[] ->
|
||||||
emqttd_pubsub:async_subscribe(Topic, SubPid),
|
emqttd_pubsub:async_subscribe(Topic, Subscriber),
|
||||||
ets:insert(subscribed, {SubPid, Topic});
|
ets:insert(mqtt_subscription, {Subscriber, Topic}),
|
||||||
|
ets:insert(mqtt_subproperty, {{Topic, Subscriber}, Options}),
|
||||||
|
{ok, monitor_subpid(Subscriber, State)};
|
||||||
[_] ->
|
[_] ->
|
||||||
false
|
{error, {already_subscribed, Topic}}
|
||||||
end.
|
end.
|
||||||
|
|
||||||
%% @private
|
monitor_subpid(SubPid, State = #state{submon = PMon}) when is_pid(SubPid) ->
|
||||||
pubsub_unsubscribe_(SubPid, Topic) ->
|
State#state{submon = PMon:monitor(SubPid)};
|
||||||
emqttd_pubsub:async_unsubscribe(Topic, SubPid),
|
monitor_subpid(_SubPid, State) ->
|
||||||
ets:delete_object(subscribed, {SubPid, Topic}).
|
State.
|
||||||
|
|
||||||
monitor_subscriber_(ClientId, SubPid, State = #state{monitors = Monitors}) ->
|
do_unsubscribe_(Topic, Subscriber, State) ->
|
||||||
case dict:find(SubPid, Monitors) of
|
case ets:lookup(mqtt_subproperty, {Topic, Subscriber}) of
|
||||||
{ok, _} ->
|
[_] ->
|
||||||
State;
|
emqttd_pubsub:async_unsubscribe(Topic, Subscriber),
|
||||||
error ->
|
ets:delete_object(mqtt_subscription, {Subscriber, Topic}),
|
||||||
MRef = erlang:monitor(process, SubPid),
|
ets:delete(mqtt_subproperty, {Topic, Subscriber}),
|
||||||
State#state{monitors = dict:store(SubPid, {ClientId, MRef}, Monitors)}
|
{ok, case ets:member(mqtt_subscription, Subscriber) of
|
||||||
|
true -> State;
|
||||||
|
false -> demonitor_subpid(Subscriber, State)
|
||||||
|
end};
|
||||||
|
[] ->
|
||||||
|
{error, {subscription_not_found, Topic}}
|
||||||
end.
|
end.
|
||||||
|
|
||||||
%%--------------------------------------------------------------------
|
demonitor_subpid(SubPid, State = #state{submon = PMon}) when is_pid(SubPid) ->
|
||||||
%% Trace Functions
|
State#state{submon = PMon:demonitor(SubPid)};
|
||||||
%%--------------------------------------------------------------------
|
demonitor_subpid(_SubPid, State) ->
|
||||||
|
State.
|
||||||
|
|
||||||
trace(publish, From, _Msg) when is_atom(From) ->
|
subscriber_down_(Subscriber) ->
|
||||||
%% Dont' trace '$SYS' publish
|
lists:foreach(fun({_, Topic}) ->
|
||||||
ignore;
|
subscriber_down_(Subscriber, Topic)
|
||||||
|
end, ets:lookup(mqtt_subscription, Subscriber)),
|
||||||
|
ets:delete(mqtt_subscription, Subscriber).
|
||||||
|
|
||||||
trace(publish, From, #mqtt_message{topic = Topic, payload = Payload}) ->
|
subscriber_down_(Subscriber, Topic) ->
|
||||||
lager:info([{client, From}, {topic, Topic}],
|
case ets:lookup(mqtt_subproperty, {Topic, Subscriber}) of
|
||||||
"~s PUBLISH to ~s: ~p", [From, Topic, Payload]).
|
[] ->
|
||||||
|
%% here?
|
||||||
|
emqttd_pubsub:async_unsubscribe(Topic, Subscriber);
|
||||||
|
[_] ->
|
||||||
|
emqttd_pubsub:async_unsubscribe(Topic, Subscriber),
|
||||||
|
ets:delete(mqtt_subproperty, {Topic, Subscriber})
|
||||||
|
end.
|
||||||
|
|
||||||
%%--------------------------------------------------------------------
|
setstats(State) ->
|
||||||
%% Subscription Statistics
|
|
||||||
%%--------------------------------------------------------------------
|
|
||||||
|
|
||||||
set_subscription_stats() ->
|
|
||||||
emqttd_stats:setstats('subscriptions/count', 'subscriptions/max',
|
emqttd_stats:setstats('subscriptions/count', 'subscriptions/max',
|
||||||
mnesia:table_info(subscription, size)).
|
ets:info(mqtt_subscription, size)), State.
|
||||||
|
|
||||||
%%--------------------------------------------------------------------
|
|
||||||
|
|
||||||
ok(State) -> {reply, ok, State}.
|
|
||||||
|
|
||||||
|
|
|
@ -77,6 +77,9 @@
|
||||||
%% Old Client Pid that has been kickout
|
%% Old Client Pid that has been kickout
|
||||||
old_client_pid :: pid(),
|
old_client_pid :: pid(),
|
||||||
|
|
||||||
|
%% Username
|
||||||
|
username :: binary() | undefined,
|
||||||
|
|
||||||
%% Last packet id of the session
|
%% Last packet id of the session
|
||||||
packet_id = 1,
|
packet_id = 1,
|
||||||
|
|
||||||
|
@ -136,9 +139,9 @@
|
||||||
"Session(~s): " ++ Format, [State#session.client_id | Args])).
|
"Session(~s): " ++ Format, [State#session.client_id | Args])).
|
||||||
|
|
||||||
%% @doc Start a session.
|
%% @doc Start a session.
|
||||||
-spec(start_link(boolean(), mqtt_client_id(), pid()) -> {ok, pid()} | {error, any()}).
|
-spec(start_link(boolean(), {mqtt_client_id(), mqtt_username()}, pid()) -> {ok, pid()} | {error, any()}).
|
||||||
start_link(CleanSess, ClientId, ClientPid) ->
|
start_link(CleanSess, {ClientId, Username}, ClientPid) ->
|
||||||
gen_server2:start_link(?MODULE, [CleanSess, ClientId, ClientPid], []).
|
gen_server2:start_link(?MODULE, [CleanSess, {ClientId, Username}, ClientPid], []).
|
||||||
|
|
||||||
%% @doc Resume a session.
|
%% @doc Resume a session.
|
||||||
-spec(resume(pid(), mqtt_client_id(), pid()) -> ok).
|
-spec(resume(pid(), mqtt_client_id(), pid()) -> ok).
|
||||||
|
@ -175,11 +178,11 @@ subscribe(SessPid, PacketId, TopicTable) ->
|
||||||
-spec(publish(pid(), mqtt_message()) -> ok | {error, any()}).
|
-spec(publish(pid(), mqtt_message()) -> ok | {error, any()}).
|
||||||
publish(_SessPid, Msg = #mqtt_message{qos = ?QOS_0}) ->
|
publish(_SessPid, Msg = #mqtt_message{qos = ?QOS_0}) ->
|
||||||
%% publish qos0 directly
|
%% publish qos0 directly
|
||||||
emqttd:publish(Msg);
|
emqttd:publish(Msg), ok;
|
||||||
|
|
||||||
publish(_SessPid, Msg = #mqtt_message{qos = ?QOS_1}) ->
|
publish(_SessPid, Msg = #mqtt_message{qos = ?QOS_1}) ->
|
||||||
%% publish qos1 directly, and client will puback automatically
|
%% publish qos1 directly, and client will puback automatically
|
||||||
emqttd:publish(Msg);
|
emqttd:publish(Msg), ok;
|
||||||
|
|
||||||
publish(SessPid, Msg = #mqtt_message{qos = ?QOS_2}) ->
|
publish(SessPid, Msg = #mqtt_message{qos = ?QOS_2}) ->
|
||||||
%% publish qos2 by session
|
%% publish qos2 by session
|
||||||
|
@ -208,22 +211,22 @@ unsubscribe(SessPid, Topics) ->
|
||||||
gen_server2:cast(SessPid, {unsubscribe, Topics}).
|
gen_server2:cast(SessPid, {unsubscribe, Topics}).
|
||||||
|
|
||||||
%%--------------------------------------------------------------------
|
%%--------------------------------------------------------------------
|
||||||
%% gen_server callbacks
|
%% gen_server Callbacks
|
||||||
%%--------------------------------------------------------------------
|
%%--------------------------------------------------------------------
|
||||||
|
|
||||||
init([CleanSess, ClientId, ClientPid]) ->
|
init([CleanSess, {ClientId, Username}, ClientPid]) ->
|
||||||
process_flag(trap_exit, true),
|
process_flag(trap_exit, true),
|
||||||
true = link(ClientPid),
|
true = link(ClientPid),
|
||||||
QEnv = emqttd:env(mqtt, queue),
|
SessEnv = emqttd_conf:session(),
|
||||||
SessEnv = emqttd:env(mqtt, session),
|
|
||||||
Session = #session{
|
Session = #session{
|
||||||
clean_sess = CleanSess,
|
clean_sess = CleanSess,
|
||||||
client_id = ClientId,
|
client_id = ClientId,
|
||||||
client_pid = ClientPid,
|
client_pid = ClientPid,
|
||||||
|
username = Username,
|
||||||
subscriptions = dict:new(),
|
subscriptions = dict:new(),
|
||||||
inflight_queue = [],
|
inflight_queue = [],
|
||||||
max_inflight = get_value(max_inflight, SessEnv, 0),
|
max_inflight = get_value(max_inflight, SessEnv, 0),
|
||||||
message_queue = emqttd_mqueue:new(ClientId, QEnv, emqttd_alarm:alarm_fun()),
|
message_queue = emqttd_mqueue:new(ClientId, emqttd_conf:queue(), emqttd_alarm:alarm_fun()),
|
||||||
awaiting_rel = #{},
|
awaiting_rel = #{},
|
||||||
awaiting_ack = #{},
|
awaiting_ack = #{},
|
||||||
awaiting_comp = #{},
|
awaiting_comp = #{},
|
||||||
|
@ -233,8 +236,8 @@ init([CleanSess, ClientId, ClientPid]) ->
|
||||||
expired_after = get_value(expired_after, SessEnv) * 60,
|
expired_after = get_value(expired_after, SessEnv) * 60,
|
||||||
collect_interval = get_value(collect_interval, SessEnv, 0),
|
collect_interval = get_value(collect_interval, SessEnv, 0),
|
||||||
timestamp = os:timestamp()},
|
timestamp = os:timestamp()},
|
||||||
emqttd_sm:register_session(CleanSess, ClientId, sess_info(Session)),
|
emqttd_sm:reg_session(ClientId, CleanSess, sess_info(Session)),
|
||||||
%% start statistics
|
%% Start statistics
|
||||||
{ok, start_collector(Session), hibernate}.
|
{ok, start_collector(Session), hibernate}.
|
||||||
|
|
||||||
prioritise_call(Msg, _From, _Len, _State) ->
|
prioritise_call(Msg, _From, _Len, _State) ->
|
||||||
|
@ -285,62 +288,68 @@ handle_call({publish, Msg = #mqtt_message{qos = ?QOS_2, pktid = PktId}},
|
||||||
handle_call(Req, _From, State) ->
|
handle_call(Req, _From, State) ->
|
||||||
?UNEXPECTED_REQ(Req, State).
|
?UNEXPECTED_REQ(Req, State).
|
||||||
|
|
||||||
handle_cast({subscribe, TopicTable0, AckFun}, Session = #session{client_id = ClientId,
|
%%TODO: 2.0 FIX
|
||||||
subscriptions = Subscriptions}) ->
|
|
||||||
|
|
||||||
case emqttd:run_hooks('client.subscribe', [ClientId], TopicTable0) of
|
handle_cast({subscribe, TopicTable, AckFun}, Session = #session{client_id = ClientId,
|
||||||
{ok, TopicTable} ->
|
username = Username,
|
||||||
|
subscriptions = Subscriptions}) ->
|
||||||
?LOG(info, "Subscribe ~p", [TopicTable], Session),
|
?LOG(info, "Subscribe ~p", [TopicTable], Session),
|
||||||
Subscriptions1 = lists:foldl(
|
{GrantedQos, Subscriptions1} =
|
||||||
fun({Topic, Qos}, SubDict) ->
|
lists:foldl(fun({RawTopic, Qos}, {QosAcc, SubDict}) ->
|
||||||
case dict:find(Topic, SubDict) of
|
{Topic, Opts} = emqttd_topic:strip(RawTopic),
|
||||||
{ok, Qos} ->
|
case emqttd:run_hooks('client.subscribe', [{ClientId, Username}], {Topic, Opts}) of
|
||||||
?LOG(warning, "duplicated subscribe: ~s, qos = ~w", [Topic, Qos], Session),
|
{ok, {Topic1, Opts1}} ->
|
||||||
|
NewQos = proplists:get_value(qos, Opts1, Qos),
|
||||||
|
{[NewQos | QosAcc], case dict:find(Topic, SubDict) of
|
||||||
|
{ok, NewQos} ->
|
||||||
|
?LOG(warning, "duplicated subscribe: ~s, qos = ~w", [Topic, NewQos], Session),
|
||||||
SubDict;
|
SubDict;
|
||||||
{ok, OldQos} ->
|
{ok, OldQos} ->
|
||||||
emqttd_server:update_subscription(ClientId, Topic, OldQos, Qos),
|
emqttd:setqos(Topic, ClientId, NewQos),
|
||||||
?LOG(warning, "duplicated subscribe ~s, old_qos=~w, new_qos=~w", [Topic, OldQos, Qos], Session),
|
?LOG(warning, "duplicated subscribe ~s, old_qos=~w, new_qos=~w", [Topic, OldQos, NewQos], Session),
|
||||||
dict:store(Topic, Qos, SubDict);
|
dict:store(Topic, NewQos, SubDict);
|
||||||
error ->
|
error ->
|
||||||
emqttd:subscribe(ClientId, Topic, Qos),
|
emqttd:subscribe(Topic1, ClientId, Opts1),
|
||||||
%%TODO: the design is ugly...
|
%%TODO: the design is ugly...
|
||||||
%% <MQTT V3.1.1>: 3.8.4
|
%% <MQTT V3.1.1>: 3.8.4
|
||||||
%% Where the Topic Filter is not identical to any existing Subscription’s filter,
|
%% Where the Topic Filter is not identical to any existing Subscription’s filter,
|
||||||
%% a new Subscription is created and all matching retained messages are sent.
|
%% a new Subscription is created and all matching retained messages are sent.
|
||||||
emqttd_retainer:dispatch(Topic, self()),
|
emqttd_retainer:dispatch(Topic1, self()),
|
||||||
|
emqttd:run_hooks('client.subscribe.after', [{ClientId, Username}], {Topic1, Opts1}),
|
||||||
|
|
||||||
dict:store(Topic, Qos, SubDict)
|
dict:store(Topic1, NewQos, SubDict)
|
||||||
|
end};
|
||||||
|
{stop, _} ->
|
||||||
|
?LOG(error, "Cannot subscribe: ~p", [Topic], Session),
|
||||||
|
{[128 | QosAcc], SubDict}
|
||||||
end
|
end
|
||||||
end, Subscriptions, TopicTable),
|
end, {[], Subscriptions}, TopicTable),
|
||||||
AckFun([Qos || {_, Qos} <- TopicTable]),
|
AckFun(lists:reverse(GrantedQos)),
|
||||||
emqttd:run_hooks('client.subscribe.after', [ClientId], TopicTable),
|
|
||||||
hibernate(Session#session{subscriptions = Subscriptions1});
|
hibernate(Session#session{subscriptions = Subscriptions1});
|
||||||
{stop, TopicTable} ->
|
|
||||||
?LOG(error, "Cannot subscribe: ~p", [TopicTable], Session),
|
|
||||||
hibernate(Session)
|
|
||||||
end;
|
|
||||||
|
|
||||||
handle_cast({unsubscribe, Topics0}, Session = #session{client_id = ClientId,
|
%%TODO: 2.0 FIX
|
||||||
|
|
||||||
|
handle_cast({unsubscribe, Topics}, Session = #session{client_id = ClientId,
|
||||||
|
username = Username,
|
||||||
subscriptions = Subscriptions}) ->
|
subscriptions = Subscriptions}) ->
|
||||||
|
|
||||||
case emqttd:run_hooks('client.unsubscribe', [ClientId], Topics0) of
|
|
||||||
{ok, Topics} ->
|
|
||||||
?LOG(info, "unsubscribe ~p", [Topics], Session),
|
?LOG(info, "unsubscribe ~p", [Topics], Session),
|
||||||
Subscriptions1 = lists:foldl(
|
Subscriptions1 =
|
||||||
fun(Topic, SubDict) ->
|
lists:foldl(fun(RawTopic, SubDict) ->
|
||||||
case dict:find(Topic, SubDict) of
|
{Topic0, _Opts} = emqttd_topic:strip(RawTopic),
|
||||||
{ok, Qos} ->
|
case emqttd:run_hooks('client.unsubscribe', [ClientId, Username], Topic0) of
|
||||||
emqttd:unsubscribe(ClientId, Topic, Qos),
|
{ok, Topic1} ->
|
||||||
dict:erase(Topic, SubDict);
|
case dict:find(Topic1, SubDict) of
|
||||||
|
{ok, _Qos} ->
|
||||||
|
emqttd:unsubscribe(Topic1, ClientId),
|
||||||
|
dict:erase(Topic1, SubDict);
|
||||||
error ->
|
error ->
|
||||||
SubDict
|
SubDict
|
||||||
|
end;
|
||||||
|
{stop, _} ->
|
||||||
|
SubDict
|
||||||
end
|
end
|
||||||
end, Subscriptions, Topics),
|
end, Subscriptions, Topics),
|
||||||
hibernate(Session#session{subscriptions = Subscriptions1});
|
hibernate(Session#session{subscriptions = Subscriptions1});
|
||||||
{stop, Topics} ->
|
|
||||||
?LOG(info, "Cannot unsubscribe: ~p", [Topics], Session),
|
|
||||||
hibernate(Session)
|
|
||||||
end;
|
|
||||||
|
|
||||||
handle_cast({destroy, ClientId}, Session = #session{client_id = ClientId}) ->
|
handle_cast({destroy, ClientId}, Session = #session{client_id = ClientId}) ->
|
||||||
?LOG(warning, "destroyed", [], Session),
|
?LOG(warning, "destroyed", [], Session),
|
||||||
|
@ -386,8 +395,7 @@ handle_cast({resume, ClientId, ClientPid}, Session = #session{client_id = C
|
||||||
if
|
if
|
||||||
CleanSess =:= true ->
|
CleanSess =:= true ->
|
||||||
?LOG(warning, "CleanSess changed to false.", [], Session),
|
?LOG(warning, "CleanSess changed to false.", [], Session),
|
||||||
emqttd_sm:unregister_session(CleanSess, ClientId),
|
emqttd_sm:reg_session(ClientId, false, sess_info(Session1));
|
||||||
emqttd_sm:register_session(false, ClientId, sess_info(Session1));
|
|
||||||
CleanSess =:= false ->
|
CleanSess =:= false ->
|
||||||
ok
|
ok
|
||||||
end,
|
end,
|
||||||
|
@ -501,7 +509,7 @@ handle_info({timeout, awaiting_comp, PktId}, Session = #session{awaiting_comp =
|
||||||
end;
|
end;
|
||||||
|
|
||||||
handle_info(collect_info, Session = #session{clean_sess = CleanSess, client_id = ClientId}) ->
|
handle_info(collect_info, Session = #session{clean_sess = CleanSess, client_id = ClientId}) ->
|
||||||
emqttd_sm:register_session(CleanSess, ClientId, sess_info(Session)),
|
emqttd_sm:reg_session(ClientId, CleanSess, sess_info(Session)),
|
||||||
hibernate(start_collector(Session));
|
hibernate(start_collector(Session));
|
||||||
|
|
||||||
handle_info({'EXIT', ClientPid, _Reason}, Session = #session{clean_sess = true,
|
handle_info({'EXIT', ClientPid, _Reason}, Session = #session{clean_sess = true,
|
||||||
|
@ -532,8 +540,9 @@ handle_info(expired, Session) ->
|
||||||
handle_info(Info, Session) ->
|
handle_info(Info, Session) ->
|
||||||
?UNEXPECTED_INFO(Info, Session).
|
?UNEXPECTED_INFO(Info, Session).
|
||||||
|
|
||||||
terminate(_Reason, #session{clean_sess = CleanSess, client_id = ClientId}) ->
|
terminate(_Reason, #session{client_id = ClientId}) ->
|
||||||
emqttd_sm:unregister_session(CleanSess, ClientId).
|
emqttd:subscriber_down(ClientId),
|
||||||
|
emqttd_sm:unreg_session(ClientId).
|
||||||
|
|
||||||
code_change(_OldVsn, Session, _Extra) ->
|
code_change(_OldVsn, Session, _Extra) ->
|
||||||
{ok, Session}.
|
{ok, Session}.
|
||||||
|
@ -650,11 +659,12 @@ await(#mqtt_message{pktid = PktId}, Session = #session{awaiting_ack = Awaiting
|
||||||
Session#session{awaiting_ack = Awaiting1}.
|
Session#session{awaiting_ack = Awaiting1}.
|
||||||
|
|
||||||
acked(PktId, Session = #session{client_id = ClientId,
|
acked(PktId, Session = #session{client_id = ClientId,
|
||||||
|
username = Username,
|
||||||
inflight_queue = InflightQ,
|
inflight_queue = InflightQ,
|
||||||
awaiting_ack = Awaiting}) ->
|
awaiting_ack = Awaiting}) ->
|
||||||
case lists:keyfind(PktId, 1, InflightQ) of
|
case lists:keyfind(PktId, 1, InflightQ) of
|
||||||
{_, Msg} ->
|
{_, Msg} ->
|
||||||
emqttd:run_hooks('message.acked', [ClientId], Msg);
|
emqttd:run_hooks('message.acked', [{ClientId, Username}], Msg);
|
||||||
false ->
|
false ->
|
||||||
?LOG(error, "Cannot find acked pktid: ~p", [PktId], Session)
|
?LOG(error, "Cannot find acked pktid: ~p", [PktId], Session)
|
||||||
end,
|
end,
|
||||||
|
|
|
@ -29,9 +29,9 @@ start_link() ->
|
||||||
supervisor:start_link({local, ?MODULE}, ?MODULE, []).
|
supervisor:start_link({local, ?MODULE}, ?MODULE, []).
|
||||||
|
|
||||||
%% @doc Start a session
|
%% @doc Start a session
|
||||||
-spec(start_session(boolean(), binary(), pid()) -> {ok, pid()}).
|
-spec(start_session(boolean(), {binary(), binary() | undefined} , pid()) -> {ok, pid()}).
|
||||||
start_session(CleanSess, ClientId, ClientPid) ->
|
start_session(CleanSess, {ClientId, Username}, ClientPid) ->
|
||||||
supervisor:start_child(?MODULE, [CleanSess, ClientId, ClientPid]).
|
supervisor:start_child(?MODULE, [CleanSess, {ClientId, Username}, ClientPid]).
|
||||||
|
|
||||||
%%--------------------------------------------------------------------
|
%%--------------------------------------------------------------------
|
||||||
%% Supervisor callbacks
|
%% Supervisor callbacks
|
||||||
|
|
|
@ -32,9 +32,9 @@
|
||||||
%% API Function Exports
|
%% API Function Exports
|
||||||
-export([start_link/2]).
|
-export([start_link/2]).
|
||||||
|
|
||||||
-export([start_session/2, lookup_session/1]).
|
-export([start_session/2, lookup_session/1, reg_session/3, unreg_session/1]).
|
||||||
|
|
||||||
-export([register_session/3, unregister_session/2]).
|
-export([dispatch/3]).
|
||||||
|
|
||||||
%% gen_server Function Exports
|
%% gen_server Function Exports
|
||||||
-export([init/1, handle_call/3, handle_cast/2, handle_info/2,
|
-export([init/1, handle_call/3, handle_cast/2, handle_info/2,
|
||||||
|
@ -58,14 +58,14 @@
|
||||||
|
|
||||||
mnesia(boot) ->
|
mnesia(boot) ->
|
||||||
%% Global Session Table
|
%% Global Session Table
|
||||||
ok = emqttd_mnesia:create_table(session, [
|
ok = emqttd_mnesia:create_table(mqtt_session, [
|
||||||
{type, set},
|
{type, set},
|
||||||
{ram_copies, [node()]},
|
{ram_copies, [node()]},
|
||||||
{record_name, mqtt_session},
|
{record_name, mqtt_session},
|
||||||
{attributes, record_info(fields, mqtt_session)}]);
|
{attributes, record_info(fields, mqtt_session)}]);
|
||||||
|
|
||||||
mnesia(copy) ->
|
mnesia(copy) ->
|
||||||
ok = emqttd_mnesia:copy_table(session).
|
ok = emqttd_mnesia:copy_table(mqtt_session).
|
||||||
|
|
||||||
%%--------------------------------------------------------------------
|
%%--------------------------------------------------------------------
|
||||||
%% API
|
%% API
|
||||||
|
@ -77,36 +77,35 @@ start_link(Pool, Id) ->
|
||||||
gen_server2:start_link({local, ?PROC_NAME(?MODULE, Id)}, ?MODULE, [Pool, Id], []).
|
gen_server2:start_link({local, ?PROC_NAME(?MODULE, Id)}, ?MODULE, [Pool, Id], []).
|
||||||
|
|
||||||
%% @doc Start a session
|
%% @doc Start a session
|
||||||
-spec(start_session(boolean(), binary()) -> {ok, pid(), boolean()} | {error, any()}).
|
-spec(start_session(boolean(), {binary(), binary() | undefined}) -> {ok, pid(), boolean()} | {error, any()}).
|
||||||
start_session(CleanSess, ClientId) ->
|
start_session(CleanSess, {ClientId, Username}) ->
|
||||||
SM = gproc_pool:pick_worker(?POOL, ClientId),
|
SM = gproc_pool:pick_worker(?POOL, ClientId),
|
||||||
call(SM, {start_session, {CleanSess, ClientId, self()}}).
|
call(SM, {start_session, CleanSess, {ClientId, Username}, self()}).
|
||||||
|
|
||||||
%% @doc Lookup a Session
|
%% @doc Lookup a Session
|
||||||
-spec(lookup_session(binary()) -> mqtt_session() | undefined).
|
-spec(lookup_session(binary()) -> mqtt_session() | undefined).
|
||||||
lookup_session(ClientId) ->
|
lookup_session(ClientId) ->
|
||||||
case mnesia:dirty_read(session, ClientId) of
|
case mnesia:dirty_read(mqtt_session, ClientId) of
|
||||||
[Session] -> Session;
|
[Session] -> Session;
|
||||||
[] -> undefined
|
[] -> undefined
|
||||||
end.
|
end.
|
||||||
|
|
||||||
%% @doc Register a session with info.
|
%% @doc Register a session with info.
|
||||||
-spec(register_session(CleanSess, ClientId, Info) -> ok when
|
-spec(reg_session(binary(), boolean(), [tuple()]) -> true).
|
||||||
CleanSess :: boolean(),
|
reg_session(ClientId, CleanSess, Properties) ->
|
||||||
ClientId :: binary(),
|
ets:insert(mqtt_local_session, {ClientId, self(), CleanSess, Properties}).
|
||||||
Info :: [tuple()]).
|
|
||||||
register_session(CleanSess, ClientId, Info) ->
|
|
||||||
ets:insert(sesstab(CleanSess), {{ClientId, self()}, Info}).
|
|
||||||
|
|
||||||
%% @doc Unregister a session.
|
%% @doc Unregister a session.
|
||||||
-spec(unregister_session(CleanSess, ClientId) -> ok when
|
-spec(unreg_session(binary()) -> true).
|
||||||
CleanSess :: boolean(),
|
unreg_session(ClientId) ->
|
||||||
ClientId :: binary()).
|
ets:delete(mqtt_local_session, ClientId).
|
||||||
unregister_session(CleanSess, ClientId) ->
|
|
||||||
ets:delete(sesstab(CleanSess), {ClientId, self()}).
|
|
||||||
|
|
||||||
sesstab(true) -> mqtt_transient_session;
|
dispatch(ClientId, Topic, Msg) ->
|
||||||
sesstab(false) -> mqtt_persistent_session.
|
try ets:lookup_element(mqtt_local_session, ClientId, 2) of
|
||||||
|
Pid -> Pid ! {dispatch, Topic, Msg}
|
||||||
|
catch
|
||||||
|
error:badarg -> io:format("Session Not Found: ~p~n", [ClientId]), ok %%TODO: How??
|
||||||
|
end.
|
||||||
|
|
||||||
call(SM, Req) ->
|
call(SM, Req) ->
|
||||||
gen_server2:call(SM, Req, ?TIMEOUT). %%infinity).
|
gen_server2:call(SM, Req, ?TIMEOUT). %%infinity).
|
||||||
|
@ -129,11 +128,11 @@ prioritise_info(_Msg, _Len, _State) ->
|
||||||
2.
|
2.
|
||||||
|
|
||||||
%% Persistent Session
|
%% Persistent Session
|
||||||
handle_call({start_session, Client = {false, ClientId, ClientPid}}, _From, State) ->
|
handle_call({start_session, false, {ClientId, Username}, ClientPid}, _From, State) ->
|
||||||
case lookup_session(ClientId) of
|
case lookup_session(ClientId) of
|
||||||
undefined ->
|
undefined ->
|
||||||
%% Create session locally
|
%% Create session locally
|
||||||
create_session(Client, State);
|
create_session({false, {ClientId, Username}, ClientPid}, State);
|
||||||
Session ->
|
Session ->
|
||||||
case resume_session(Session, ClientPid) of
|
case resume_session(Session, ClientPid) of
|
||||||
{ok, SessPid} ->
|
{ok, SessPid} ->
|
||||||
|
@ -144,7 +143,8 @@ handle_call({start_session, Client = {false, ClientId, ClientPid}}, _From, State
|
||||||
end;
|
end;
|
||||||
|
|
||||||
%% Transient Session
|
%% Transient Session
|
||||||
handle_call({start_session, Client = {true, ClientId, _ClientPid}}, _From, State) ->
|
handle_call({start_session, true, {ClientId, Username}, ClientPid}, _From, State) ->
|
||||||
|
Client = {true, {ClientId, Username}, ClientPid},
|
||||||
case lookup_session(ClientId) of
|
case lookup_session(ClientId) of
|
||||||
undefined ->
|
undefined ->
|
||||||
create_session(Client, State);
|
create_session(Client, State);
|
||||||
|
@ -167,11 +167,13 @@ handle_info({'DOWN', MRef, process, DownPid, _Reason}, State) ->
|
||||||
case dict:find(MRef, State#state.monitors) of
|
case dict:find(MRef, State#state.monitors) of
|
||||||
{ok, ClientId} ->
|
{ok, ClientId} ->
|
||||||
mnesia:transaction(fun() ->
|
mnesia:transaction(fun() ->
|
||||||
case mnesia:wread({session, ClientId}) of
|
case mnesia:wread({mqtt_session, ClientId}) of
|
||||||
[] -> ok;
|
[] ->
|
||||||
|
ok;
|
||||||
[Sess = #mqtt_session{sess_pid = DownPid}] ->
|
[Sess = #mqtt_session{sess_pid = DownPid}] ->
|
||||||
mnesia:delete_object(session, Sess, write);
|
mnesia:delete_object(mqtt_session, Sess, write);
|
||||||
[_Sess] -> ok
|
[_Sess] ->
|
||||||
|
ok
|
||||||
end
|
end
|
||||||
end),
|
end),
|
||||||
{noreply, erase_monitor(MRef, State), hibernate};
|
{noreply, erase_monitor(MRef, State), hibernate};
|
||||||
|
@ -194,8 +196,8 @@ code_change(_OldVsn, State, _Extra) ->
|
||||||
%%--------------------------------------------------------------------
|
%%--------------------------------------------------------------------
|
||||||
|
|
||||||
%% Create Session Locally
|
%% Create Session Locally
|
||||||
create_session({CleanSess, ClientId, ClientPid}, State) ->
|
create_session({CleanSess, {ClientId, Username}, ClientPid}, State) ->
|
||||||
case create_session(CleanSess, ClientId, ClientPid) of
|
case create_session(CleanSess, {ClientId, Username}, ClientPid) of
|
||||||
{ok, SessPid} ->
|
{ok, SessPid} ->
|
||||||
{reply, {ok, SessPid, false},
|
{reply, {ok, SessPid, false},
|
||||||
monitor_session(ClientId, SessPid, State)};
|
monitor_session(ClientId, SessPid, State)};
|
||||||
|
@ -203,12 +205,10 @@ create_session({CleanSess, ClientId, ClientPid}, State) ->
|
||||||
{reply, {error, Error}, State}
|
{reply, {error, Error}, State}
|
||||||
end.
|
end.
|
||||||
|
|
||||||
create_session(CleanSess, ClientId, ClientPid) ->
|
create_session(CleanSess, {ClientId, Username}, ClientPid) ->
|
||||||
case emqttd_session_sup:start_session(CleanSess, ClientId, ClientPid) of
|
case emqttd_session_sup:start_session(CleanSess, {ClientId, Username}, ClientPid) of
|
||||||
{ok, SessPid} ->
|
{ok, SessPid} ->
|
||||||
Session = #mqtt_session{client_id = ClientId,
|
Session = #mqtt_session{client_id = ClientId, sess_pid = SessPid, persistent = not CleanSess},
|
||||||
sess_pid = SessPid,
|
|
||||||
persistent = not CleanSess},
|
|
||||||
case insert_session(Session) of
|
case insert_session(Session) of
|
||||||
{aborted, {conflict, ConflictPid}} ->
|
{aborted, {conflict, ConflictPid}} ->
|
||||||
%% Conflict with othe node?
|
%% Conflict with othe node?
|
||||||
|
@ -224,17 +224,16 @@ create_session(CleanSess, ClientId, ClientPid) ->
|
||||||
insert_session(Session = #mqtt_session{client_id = ClientId}) ->
|
insert_session(Session = #mqtt_session{client_id = ClientId}) ->
|
||||||
mnesia:transaction(
|
mnesia:transaction(
|
||||||
fun() ->
|
fun() ->
|
||||||
case mnesia:wread({session, ClientId}) of
|
case mnesia:wread({mqtt_session, ClientId}) of
|
||||||
[] ->
|
[] ->
|
||||||
mnesia:write(session, Session, write);
|
mnesia:write(mqtt_session, Session, write);
|
||||||
[#mqtt_session{sess_pid = SessPid}] ->
|
[#mqtt_session{sess_pid = SessPid}] ->
|
||||||
mnesia:abort({conflict, SessPid})
|
mnesia:abort({conflict, SessPid})
|
||||||
end
|
end
|
||||||
end).
|
end).
|
||||||
|
|
||||||
%% Local node
|
%% Local node
|
||||||
resume_session(Session = #mqtt_session{client_id = ClientId,
|
resume_session(Session = #mqtt_session{client_id = ClientId, sess_pid = SessPid}, ClientPid)
|
||||||
sess_pid = SessPid}, ClientPid)
|
|
||||||
when node(SessPid) =:= node() ->
|
when node(SessPid) =:= node() ->
|
||||||
|
|
||||||
case is_process_alive(SessPid) of
|
case is_process_alive(SessPid) of
|
||||||
|
@ -284,7 +283,7 @@ destroy_session(Session = #mqtt_session{client_id = ClientId,
|
||||||
end.
|
end.
|
||||||
|
|
||||||
remove_session(Session) ->
|
remove_session(Session) ->
|
||||||
case mnesia:transaction(fun mnesia:delete_object/3, [session, Session, write]) of
|
case mnesia:transaction(fun mnesia:delete_object/1, [Session]) of
|
||||||
{atomic, ok} -> ok;
|
{atomic, ok} -> ok;
|
||||||
{aborted, Error} -> {error, Error}
|
{aborted, Error} -> {error, Error}
|
||||||
end.
|
end.
|
||||||
|
|
|
@ -54,10 +54,9 @@ handle_info({mnesia_system_event, {mnesia_down, Node}}, State) ->
|
||||||
lager:error("!!!Mnesia node down: ~s", [Node]),
|
lager:error("!!!Mnesia node down: ~s", [Node]),
|
||||||
Fun = fun() ->
|
Fun = fun() ->
|
||||||
ClientIds =
|
ClientIds =
|
||||||
mnesia:select(session, [{#mqtt_session{client_id = '$1', sess_pid = '$2', _ = '_'},
|
mnesia:select(mqtt_session, [{#mqtt_session{client_id = '$1', sess_pid = '$2', _ = '_'},
|
||||||
[{'==', {node, '$2'}, Node}],
|
[{'==', {node, '$2'}, Node}], ['$1']}]),
|
||||||
['$1']}]),
|
lists:foreach(fun(ClientId) -> mnesia:delete({mqtt_session, ClientId}) end, ClientIds)
|
||||||
lists:foreach(fun(ClientId) -> mnesia:delete({session, ClientId}) end, ClientIds)
|
|
||||||
end,
|
end,
|
||||||
mnesia:async_dirty(Fun),
|
mnesia:async_dirty(Fun),
|
||||||
{noreply, State};
|
{noreply, State};
|
||||||
|
@ -83,5 +82,5 @@ code_change(_OldVsn, State, _Extra) ->
|
||||||
%%--------------------------------------------------------------------
|
%%--------------------------------------------------------------------
|
||||||
|
|
||||||
setstats(State = #state{stats_fun = StatsFun}) ->
|
setstats(State = #state{stats_fun = StatsFun}) ->
|
||||||
StatsFun(ets:info(mqtt_persistent_session, size)), State.
|
StatsFun(ets:info(mqtt_local_session, size)), State.
|
||||||
|
|
||||||
|
|
|
@ -25,8 +25,6 @@
|
||||||
|
|
||||||
-define(HELPER, emqttd_sm_helper).
|
-define(HELPER, emqttd_sm_helper).
|
||||||
|
|
||||||
-define(TABS, [mqtt_transient_session, mqtt_persistent_session]).
|
|
||||||
|
|
||||||
%% API
|
%% API
|
||||||
-export([start_link/0]).
|
-export([start_link/0]).
|
||||||
|
|
||||||
|
@ -38,7 +36,7 @@ start_link() ->
|
||||||
|
|
||||||
init([]) ->
|
init([]) ->
|
||||||
%% Create session tables
|
%% Create session tables
|
||||||
create_session_tabs(),
|
ets:new(mqtt_local_session, [public, ordered_set, named_table, {write_concurrency, true}]),
|
||||||
|
|
||||||
%% Helper
|
%% Helper
|
||||||
StatsFun = emqttd_stats:statsfun('sessions/count', 'sessions/max'),
|
StatsFun = emqttd_stats:statsfun('sessions/count', 'sessions/max'),
|
||||||
|
@ -51,8 +49,3 @@ init([]) ->
|
||||||
|
|
||||||
{ok, {{one_for_all, 10, 3600}, [Helper, PoolSup]}}.
|
{ok, {{one_for_all, 10, 3600}, [Helper, PoolSup]}}.
|
||||||
|
|
||||||
create_session_tabs() ->
|
|
||||||
Opts = [ordered_set, named_table, public,
|
|
||||||
{write_concurrency, true}],
|
|
||||||
[ets:new(Tab, Opts) || Tab <- ?TABS].
|
|
||||||
|
|
||||||
|
|
|
@ -122,7 +122,7 @@ init([]) ->
|
||||||
Topics = ?SYSTOP_CLIENTS ++ ?SYSTOP_SESSIONS ++ ?SYSTOP_PUBSUB ++ ?SYSTOP_RETAINED,
|
Topics = ?SYSTOP_CLIENTS ++ ?SYSTOP_SESSIONS ++ ?SYSTOP_PUBSUB ++ ?SYSTOP_RETAINED,
|
||||||
ets:insert(?STATS_TAB, [{Topic, 0} || Topic <- Topics]),
|
ets:insert(?STATS_TAB, [{Topic, 0} || Topic <- Topics]),
|
||||||
% Create $SYS Topics
|
% Create $SYS Topics
|
||||||
[ok = emqttd:create(topic, stats_topic(Topic)) || Topic <- Topics],
|
% [ok = emqttd:create(topic, stats_topic(Topic)) || Topic <- Topics],
|
||||||
% Tick to publish stats
|
% Tick to publish stats
|
||||||
{ok, #state{tick_tref = emqttd_broker:start_tick(tick)}, hibernate}.
|
{ok, #state{tick_tref = emqttd_broker:start_tick(tick)}, hibernate}.
|
||||||
|
|
||||||
|
|
|
@ -162,10 +162,10 @@ publish(Sysmon, WarnMsg) ->
|
||||||
topic(Sysmon) ->
|
topic(Sysmon) ->
|
||||||
emqttd_topic:systop(list_to_binary(lists:concat(['sysmon/', Sysmon]))).
|
emqttd_topic:systop(list_to_binary(lists:concat(['sysmon/', Sysmon]))).
|
||||||
|
|
||||||
start_tracelog(undefined) ->
|
%% start_tracelog(undefined) ->
|
||||||
{ok, undefined};
|
%% {ok, undefined};
|
||||||
start_tracelog(LogFile) ->
|
%% start_tracelog(LogFile) ->
|
||||||
lager:trace_file(LogFile, [{sysmon, true}], info, ?LOG_FMT).
|
%% lager:trace_file(LogFile, [{sysmon, true}], info, ?LOG_FMT).
|
||||||
|
|
||||||
cancel_tracelog(undefined) ->
|
cancel_tracelog(undefined) ->
|
||||||
ok;
|
ok;
|
||||||
|
|
|
@ -28,7 +28,15 @@ start_link() ->
|
||||||
supervisor:start_link({local, ?MODULE}, ?MODULE, []).
|
supervisor:start_link({local, ?MODULE}, ?MODULE, []).
|
||||||
|
|
||||||
init([]) ->
|
init([]) ->
|
||||||
Sysmon = {sysmon, {emqttd_sysmon, start_link, [emqttd:env(sysmon)]},
|
Sysmon = {sysmon, {emqttd_sysmon, start_link, [opts()]},
|
||||||
permanent, 5000, worker, [emqttd_sysmon]} ,
|
permanent, 5000, worker, [emqttd_sysmon]},
|
||||||
{ok, {{one_for_one, 10, 100}, [Sysmon]}}.
|
{ok, {{one_for_one, 10, 100}, [Sysmon]}}.
|
||||||
|
|
||||||
|
opts() ->
|
||||||
|
Opts = [{long_gc, emqttd:conf(sysmon_long_gc)},
|
||||||
|
{long_schedule, emqttd:conf(sysmon_long_schedule)},
|
||||||
|
{large_heap, emqttd:conf(sysmon_large_heap)},
|
||||||
|
{busy_port, emqttd:conf(busy_port)},
|
||||||
|
{busy_dist_port, emqttd:conf(sysmon_busy_dist_port)}],
|
||||||
|
[{Key, Val} || {Key, {ok, Val}} <- Opts].
|
||||||
|
|
||||||
|
|
|
@ -16,17 +16,21 @@
|
||||||
|
|
||||||
-module(emqttd_topic).
|
-module(emqttd_topic).
|
||||||
|
|
||||||
|
-import(lists, [reverse/1]).
|
||||||
|
|
||||||
-export([match/2, validate/1, triples/1, words/1, wildcard/1]).
|
-export([match/2, validate/1, triples/1, words/1, wildcard/1]).
|
||||||
|
|
||||||
-export([join/1, feed_var/3, is_queue/1, systop/1]).
|
-export([join/1, feed_var/3, systop/1]).
|
||||||
|
|
||||||
-type topic() :: binary().
|
-export([strip/1, strip/2]).
|
||||||
|
|
||||||
-type word() :: '' | '+' | '#' | binary().
|
-type(topic() :: binary()).
|
||||||
|
|
||||||
-type words() :: list(word()).
|
-type(word() :: '' | '+' | '#' | binary()).
|
||||||
|
|
||||||
-type triple() :: {root | binary(), word(), binary()}.
|
-type(words() :: list(word())).
|
||||||
|
|
||||||
|
-type(triple() :: {root | binary(), word(), binary()}).
|
||||||
|
|
||||||
-export_type([topic/0, word/0, triple/0]).
|
-export_type([topic/0, word/0, triple/0]).
|
||||||
|
|
||||||
|
@ -111,7 +115,7 @@ triples(Topic) when is_binary(Topic) ->
|
||||||
triples(words(Topic), root, []).
|
triples(words(Topic), root, []).
|
||||||
|
|
||||||
triples([], _Parent, Acc) ->
|
triples([], _Parent, Acc) ->
|
||||||
lists:reverse(Acc);
|
reverse(Acc);
|
||||||
|
|
||||||
triples([W|Words], Parent, Acc) ->
|
triples([W|Words], Parent, Acc) ->
|
||||||
Node = join(Parent, W),
|
Node = join(Parent, W),
|
||||||
|
@ -137,13 +141,6 @@ word(<<"+">>) -> '+';
|
||||||
word(<<"#">>) -> '#';
|
word(<<"#">>) -> '#';
|
||||||
word(Bin) -> Bin.
|
word(Bin) -> Bin.
|
||||||
|
|
||||||
%% @doc Queue is a special topic name that starts with "$queue/"
|
|
||||||
-spec(is_queue(topic()) -> boolean()).
|
|
||||||
is_queue(<<"$queue/", _Queue/binary>>) ->
|
|
||||||
true;
|
|
||||||
is_queue(_) ->
|
|
||||||
false.
|
|
||||||
|
|
||||||
%% @doc '$SYS' Topic.
|
%% @doc '$SYS' Topic.
|
||||||
systop(Name) when is_atom(Name) ->
|
systop(Name) when is_atom(Name) ->
|
||||||
list_to_binary(lists:concat(["$SYS/brokers/", node(), "/", Name]));
|
list_to_binary(lists:concat(["$SYS/brokers/", node(), "/", Name]));
|
||||||
|
@ -155,7 +152,7 @@ systop(Name) when is_binary(Name) ->
|
||||||
feed_var(Var, Val, Topic) ->
|
feed_var(Var, Val, Topic) ->
|
||||||
feed_var(Var, Val, words(Topic), []).
|
feed_var(Var, Val, words(Topic), []).
|
||||||
feed_var(_Var, _Val, [], Acc) ->
|
feed_var(_Var, _Val, [], Acc) ->
|
||||||
join(lists:reverse(Acc));
|
join(reverse(Acc));
|
||||||
feed_var(Var, Val, [Var|Words], Acc) ->
|
feed_var(Var, Val, [Var|Words], Acc) ->
|
||||||
feed_var(Var, Val, Words, [Val|Acc]);
|
feed_var(Var, Val, Words, [Val|Acc]);
|
||||||
feed_var(Var, Val, [W|Words], Acc) ->
|
feed_var(Var, Val, [W|Words], Acc) ->
|
||||||
|
@ -175,3 +172,28 @@ join(Words) ->
|
||||||
end, {true, <<>>}, [bin(W) || W <- Words]),
|
end, {true, <<>>}, [bin(W) || W <- Words]),
|
||||||
Bin.
|
Bin.
|
||||||
|
|
||||||
|
-spec(strip(topic()) -> {topic(), [local | {share, binary()}]}).
|
||||||
|
strip(Topic) when is_binary(Topic) ->
|
||||||
|
strip(Topic, []).
|
||||||
|
|
||||||
|
strip(Topic = <<"$local/", Topic1/binary>>, Options) ->
|
||||||
|
case lists:member(local, Options) of
|
||||||
|
true -> error({invalid_topic, Topic});
|
||||||
|
false -> strip(Topic1, [local | Options])
|
||||||
|
end;
|
||||||
|
|
||||||
|
strip(Topic = <<"$queue/", Topic1/binary>>, Options) ->
|
||||||
|
case lists:keyfind(share, 1, Options) of
|
||||||
|
{share, _} -> error({invalid_topic, Topic});
|
||||||
|
false -> strip(Topic1, [{share, '$queue'} | Options])
|
||||||
|
end;
|
||||||
|
|
||||||
|
strip(Topic = <<"$share/", Topic1/binary>>, Options) ->
|
||||||
|
case lists:keyfind(share, 1, Options) of
|
||||||
|
{share, _} -> error({invalid_topic, Topic});
|
||||||
|
false -> [Share, Topic2] = binary:split(Topic1, <<"/">>),
|
||||||
|
{Topic2, [{share, Share} | Options]}
|
||||||
|
end;
|
||||||
|
|
||||||
|
strip(Topic, Options) -> {Topic, Options}.
|
||||||
|
|
||||||
|
|
|
@ -38,21 +38,21 @@
|
||||||
-spec(mnesia(boot | copy) -> ok).
|
-spec(mnesia(boot | copy) -> ok).
|
||||||
mnesia(boot) ->
|
mnesia(boot) ->
|
||||||
%% Trie Table
|
%% Trie Table
|
||||||
ok = emqttd_mnesia:create_table(trie, [
|
ok = emqttd_mnesia:create_table(mqtt_trie, [
|
||||||
{ram_copies, [node()]},
|
{ram_copies, [node()]},
|
||||||
{record_name, trie},
|
{record_name, trie},
|
||||||
{attributes, record_info(fields, trie)}]),
|
{attributes, record_info(fields, trie)}]),
|
||||||
%% Trie Node Table
|
%% Trie Node Table
|
||||||
ok = emqttd_mnesia:create_table(trie_node, [
|
ok = emqttd_mnesia:create_table(mqtt_trie_node, [
|
||||||
{ram_copies, [node()]},
|
{ram_copies, [node()]},
|
||||||
{record_name, trie_node},
|
{record_name, trie_node},
|
||||||
{attributes, record_info(fields, trie_node)}]);
|
{attributes, record_info(fields, trie_node)}]);
|
||||||
|
|
||||||
mnesia(copy) ->
|
mnesia(copy) ->
|
||||||
%% Copy Trie Table
|
%% Copy Trie Table
|
||||||
ok = emqttd_mnesia:copy_table(trie),
|
ok = emqttd_mnesia:copy_table(mqtt_trie),
|
||||||
%% Copy Trie Node Table
|
%% Copy Trie Node Table
|
||||||
ok = emqttd_mnesia:copy_table(trie_node).
|
ok = emqttd_mnesia:copy_table(mqtt_trie_node).
|
||||||
|
|
||||||
%%--------------------------------------------------------------------
|
%%--------------------------------------------------------------------
|
||||||
%% Trie API
|
%% Trie API
|
||||||
|
@ -61,16 +61,16 @@ mnesia(copy) ->
|
||||||
%% @doc Insert topic to trie
|
%% @doc Insert topic to trie
|
||||||
-spec(insert(Topic :: binary()) -> ok).
|
-spec(insert(Topic :: binary()) -> ok).
|
||||||
insert(Topic) when is_binary(Topic) ->
|
insert(Topic) when is_binary(Topic) ->
|
||||||
case mnesia:read(trie_node, Topic) of
|
case mnesia:read(mqtt_trie_node, Topic) of
|
||||||
[#trie_node{topic=Topic}] ->
|
[#trie_node{topic=Topic}] ->
|
||||||
ok;
|
ok;
|
||||||
[TrieNode=#trie_node{topic=undefined}] ->
|
[TrieNode=#trie_node{topic=undefined}] ->
|
||||||
mnesia:write(TrieNode#trie_node{topic=Topic});
|
write_trie_node(TrieNode#trie_node{topic=Topic});
|
||||||
[] ->
|
[] ->
|
||||||
%add trie path
|
% Add trie path
|
||||||
lists:foreach(fun add_path/1, emqttd_topic:triples(Topic)),
|
lists:foreach(fun add_path/1, emqttd_topic:triples(Topic)),
|
||||||
%add last node
|
% Add last node
|
||||||
mnesia:write(#trie_node{node_id=Topic, topic=Topic})
|
write_trie_node(#trie_node{node_id=Topic, topic=Topic})
|
||||||
end.
|
end.
|
||||||
|
|
||||||
%% @doc Find trie nodes that match topic
|
%% @doc Find trie nodes that match topic
|
||||||
|
@ -82,17 +82,17 @@ match(Topic) when is_binary(Topic) ->
|
||||||
%% @doc Lookup a Trie Node
|
%% @doc Lookup a Trie Node
|
||||||
-spec(lookup(NodeId :: binary()) -> [#trie_node{}]).
|
-spec(lookup(NodeId :: binary()) -> [#trie_node{}]).
|
||||||
lookup(NodeId) ->
|
lookup(NodeId) ->
|
||||||
mnesia:read(trie_node, NodeId).
|
mnesia:read(mqtt_trie_node, NodeId).
|
||||||
|
|
||||||
%% @doc Delete topic from trie
|
%% @doc Delete topic from trie
|
||||||
-spec(delete(Topic :: binary()) -> ok).
|
-spec(delete(Topic :: binary()) -> ok).
|
||||||
delete(Topic) when is_binary(Topic) ->
|
delete(Topic) when is_binary(Topic) ->
|
||||||
case mnesia:read(trie_node, Topic) of
|
case mnesia:read(mqtt_trie_node, Topic) of
|
||||||
[#trie_node{edge_count=0}] ->
|
[#trie_node{edge_count=0}] ->
|
||||||
mnesia:delete({trie_node, Topic}),
|
mnesia:delete({mqtt_trie_node, Topic}),
|
||||||
delete_path(lists:reverse(emqttd_topic:triples(Topic)));
|
delete_path(lists:reverse(emqttd_topic:triples(Topic)));
|
||||||
[TrieNode] ->
|
[TrieNode] ->
|
||||||
mnesia:write(TrieNode#trie_node{topic = undefined});
|
write_trie_node(TrieNode#trie_node{topic = undefined});
|
||||||
[] ->
|
[] ->
|
||||||
ok
|
ok
|
||||||
end.
|
end.
|
||||||
|
@ -105,18 +105,18 @@ delete(Topic) when is_binary(Topic) ->
|
||||||
%% @doc Add path to trie tree.
|
%% @doc Add path to trie tree.
|
||||||
add_path({Node, Word, Child}) ->
|
add_path({Node, Word, Child}) ->
|
||||||
Edge = #trie_edge{node_id=Node, word=Word},
|
Edge = #trie_edge{node_id=Node, word=Word},
|
||||||
case mnesia:read(trie_node, Node) of
|
case mnesia:read(mqtt_trie_node, Node) of
|
||||||
[TrieNode = #trie_node{edge_count=Count}] ->
|
[TrieNode = #trie_node{edge_count=Count}] ->
|
||||||
case mnesia:wread({trie, Edge}) of
|
case mnesia:wread({mqtt_trie, Edge}) of
|
||||||
[] ->
|
[] ->
|
||||||
mnesia:write(TrieNode#trie_node{edge_count=Count+1}),
|
write_trie_node(TrieNode#trie_node{edge_count=Count+1}),
|
||||||
mnesia:write(#trie{edge=Edge, node_id=Child});
|
write_trie(#trie{edge=Edge, node_id=Child});
|
||||||
[_] ->
|
[_] ->
|
||||||
ok
|
ok
|
||||||
end;
|
end;
|
||||||
[] ->
|
[] ->
|
||||||
mnesia:write(#trie_node{node_id=Node, edge_count=1}),
|
write_trie_node(#trie_node{node_id=Node, edge_count=1}),
|
||||||
mnesia:write(#trie{edge=Edge, node_id=Child})
|
write_trie(#trie{edge=Edge, node_id=Child})
|
||||||
end.
|
end.
|
||||||
|
|
||||||
%% @private
|
%% @private
|
||||||
|
@ -128,11 +128,11 @@ match_node(NodeId, Words) ->
|
||||||
match_node(NodeId, Words, []).
|
match_node(NodeId, Words, []).
|
||||||
|
|
||||||
match_node(NodeId, [], ResAcc) ->
|
match_node(NodeId, [], ResAcc) ->
|
||||||
mnesia:read(trie_node, NodeId) ++ 'match_#'(NodeId, ResAcc);
|
mnesia:read(mqtt_trie_node, NodeId) ++ 'match_#'(NodeId, ResAcc);
|
||||||
|
|
||||||
match_node(NodeId, [W|Words], ResAcc) ->
|
match_node(NodeId, [W|Words], ResAcc) ->
|
||||||
lists:foldl(fun(WArg, Acc) ->
|
lists:foldl(fun(WArg, Acc) ->
|
||||||
case mnesia:read(trie, #trie_edge{node_id=NodeId, word=WArg}) of
|
case mnesia:read(mqtt_trie, #trie_edge{node_id=NodeId, word=WArg}) of
|
||||||
[#trie{node_id=ChildId}] -> match_node(ChildId, Words, Acc);
|
[#trie{node_id=ChildId}] -> match_node(ChildId, Words, Acc);
|
||||||
[] -> Acc
|
[] -> Acc
|
||||||
end
|
end
|
||||||
|
@ -141,9 +141,9 @@ match_node(NodeId, [W|Words], ResAcc) ->
|
||||||
%% @private
|
%% @private
|
||||||
%% @doc Match node with '#'.
|
%% @doc Match node with '#'.
|
||||||
'match_#'(NodeId, ResAcc) ->
|
'match_#'(NodeId, ResAcc) ->
|
||||||
case mnesia:read(trie, #trie_edge{node_id=NodeId, word = '#'}) of
|
case mnesia:read(mqtt_trie, #trie_edge{node_id=NodeId, word = '#'}) of
|
||||||
[#trie{node_id=ChildId}] ->
|
[#trie{node_id=ChildId}] ->
|
||||||
mnesia:read(trie_node, ChildId) ++ ResAcc;
|
mnesia:read(mqtt_trie_node, ChildId) ++ ResAcc;
|
||||||
[] ->
|
[] ->
|
||||||
ResAcc
|
ResAcc
|
||||||
end.
|
end.
|
||||||
|
@ -153,16 +153,24 @@ match_node(NodeId, [W|Words], ResAcc) ->
|
||||||
delete_path([]) ->
|
delete_path([]) ->
|
||||||
ok;
|
ok;
|
||||||
delete_path([{NodeId, Word, _} | RestPath]) ->
|
delete_path([{NodeId, Word, _} | RestPath]) ->
|
||||||
mnesia:delete({trie, #trie_edge{node_id=NodeId, word=Word}}),
|
mnesia:delete({mqtt_trie, #trie_edge{node_id=NodeId, word=Word}}),
|
||||||
case mnesia:read(trie_node, NodeId) of
|
case mnesia:read(mqtt_trie_node, NodeId) of
|
||||||
[#trie_node{edge_count=1, topic=undefined}] ->
|
[#trie_node{edge_count=1, topic=undefined}] ->
|
||||||
mnesia:delete({trie_node, NodeId}),
|
mnesia:delete({mqtt_trie_node, NodeId}),
|
||||||
delete_path(RestPath);
|
delete_path(RestPath);
|
||||||
[TrieNode=#trie_node{edge_count=1, topic=_}] ->
|
[TrieNode=#trie_node{edge_count=1, topic=_}] ->
|
||||||
mnesia:write(TrieNode#trie_node{edge_count=0});
|
write_trie_node(TrieNode#trie_node{edge_count=0});
|
||||||
[TrieNode=#trie_node{edge_count=C}] ->
|
[TrieNode=#trie_node{edge_count=C}] ->
|
||||||
mnesia:write(TrieNode#trie_node{edge_count=C-1});
|
write_trie_node(TrieNode#trie_node{edge_count=C-1});
|
||||||
[] ->
|
[] ->
|
||||||
throw({notfound, NodeId})
|
throw({notfound, NodeId})
|
||||||
end.
|
end.
|
||||||
|
|
||||||
|
%% @private
|
||||||
|
write_trie(Trie) ->
|
||||||
|
mnesia:write(mqtt_trie, Trie, write).
|
||||||
|
|
||||||
|
%% @private
|
||||||
|
write_trie_node(TrieNode) ->
|
||||||
|
mnesia:write(mqtt_trie_node, TrieNode, write).
|
||||||
|
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue