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, &notifications); 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

  • このエントリーをはてなブックマークに追加