iOS Push notification client written in Golang 2
We create APNs client in Part 1 and it can only do sending fixed parameter. This article describes how to create server that accept arbitrary parameters and send formatted request to APNs. Full code is here.
Environment
- Golang 1.9
Create Model
Notification
model is the data of Remote notification. Tokens
is token of the device we want to send. We can send same notification to multiple devices designated in Tokens.
type Notification struct {
Tokens []string `json:"token"`
Message string `json:"message"`
Title string `json:"title,omitempty"`
Subtitle string `json:"subtitle,omitempty"`
Badge int `json:"badge,omitempty"`
Sound string `json:"sound,omitempty"`
Category string `json:"category,omitempty"`
ContentAvailable int `json:"content_available,omitempty"`
Expiry int `json:"expiry,omitempty"`
Priority int `json:"priority,omitempty"`
}
type Notifications struct {
Notification []Notification `json:"notifications"`
}
Server
RunServer()
start HTTP server that listens port 8080 and accept send notification request from us. ResponseHandler()
is handling HTTP request and pass Notifications
to enqueueNotification()
if request body is valid. This method called as gorutine, so it executes asynchronously and enqueue each notification we want to send to NotificationQueue
. NotificationQueue
is the queue of APNs request and the receiver can receive value in the form of FIFO. The workers created by StartNotificationWorkers()
are waiting for the value from NotificationQueue. The worker sends APNs request in sendRequest(notification)
when receive Notifications model from a channel.
func RunServer(server *http.Server) {
var l net.Listener
listeners, err := listener.ListenAll()
if err != nil && err != listener.ErrNoListeningTarget {
fmt.Println(err)
return
}
if len(listeners) > 0 {
l = listeners[0]
}
if l == nil {
l, err = net.Listen("tcp", ":8080")
if err != nil {
fmt.Println(err)
return
}
}
server.Serve(l)
}
func ResponseHandler(w http.ResponseWriter, r *http.Request) {
fmt.Printf("%s%s", r.Host, r.RequestURI)
var err error
if r.Method != "POST" {
sendResponse(w,"HTTP Method must be POST",400)
return
}
body, err := ioutil.ReadAll(r.Body)
if err != nil {
sendResponse(w,"Failed to read body",400)
}
var notifications Notifications
if err = json.Unmarshal(body, ¬ifications); err != nil {
sendResponse(w,"Invalid json", 400)
} else {
go enqueueNotification(notifications)
}
sendResponse(w, "ok", 200)
}
func enqueueNotification(notifications Notifications) {
for _, notification := range notifications.Notification {
for _, token := range notification.Tokens {
notification2 := notification
notification2.Tokens = []string{token}
NotificationQueue <- notification2
}
}
}
func StartNotificationWorkers() {
NotificationQueue = make(chan Notification)
for i := 0; i <= runtime.NumCPU(); i++ {
go pushNotificationWorker()
}
}
func pushNotificationWorker() {
for {
notification := <- NotificationQueue
sendRequest(notification)
}
}
Send request
sendRequest()
send APNs request of each notification. It needs to set jwt to a header and create payload conform to the format. header and payload is a struct of represent APNs header and payload.
func sendRequest(notification Notification) {
for _, token := range notification.Tokens {
jwtToken, err := CreateJWT()
if err != nil {
fmt.Println(err)
break
}
header := NewHeader(jwtToken)
payload := NewPayload(notification)
req, err := Client.APNsRequest(token, header, payload)
if err != nil {
fmt.Println(err)
break
}
res, err := Client.HTTPClient.Do(req)
if err != nil {
fmt.Println(err)
break
}
fmt.Println(res.Status)
body, err := ioutil.ReadAll(res.Body)
if err != nil {
fmt.Println(err)
var r Response
if err := json.Unmarshal(body, &r); err != nil {
fmt.Println(err)
return
}
fmt.Println(r)
break
}
}
}
Conclusion
I show a minimum implementation of golang APNs push server, but it not suitable to run production environment since there is no error handling and retry failed request.
This article heavily inspired by gaurun