MongoosePush
MongoosePush is a simple, RESTful service written in Elixir, providing ability to send push
notifications to FCM
(Firebase Cloud Messaging) and/or
APNS
(Apple Push Notification Service) via their HTTP/2
API.
Quick start
Docker
Running from DockerHub
We provide prebuilt MongoosePush images. Configuration requires either an FCM token, APNS certificates or an APNS token. Depending on your usecase, you can have some or all of them in a standalone MongoosePush instance or using a docker container. For the full configuration you need to set the following directory structure up:
priv/
ssl/
- rest_cert.pem - The HTTP endpoint certificate
- rest_key.pem - private key for the HTTP endpoint certificate (has to be unencrypted)
apns/
- prod_cert.pem - Production APNS app certificate
- prod_key.pem - Production APNS app certificate's private key (has to be unencrypted)
- dev_cert.pem - Development APNS app certificate
- dev_key.pem - Development APNS app certificate's private key (has to be unencrypted)
- token.p8 -
APNS
authentication token
fcm/
- token.json -
FCM
service account JSON file
- token.json -
If you want to use APNS
token authentication you need to provide token and set key_id
and team_id
environmental variables. To see how to obtain token and key_id
read:
https://developer.apple.com/documentation/usernotifications/setting_up_a_remote_notification_server/establishing_a_token_based_connection_to_apns
To see how to obtain team_id
read: https://www.mobiloud.com/help/knowledge-base/ios-app-transfer/
FCM
JSON file can be generated by Firebase console (https://console.firebase.google.com). Go to your project -> Project Settings
-> Service accounts
-> Generate new private key
Assuming that you have the priv
directory with all ceriticates and fcm token in current directory, then you may start MongoosePush with the following command:
docker run -v `pwd`/priv:/opt/app/priv \
-e PUSH_HTTPS_CERTFILE="/opt/app/priv/ssl/rest_cert.pem" \
-e PUSH_HTTPS_KEYFILE="/opt/app/priv/ssl/rest_key.pem" \
-it --rm mongooseim/mongoose-push:latest
Building
Building docker is really easy, just type:
MIX_ENV=prod mix do deps.get, certs.dev, docker.build, docker.release
As a result of this command you get access to mongoose_push:release
docker image. You may run it by typing:
docker run -it --rm mongoose_push:release foreground
The docker image that you have just built, exposes the port 8443
for the HTTP API of MongoosePush. It contains a VOLUME
for path /opt/app - it is handy for injecting APNS
and HTTP API
certificates since by default the docker image comes with test, self-signed certificates.
Configuring
The docker image of MongoosePush contains common, basic configuration that is generated from config/prod.exs
. All useful options may be overridden via system environmental variables. Below there's a full list of the variables you may set while running docker (via docker -e
switch), but if there's something you feel, you need to change other then that, then you need to prepare your own config/prod.exs
before image build.
Environmental variables to configure production release:
Settings for HTTP endpoint:
PUSH_HTTPS_BIND_ADDR
- Bind IP address of the HTTP endpoint. Default value in prod release is "127.0.0.1", but docker overrides this with "0.0.0.0"PUSH_HTTPS_PORT
- The port of the MongoosePush HTTP endpoint. Please note that docker exposes only8443
port, so changing this setting is not recommendedPUSH_HTTPS_KEYFILE
- Path to a PEM keyfile used for HTTP endpointPUSH_HTTPS_CERTFILE
- Path to a PEM certfile used for HTTP endpointPUSH_HTTPS_ACCEPTORS
- Number of TCP acceptors to start
General settings:
PUSH_LOGLEVEL
-debug
/info
/warn
/error
- Log level of the application.info
is the default onePUSH_FCM_ENABLED
-true
/false
- Enable or disableFCM
support. Enabled by defaultPUSH_APNS_ENABLED
-true
/false
- Enable or disableAPNS
support. Enabled by defaultTLS_SERVER_CERT_VALIDATION
-true
/false
- Enable or distable TLS options for both FCM and APNS.
Settings for FCM service:
PUSH_FCM_ENDPOINT
- Hostname ofFCM
service. Set only for local testing. By default this option points to the Google's official hostnamePUSH_FCM_APP_FILE
- Path toFCM
service account JSON file. For details look at Running from DockerHub sectionPUSH_FCM_POOL_SIZE
- Connection pool size forFCM
service
Settings for development APNS service:
PUSH_APNS_DEV_ENDPOINT
- Hostname ofAPNS
service. Set only for local testing. By default this option points to the Apple's official hostnamePUSH_APNS_DEV_CERT
- Path Apple's development certfile used to communicate withAPNS
PUSH_APNS_DEV_KEY
- Path Apple's development keyfile used to communicate withAPNS
PUSH_APNS_DEV_KEY_ID
- Key ID generated from Apple's developer console. For details look at Running from DockerHub section required for token authenticationPUSH_APNS_DEV_TEAM_ID
- TEAM ID generated from Apple's developer console. For details look at Running from DockerHub section required for token authenticatonPUSH_APNS_DEV_P8_TOKEN
- Token generated from Apple's developer console. For details look at Running from DockerHub sectionPUSH_APNS_DEV_USE_2197
-true
/false
- Enable or disable use of alternative2197
port forAPNS
connections in development mode. Disabled by defaultPUSH_APNS_DEV_POOL_SIZE
- Connection pool size forAPNS
service in development modePUSH_APNS_DEV_DEFAULT_TOPIC
- DefaultAPNS
topic to be set if the client app doesn't specify it with the API call. If this option is not set, MongoosePush will try to extract this value from the provided APNS certificate (the first topic will be assumed default). DEV certificates normally don't provide any topics, so this option can be safely left unset
Settings for production APNS service:
PUSH_APNS_PROD_ENDPOINT
- Hostname ofAPNS
service. Set only for local testing. By default this option points to the Apple's official hostnamePUSH_APNS_PROD_CERT
- Path Apple's production certfile used to communicate withAPNS
PUSH_APNS_PROD_KEY
- Path Apple's production keyfile used to communicate withAPNS
PUSH_APNS_PROD_KEY_ID
- Key ID generated from Apple's developer console. For details look at Running from DockerHub section required for token authenticationPUSH_APNS_PROD_TEAM_ID
- TEAM ID generated from Apple's developer console. For details look at Running from DockerHub section required for token authenticatonPUSH_APNS_PROD_P8_TOKEN
- Token generated from Apple's developer console. For details look at Running from DockerHub sectionPUSH_APNS_PROD_USE_2197
-true
/false
- Enable or disable use of alternative2197
port forAPNS
connections in production mode. Disabled by defaultPUSH_APNS_PROD_POOL_SIZE
- Connection pool size forAPNS
service in production modePUSH_APNS_PROD_DEFAULT_TOPIC
- DefaultAPNS
topic to be set if the client app doesn't specify it with the API call. If this option is not set, MongoosePush will try to extract this value from the provided APNS certificate (the first topic will be assumed default)
Local build
Perquisites
- Elixir 1.5+ (http://elixir-lang.org/install.html)
- Erlang/OTP 19.3+
NOTE: Some Erlang/OTP 20.x releases / builds contain TLS bug that prevents connecting to APNS servers. When building with this Erlang version, please make sure that MongoosePushRuntimeTest test suite passes. It is however highly recommended to build MongoosePush with Erlang/OTP 21.x.
- Rebar3 (just enter
mix local.rebar
)
Build and run of production release
Build step is really easy. Just type in root of the repository:
MIX_ENV=prod mix do deps.get, compile, certs.dev, distillery.release
After this step you may try to run the service via:
_build/prod/rel/mongoose_push/bin/mongoose_push foreground
Yeah, I know... It crashed. Running this service is fast and simple but unfortunately you can't have push notifications without properly configured FCM
and/or APNS
service. You can find out how to properly configure it in Configuration
section of this README.
Build and run of development release
Build step is really easy. Just type in root of the repository:
MIX_ENV=dev mix do deps.get, compile, certs.dev, distillery.release
Development release is by default configured to connect to local APNS / FCM mock. This configuration may be changed as needed
in config/dev.exs
file.
For now, let's just start those mocks so that we can use default dev configuration:
docker-compose -f test/docker/docker-compose.yml up -d
After this step you may try to run the service via:
_build/dev/rel/mongoose_push/bin/mongoose_push console
Running tests
Setup FCM and APNS mocks first:
$ docker-compose -f test/docker/docker-compose.yml up -d
Generate certificates. This step is needed to be run only once:
mix certs.dev
And finally run tests:
$ mix test
You can cleanup docker after tests by calling:
$ docker-compose -f test/docker/docker-compose.yml down
Configuration
The whole configuration is contained in file config/{prod|dev|test}.exs
depending on which MIX_ENV
you will be using. You should use MIX_ENV=prod
for production installations and MIX_ENV=dev
for your development. Anyway, lets take a look on config/dev.exs
, part by part.
RESTful API configuration
config :maru, MongoosePush.Router,
versioning: [
using: :path
],
https: [
ip: {127, 0, 0, 1},
port: 8443,
keyfile: "priv/ssl/fake_key.pem",
certfile: "priv/ssl/fake_cert.pem",
otp_app: :mongoose_push
]
This part of configuration relates only to HTTP
endpoints exposed by MongoosePush
. Here you can set a bind IP adress (option: ip
), port and paths to your HTTP
TLS
certificates. You should ignore other options unless you know what you're doing (to learn more, explore maru's documentation).
You may entirely skip the maru
config entry to disable HTTP
API and just use this project as an Elixir
library.
FCM configuration
Lets take a look at sample FCM
service configuration:
config :mongoose_push, fcm: [
default: [
appfile: "path/to/token.json",
endpoint: "localhost",
pool_size: 5,
mode: :prod,
tls_opts: []
]
]
This is a definition of a pool - each pool has a name and configuration. It is possible to have multiple named pools with different configuration, which includes pool size, environment mode etc. Currently the only reason you may want to do this is to create separate production and development pools which may be selected by an HTTP
client by specifying matching :mode
in their push request.
Each FCM
pool may be configured by setting the following fields:
- appfile (required) - path to
FCM
service account JSON file. Details on how to get one are in Running from DockerHub section - pool_size (required) - maximum number of used
HTTP/2
connections to google's service - mode (either
:prod
or:dev
) - pool's mode. TheHTTP
client may select pool used to push a notification by specifying matching option in the request - endpoint (optional) - URL override for
FCM
service. Useful mainly in tests - port (optional) - Port number override for
FCM
service. Useful mainly in tests - tags (optional) - a list of tags. Used when choosing pool to match request tags when sending a notification. More details: https://github.com/esl/sparrow#tags
- tls_opts (optional) - a list of raw options passed to
ssl:connect
function call while connecting toFCM
. When this option is omitted, it will default to set of values that will verify server certificate based on internal CA chain. Providing this option overrides all defaults, effectively disabling certificate validation. Therefore passing this option is not recommended outside dev and test environments.
You may entirely skip the FCM
config entry to disable FCM
support.
APNS configuration
Lets take a look at sample APNS
service configuration:
config :mongoose_push, apns: [
dev: [
cert: "priv/apns/dev_cert.pem",
key: "priv/apns/dev_key.pem",
mode: :dev,
use_2197: false,
pool_size: 5,
tls_opts: []
],
prod: [
cert: "priv/apns/prod_cert.pem",
key: "priv/apns/prod_key.pem",
mode: :prod,
use_2197: false,
pool_size: 5,
tls_opts: []
]
]
Just like for FCM
, at the top level we can specify the named pools that have different configurations. For APNS
this is especially useful since Apple delivers different APS certificates for development and production use. The HTTP client can select a named pool by providing a matching :mode in the HTTP request.
Each APNS
pool may be configured by setting the following fields:
- cert (required) - relative path to
APNS
PEM
certificate issued by Apple. This certificate have to be somewhere inpriv
directory - key (required) - relative path to
PEM
private key forAPNS
certificate issued by Apple. This file have to be somewhere inpriv
directory - pool_size (required) - maximum number of used
HTTP/2
connections to google's service - mode (either
:prod
or:dev
) - pool's mode. TheHTTP
client may select pool used to push a notification by specifying matching option in the request - endpoint (optional) - URL override for
APNS
service. Useful mainly in tests - port (optional) - Port number override for
APNS
service. Useful mainly in tests - use_2197 (optional
true
orfalse
) - whether use alternative port forAPNS
: 2197 - tags (optional) - a list of tags. Used when choosing pool to match request tags when sending a notification. More details: https://github.com/esl/sparrow#tags
- tls_opts (optional) - a list of raw options passed to
ssl:connect
function call while connecting toAPNS
. When this option is omitted, it will default to set of values that will verify server certificate based on internal CA chain. Providing this option overrides all defaults, effectively disabling certificate validation. Therefore passing this option is not recommended outside dev and test environments.
You may entirely skip the APNS
config entry to disable APNS
support.
Converting APNS files
If you happen to have APNS files in pkcs12
format (.p12 or .pfx extenstion) you need to convert them to PEM
format which is understood by MongoosePush. Belowe you can find sample openssl
commands which may be helpful.
Get cert from pkcs12 file
openssl pkcs12 -in YourAPNS.p12 -out YourCERT.pem -nodes -nokeys
Get key from pkcs12 file
openssl pkcs12 -in YourAPNS.p12 -out YourKEY.pem -nodes -nocerts
RESTful API
Swagger
If for some reason you need Swagger
spec for this RESTful
service, there is a swagger endpoint available via an HTTP
path /swagger.json
Just tell me what to send already
Request
There is only one endpoint at this moment:
POST /v2/notification/{device_id}
As you can imagine, {device_id}
should be replaced with device ID/Token generated by your push notification provider (FCM
or APNS
). The notification should be sent as JSON
payload of this request. Minimal JSON
request could be like this:
{
"service": "apns",
"alert":
{
"body": "notification's text body",
"title": "notification's title"
}
}
The full list of options contains the following:
- service (required,
apns
orfcm
) - push notifications provider to be used for this notification - mode (optional,
prod
(default) ordev
) - allows for selecting named pool configured inMongoosePush
- priority (optional) - Either
normal
orhigh
. Those values are used without changes for FCM. For APNS however,normal
maps to priority5
, whilehigh
maps to priority10
. Please refer to FCM / APNS documentation for more details on those values. By defaultpriority
is not set at all, therefore the push notification service decides which value is used by default. - time_to_live (optional) - Maximum lifespan of an FCM notification. For more details, please, refer to the official FCM documentation.
- mutable_content (optional,
true
/false
(default)) - Only applicable to APNS. Sets "mutable-content=1" in APNS payload. - topic (optional,
APNS
specific) - if APNS certificate configured inMongoosePush
allows for multiple applications, this field selects the application. Please refer toAPNS
documentation for more datails - tags (optional) - a list of tags used to choose a pool with matching tags. To see how tags work read: https://github.com/esl/sparrow#tags
- data (optional) - custom JSON structure sent to the target device. For
APNS
, all keys form this stucture are merged into highest level APS message (the one that holds 'aps' key), while forFCM
the wholedata
json stucture is sent as FCM'sdata payload
along withnotification
. alert (optional) - JSON stucture that if provided will send non-silent notification with the following fields:
- body (required) - text body of notification
- title (required) - short title of notification
- click_action (optional) - for
FCM
itsactivity
to run when notification is clicked. ForAPNS
itscategory
to invoke. Please refer to Android/iOS documentation for more details about this action - tag (optional,
FCM
specific) - notifications aggregation key - badge (optional,
APNS
specific) - unread notifications count - sound (optional) - sound that should be play when notification arrives. Please refer to FCM / APNS documentation for more details.
Please note that either alert and data has to be provided (also can be both). If you only specify alert, the request will result in classic, simple notification. If you only specify data, the request will result in "silent" notification, i.e. the client will receive the data and will be able to decide whether notification shall be shown and how should be shown to the user. If you specify both alert and data, target device will receive both notification and the custom data payload to process.
Description of the possible server responses
- 200
"OK"
- the request was successful. - 400
{"reason" : "invalid_request"|"no_matching_pool"}
- the request was invalid. - 410
{"reason" : "unregistered"}
- the device was not registered. - 413
{"reason" : "payload_too_large"}
- the payload was too large. - 429
{"reason" : "too_many_requests"}
- there were too many requests to the server. - 503
{"reason" : "service_internal"|"internal_config"|"unspecified"}
- the internal service or configuration error occured. - 520
{"reason" : "unspecified"}
- the unknown error occured. - 500
{"reason" : reason}
- the server internal error occured, specified by reason.
Metrics
MongoosePush supports metrics based on elixometer
. In order to enable metrics, you need to add an elixometer
configuration in the config file matching your release type (or simply sys.config
when you need this on already released MongoosePush). The following example config will enable simplest reporter - TTY (already enabled in :dev
environment):
config :exometer_core, report: [reporters: [{:exometer_report_tty, []}]]
config :elixometer, reporter: :exometer_report_tty,
env: Mix.env,
metric_prefix: "mongoose_push"
The example below on the other hand will enable graphite
reporter (replace GRAPHITE_OPTIONS with a list of options for graphite
):
Before making a release:
config :exometer_core, report: [reporters: [{:exometer_report_graphite, GRAPHITE_OPTIONS}]]
config :elixometer, reporter: :exometer_report_graphite,
env: Mix.env,
metric_prefix: "mongoose_push"
or if you modify an existing release (sys.config
):
{exometer_core,[{report, [{reporters, [{exometer_report_graphite, GRAPHITE_OPTIONS}]}]}]},
{elixometer,
[{reporter, exometer_report_graphite},
{env, prod},
{metric_prefix, <<"mongoose_push">>}]},
I use MongoosePush docker, where do I find sys.config
?
If you use dockerized MongoosePush, you need to do the following:
- Start MongoosePush docker, let's assume its name is
mongoose_push
- Run:
docker cp mongoose_push:/opt/app/var/sys.config sys.config
on you docker host (this will get the currentsys.config
to your${CWD}
) - Modify the
sys.config
as you see fit (for metrics, see above) - Stop MongoosePush docker container and restart it with the modified
sys.config
as volume in/opt/app/sys.config
(yes, this is not the path we used to copy this file from, this is an override)
Available metrics
The following metrics are available:
mongoose_push.${METRIC_TYPE}.push.${SERVICE}.${MODE}.error.all
mongoose_push.${METRIC_TYPE}.push.${SERVICE}.${MODE}.error.${REASON}
mongoose_push.${METRIC_TYPE}.push.${SERVICE}.${MODE}.success
Where:
- METRIC_TYPE is either
timers
orspirals
- SERVICE is either
fcm
orapns
- MODE is either
prod
ordev
- REASON is an arbitrary error reason term