{"id":39660426,"url":"https://github.com/baobabus/go-apns","last_synced_at":"2026-01-18T09:23:26.529Z","repository":{"id":57554550,"uuid":"110162547","full_name":"baobabus/go-apns","owner":"baobabus","description":"Auto-scaling client for Apple Push Notification HTTP/2 service done \"the Go way\".","archived":false,"fork":false,"pushed_at":"2017-12-24T20:25:49.000Z","size":298,"stargazers_count":10,"open_issues_count":0,"forks_count":0,"subscribers_count":2,"default_branch":"master","last_synced_at":"2024-06-20T00:35:53.587Z","etag":null,"topics":["apns","apns-http2","apns2","go","golang"],"latest_commit_sha":null,"homepage":"","language":"Go","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/baobabus.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2017-11-09T20:25:49.000Z","updated_at":"2023-05-15T04:36:22.000Z","dependencies_parsed_at":"2022-09-26T18:51:22.324Z","dependency_job_id":null,"html_url":"https://github.com/baobabus/go-apns","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/baobabus/go-apns","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/baobabus%2Fgo-apns","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/baobabus%2Fgo-apns/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/baobabus%2Fgo-apns/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/baobabus%2Fgo-apns/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/baobabus","download_url":"https://codeload.github.com/baobabus/go-apns/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/baobabus%2Fgo-apns/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":28534159,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-01-18T00:39:45.795Z","status":"online","status_checked_at":"2026-01-18T02:00:07.578Z","response_time":98,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["apns","apns-http2","apns2","go","golang"],"created_at":"2026-01-18T09:23:26.454Z","updated_at":"2026-01-18T09:23:26.515Z","avatar_url":"https://github.com/baobabus.png","language":"Go","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Go APNS\n\n:warning: **WORK IN PROGRESS** :warning:\n\nGo APNS is a client for Apple Push Notification service over HTTP/2 protocol done \"the Go way\".\n\n[![Build Status](https://travis-ci.org/baobabus/go-apns.svg?branch=master)](https://travis-ci.org/baobabus/go-apns)\n[![GoDoc](https://godoc.org/github.com/baobabus/go-apns/apns2?status.svg)](https://godoc.org/github.com/baobabus/go-apns/apns2)\n\n## Features\n\n- Designed to communicate with Apple Push Notification service over HTTP/2 protocol\n- Processes push requests asynchronously: just write your requests to a channel\n- Notifies about push results asynchronously: just receive on the callback channel\n- Automatically scales up processing pipeline as the load increases\n- Allows full control of the scaling process and connection handling\n- Effects back pressure as needed to ensure full awareness by the up-stream \n- Supports Go 1.7 and later\n\n## Processing Flow\n\nEach APNS Client handles all aspects of communication with APN service, including\nmanagement of HTTP/2 connections and controlling concurrent HTTP/2 streams.\n\n![Processing Flow Diagram](./doc/flow-phase1.svg)\n\n\u003cp align=\"center\" style=\"color: #888\"\u003e\u003ci\u003e(Control channels and service goroutines are not shown)\u003c/i\u003e\u003c/p\u003e\n\n1. `Submitter` picks up push requests from the processing queue\n2. `Submitter` forwards requests to internal dispatch channel\n3. Each `streamer` maintains a single HTTP/2 connection to APN service\n4. One of the `streamers` picks up a push request from the dispatch queue\n5. The `streamer` allocates a stream in its HTTP/2 connection\n6. The `streamer` spins up a single-flight `round-tripper` goroutine\n7. The `round-tripper` synchronously POSTs a request to APN service over\nits streamer's HTTP/2 connection\n8. APN server's response is written to the callback channel\n9. `Governor` collects metrics for dispatch and callback channel blockages,\nevaluates processing throughput and spins up new streamers as needed\n\n## Scaling up\n\nDuring each poll interval governor collects stats on inbound and outbound channel blockages, \non the number of notifications pushed and the size of data sent out. It then evalutes the stats \nagainst its scaling configuration and spins up new streamers if and when is appropriate.\n\nIn the following illustrative scenario:\n\n- PollInterval = 0.2sec\n- MinSustain = 1sec\n- SettlePeriod = 2sec\n\n![Scaling sequence](./doc/scale.svg)\n\u003cp align=\"center\" style=\"color: #888\"\u003e\u003ci\u003e(Processing rate and bandwidth stats are not shown)\u003c/i\u003e\u003c/p\u003e\n\n1. Blockages on outbound channel prevent blockages on inbound channel from being counted\n2. Minimum time of sustained blockage on inbound channel is encountered\n    - new streamers are spun up asynchronously\n    - sustained blockages on inboud channel have no effect while new streamers are starting\n3. All new streamers have completed their initialization\n    - settle period begins\n    - sustained blockages on inboud channel have no effect during settle period\n4. Settle period ends\n    - since there's been minimum time of sustained blockage on inbound channel, more streamers are spun up\n    - sustained blockages on inboud channel have no effect while new streamers are starting\n 5. All new streamers have completed their initialization\n    - settle period begins\n    - sustained blockages on inboud channel have no effect during settle period\n 6. Blockages on inbound channel end - no more scaling up is needed.\n\n## Example\n\nFire-and-forget example sends a notification to three recipients. It uses\nprovider token authentication method and does not check for outcome, just\nmaking sure the push is complete.\n\n```go\npackage main\n\nimport (\n\t\"log\"\n\n\t\"github.com/baobabus/go-apns/apns2\"\n\t\"github.com/baobabus/go-apns/cryptox\"\n)\n\nfunc main() {\n\n\t// Load and parse out token signing key\n\tsigningKey, err := cryptox.PKCS8PrivateKeyFromFile(\"token_signing_pk.p8\")\n\tif err != nil {\n\t\tlog.Fatal(\"Token signing key error: \", err)\n\t}\n\n\t// Set up our client\n\tclient := \u0026apns2.Client{\n\t\tGateway:  apns2.Gateway.Production,\n\t\tSigner:   \u0026apns2.JWTSigner{\n\t\t\tKeyID: \"ABC123DEFG\", // Your key ID\n\t\t\tTeamID: \"DEF123GHIJ\", // Your team ID\n\t\t\tSigningKey: signingKey,\n\t\t},\n\t\tCommsCfg: apns2.CommsFast,\n\t\tProcCfg:  apns2.UnlimitedProcConfig,\n\t}\n\n\t// Start processing\n\terr = client.Start(nil)\n\tif err != nil {\n\t\tlog.Fatal(\"Client start error: \", err)\n\t}\n\n\t// Mock motification and recipients\n\theader := \u0026apns2.Header{ Topic: \"com.example.Alert\" }\n\tpayload := \u0026apns2.Payload{ APS: \u0026apns2.APS{Alert: \"Ping!\"} }\n\trecipients := []string{\n\t\t\"00fc13adff785122b4ad28809a3420982341241421348097878e577c991de8f0\",\n\t\t\"10fc13adff785122b4ad28809a3420982341241421348097878e577c991de8f0\",\n\t\t\"20fc13adff785122b4ad28809a3420982341241421348097878e577c991de8f0\",\n\t}\n\n\t// Push to all recipients\n\tfor _, rcpt := range recipients {\n\t\tnotif := \u0026apns2.Notification{\n\t\t\tRecipient: rcpt,\n\t\t\tHeader:    header,\n\t\t\tPayload:   payload,\n\t\t}\n\t\terr := client.Push(notif, apns2.DefaultSigner, apns2.NoContext, apns2.DefaultCallback)\n\t\tif err != nil {\n\t\t\tlog.Fatal(\"Push error: \", err)\n\t\t}\n\t}\n\n\t// Perform soft shutdown allowing the processing to complete.\n\tclient.Stop()\n}\n```\n\n## Configuration Settings and Customization\n\n### Communication Settings\n\nFollowing communication settings are supported:\n\n##### DialTimeout\n\nDialTimeout is the maximum amount of time a dial will wait for a connect\nto complete.\n\n##### MinDialBackOff\n\nMinDialBackOff is the minimum amount of time by which dial attempts\nshould be delayed after encountering a refused connection.\nActual back-off time will grow exponentially until a connection attempt\nis successful.\n\n##### MaxDialBackOff\n\nMaxDialBackOff is the maximum amount of time by which dial attempts\nshould be delayed after encountering a refused connection.\n\n##### DialBackOffJitter\n\nDialBackOffJitter is used to calculate the ramdom amount to appy to each\nback-off time calculation.\n\n##### RequestTimeout\n\nRequestTimeout specifies a time limit for requests made by the\nHTTPClient. The timeout includes connection time, any redirects,\nand reading the response body.\n\n##### KeepAlive\n\nKeepAlive specifies the keep-alive period for an active network\nconnection. If zero, keep-alives are not enabled.\nApple recommends not closing connections to APN service at all,\nbut a sinsibly long duration is acceptable.\n\n##### MaxConcurrentStreams\n\nMaxConcurrentStreams is the maximum allowed number of concurrent streams\nper HTTP/2 connection. If connection's MAX_CONCURRENT_STREAMS option\nis invoked by the remote side with a lower value, the remote request\nwill be honored if possible. (See AllowHTTP2Incursion processing option.)\n\n\nCommsCfg example:\n\n```go\nCommsCfg{\n\tDialTimeout:          1 * time.Second,\n\tMinDialBackOff:       4 * time.Second,\n\tMaxDialBackOff:       10 * time.Minute,\n\tDialBackOffJitter:    10 * funit.Percent,\n\tRequestTimeout:       2 * time.Second,\n\tKeepAlive:            10 * time.Hour,\n\tMaxConcurrentStreams: 500,\n}\n```\n\n### Processing Settings\n\nFollowing processing settings are supported:\n\n##### MaxRetries\nMaxRetries is the maximum number of times a failed notification push\nshould be reattempted. This only applies to \"retriable\" failures.\n\n##### RetryEval\nRetryEval is the function that is called when a push attempt fails\nand retry eligibility needs to be determined.\n\n##### MinConns\nMinConns is minimum number of concurrent connections to APN servers\nthat should be kept open. When a client is started it immeditely attempts\nto open the specified number of connections.\n\n##### MaxConns\nMaxConns is maximum allowed number of concurrent connections\nto APN service.\n\n##### MaxRate\nMaxRate is the throughput cap specified in notifications per second.\nIt is not strictly enforced as would be the case with a true rate\nlimiter. Instead it only prevents additional scaling from taking place\nonce the specified rate is reached.\n\nFor clarity it is best expressed in idiomatic way:\n\n```go\nMaxRate = 10000 / funit.Second\n```\n\n##### MaxBandwidth\nMaxBandwidth is the throughput cap specified in bits per second.\nIt is not strictly enforced as would be the case with a true rate\nlimiter. Instead it only prevents additional scaling from taking place\nonce the specified rate is reached.\n\nFor clarity it is best expressed in idiomatic way:\n\n```go\nMaxBandwidth = 10 * funit.Kilobit / funit.Second\n```\n\n##### Scale\nScale specifies the manner of scaling up and winding down.\nThree scaling modes come prefefined: Incremental, Exponential and Constant.\n\n```go\nScale = scale.Incremental(2) // Add two new connections each time\n```\n\n##### MinSustain\nMinSustain is the minimum duration of time over which the processing\nhas to experience blocking before a scale-up attemp is made. It is also\nthe minimum amount of time over which non-blocking processing has to\ntake place before a wind-down attemp is made.\n\n##### PollInterval\nPollInterval is the time between performance metrics sampling attempts.\n\n##### SettlePeriod\nSettlePeriod is the amount of time given to the processing for it to\nsettle down at the new rate after successful scaling up or\nwinding down attempt. Sustained performance analysis is ignored during\nthis time and no new scaling attempt is made.\n\n##### AllowHTTP2Incursion\nAllowHTTP2Incursion controls whether it is OK to perform reflection-based\nprobing of HTTP/2 layer. When enabled, scaler may access certain private\nproperties in x/net/http2 package if needed for more precise performance\nanalysis.\n\n##### UsePreciseHTTP2Metrics\nUsePreciseHTTP2Metrics, if set to true, instructs the scaler to query\nHTTP/2 layer parameters on every call that requires the data.\nSet this to false if you wish to eliminate any additional overhead that\nthis may introduce.\n\n##### HTTP2MetricsRefreshPeriod\nHTTP2MetricsRefreshPeriod, if set to a positive value, controls\nthe frequency of \"imprecise\" metrics updates. Under this approach any\nrelevant fields that are private to x/net/http2 packaged are only\nqueried periodically.\nThis reduces the overhead of any required reflection calls, but it also\nintroduces the risk of potentially relying on some stale metrics.\nIn most realistic situations, however, this can be easily tolerated\ngiven frequent enough refresh period.\n\nHTTP2MetricsRefreshPeriod value is ignored and periodic updates\nare turned off if UsePreciseHTTP2Metrics is set to true.\nSetting HTTP2MetricsRefreshPeriod to 0 or negative value disables\nmetrics refresh even if UsePreciseMetrics is false.\n\nProcCfg example:\n\n```go\nProcCfg{\n\tMaxRetries:                0,\n\tRetryEval:                 nil,\n\tMinConns:                  1,\n\tMaxConns:                  100,\n\tMaxRate:                   100000 / funit.Second,\n\tMaxBandwidth:              10 * funit.Kilobit / funit.Second,\n\tScale:                     scale.Incremental(2),\n\tMinSustain:                2 * time.Second,\n\tPollInterval:              200 * time.Millisecond,\n\tSettlePeriod:              5 * time.Second,\n\tAllowHTTP2Incursion:       true,\n\tUsePreciseHTTP2Metrics:    false,\n\tHTTP2MetricsRefreshPeriod: 200 * time.Millisecond,\n}\n```\n\n## License\n\nThe MIT License (MIT)\n\nCopyright (c) 2017 Aleksey Blinov\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fbaobabus%2Fgo-apns","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fbaobabus%2Fgo-apns","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fbaobabus%2Fgo-apns/lists"}