Gunfish
APNs and FCM provider server on HTTP/2.
- Gunfish provides the nterface as the APNs / FCM provider server.
Overview
Quick Started
$ go get github.com/kayac/Gunfish/cmd/gunfish
$ gunfish -c ./config/gunfish.toml -E production
Commandline Options
option | required | description |
---|---|---|
-port | Optional | Port number of Gunfish provider server. Default is 8003 . |
-environment, -E | Optional | Default value is production . |
-conf, -c | Optional | Please specify this option if you want to change toml config file path. (default: /etc/gunfish/config.toml .) |
-log-level | Optional | Set the log level as 'warn', 'info', or 'debug'. |
-log-format | Optional | Supports json or ltsv log formats. |
-enable-pprof | Optional | You can set the flag of pprof debug port open. |
-output-hook-stdout | Optional | Merge stdout of hook command to gunfish's stdout. |
-output-hook-stderr | Optional | Merge stderr of hook command to gunfish's stderr. |
API
POST /push/apns
To delivery remote notifications via APNS to user's devices.
param | description |
---|---|
Array | Array of JSON dictionary includes 'token' and 'payload' properties |
payload param | description |
---|---|
token | Published token from APNS to user's remote device |
payload | APNS notification payload |
Post JSON example:
[
{
"payload": {
"aps": {
"alert": "test notification",
"sound": "default"
},
"option1": "foo",
"option2": "bar"
},
"token": "apns device token",
"header": {
"apns-id": "your apns id",
"apns-topic": "your app bundle id",
"apns-push-type": "alert"
}
}
]
Response example:
{"result": "ok"}
POST /push/fcm
To delivery remote notifications via FCM (legacy) API to user's devices.
Post body format is equal to it for FCM legacy origin server.
example:
{
"registration_ids": [
"token1",
"token2"
],
"data": {
"id": "2",
"message": "Test2"
},
"notification": {
"title": "message_title",
"body": "message_body"
}
}
Response example:
{"result": "ok"}
POST /push/fcm/v1
To delivery remote notifications via FCM v1 API to user's devices.
Post body format is equal to it for FCM v1 origin server.
example:
{
"message": {
"notification": {
"title": "message_title",
"body": "message_body",
"image": "https://example.com/notification.png"
},
"data": {
"sample_key": "sample key",
"message": "sample message"
},
"token": "InstanceIDTokenForDevice"
}
}
Response example:
{"result": "ok"}
FCM v1 endpoint allows multiple payloads in a single request body. You can build request body simply concat multiple JSON payloads. Gunfish sends for each that payloads to FCM server. Limitation: Max count of payloads in a request body is 500.
GET /stats/app
{
"pid": 57843,
"debug_port": 0,
"uptime": 384,
"start_at": 1492476864,
"su_at": 0,
"period": 309,
"retry_after": 10,
"workers": 8,
"queue_size": 0,
"retry_queue_size": 0,
"workers_queue_size": 0,
"cmdq_queue_size": 0,
"retry_count": 0,
"req_count": 0,
"sent_count": 0,
"err_count": 0,
"certificate_not_after": "2027-04-16T00:53:53Z",
"certificate_expire_until": 315359584
}
To get the status of APNS proveder server.
stats type | description |
---|---|
pid | PID |
debug_port | pprof port number |
uptime | uptime |
workers | number of workers |
start_at | The time of started |
queue_size | queue size of requests |
retry_queue_size | queue size for resending notification |
workers_queue_size | summary of worker's queue size |
command_queue_size | error hook command queue size |
retry_count | summary of retry count |
request_count | request count to gunfish |
err_count | count of recieving error response |
sent_count | count of sending notification |
certificate_not_after | certificates minimum expiration date for APNs |
certificate_expire_until | certificates minimum expiration untile (sec) |
GET /stats/profile
To get the status of go application.
See detail properties that url: (https://github.com/fukata/golang-stats-api-handler).
Configuration
The Gunfish configuration file is a TOML file that Gunfish server uses to configure itself.
That configuration file should be located /etc/gunfish.toml
, and is required to start.
Here is an example configuration:
[provider]
port = 8203
worker_num = 8
queue_size = 2000
max_request_size = 1000
max_connections = 2000
error_hook = "echo -e 'Hello Gunfish at error hook!'"
[apns]
key_file = "/path/to/server.key"
cert_file = "/path/to/server.crt"
kid = "kid"
team_id = "team_id"
[fcm]
api_key = "API key for FCM"
[fcm_v1]
google_application_credentials = "/path/to/credentials.json"
param | status | description |
---|---|---|
port | optional | Listen port number. |
worker_num | optional | Number of Gunfish owns http clients. |
queue_size | optional | Limit number of posted JSON from the developer application. |
max_request_size | optional | Limit size of Posted JSON array. |
max_connections | optional | Max connections |
key_file | required | The key file path. |
cert_file | optional | The cert file path. |
kid | optional | kid for APNs provider authentication token. |
team_id | optional | team id for APNs provider authentication token. |
error_hook | optional | Error hook command. This command runs when Gunfish catches an error response. |
api_key | optional | FCM api key. If you want to delivery notifications to android, it is required. |
Error Hook
Error hook command can get an each error response with JSON format by STDIN.
for example JSON structure: (>= v0.2.x)
// APNs
{
"provider": "apns",
"apns-id": "123e4567-e89b-12d3-a456-42665544000",
"status": 400,
"token": "9fe817acbcef8173fb134d8a80123cba243c8376af83db8caf310daab1f23003",
"reason": "MissingTopic"
}
// FCM
{
"provider": "fcm",
"status": 200,
"registration_id": "8kMSTcfqrca:APA91bEfS-uC1WV374Mg83Lkn43..",
// or "to": "8kMSTcfqrca:APA91bEfS-uC1WV374Mg83Lkn43..",
"error": "InvalidRegistration"
}
// FCM v1
{
"provider": "fcmv1",
"status": 400,
"token": "testToken",
"error": {
"status": "INVALID_ARGUMENT",
"message": "The registration token is not a valid FCM registration token"
}
}
Graceful Restart
Gunfish supports graceful restarting based on Start Server
. So, you should start on start_server
command if you want graceful to restart.
### install start_server
$ go get github.com/lestrrat/go-server-starter/cmd/start_server
### Starts Gunfish with start_server
$ start_server --port 38003 --pid-file gunfish.pid -- ./gunfish -c conf/gunfish.toml
Customize
How to Implement Response Handlers
If you have to handle something on error or on success, you should implement error or success handlers. For example handlers you should implement is given below:
type CustomYourErrorHandler struct {
hookCmd string
}
func (ch CustomYourErrorHandler) OnResponse(result Result){
// ...
}
func (ch CustomYourErrorHandler) HookCmd( ) string {
return ch.hookCmd
}
Then you can use these handlers to set before to start gunfish server ( gunfish.StartServer( Config, Environment ) )
.
InitErrorResponseHandler(CustomYourErrorHandler{hookCmd: "echo 'on error!'"})
You can implement a success custom handler in the same way but a hook command is not executed in the success handler in order not to make cpu resource too tight.
Test
Requires dep for vendoring.
$ make test
The following tools are useful to send requests to gunfish for test the following.
- gunfish-cli (send push notification to Gunfish for test)
- apnsmock (APNs mock server)
$ make tools/gunfish-cli
$ make tools/apnsmock
- send a request example with gunfish-cli
$ ./gunfish-cli -type apns -count 1 -json-file some.json -verbose
$ ./gunfish-cli -type apns -count 1 -token <device token> -apns-topic <your topic> -options key1=val1,key2=val2 -verbose
- start apnsmock server
$ ./apnsmock -cert-file ./test/server.crt -key-file ./test/server.key -verbose
Benchmark
Gunfish repository includes Lua script for the benchmark. You can use wrk command with err_and_success.lua
script.
$ make tools/apnsmock
$ ./apnsmock -cert-file ./test/server.crt -key-file ./test/server.key -verbosea &
$ ./gunfish -c test/gunfish_test.toml -E test
$ wrk2 -t2 -c20 -s bench/scripts/err_and_success.lua -L -R100 http://localhost:38103