golang 实现 iOS http2 推送 Apns通知
由于要用到viop和远程推送通知 并使用go作为后台语言 就找到了开源的https://github.com/RobotsAndPencils/buford 作为代码使用,但是 发现 这个开源代码在 生产环境下 无法推送消息 一直提示 : the Topic header of the request was not specified and was required 这个错误这个
the Topic header of the request was not specified and was required 这个错误
这个错误 在开发环境和viop的生产环境里面没问题,但是就在 release的生产环境里面报这个错误!
最终发现 问题 是由于代码中的 server类的Push 在使用时 要传入header头,但是在demo中确直接传nil 发现问题后示例代码改成 :
主要是 从证书中解析出来Topic既BundleID然后再传递给Push函数即可。
id, err := service.Push(deviceToken, &headers, b)//主要需要传入 头参数
package main
import (
"encoding/json"
"fmt"
"os"
//"net/http"
"github.com/RobotsAndPencils/buford/certificate"
"github.com/RobotsAndPencils/buford/payload"
"github.com/RobotsAndPencils/buford/payload/badge"
"github.com/RobotsAndPencils/buford/push"
)
// set these variables appropriately
const (
filename = "ck.p12"
password = "123456"
//host = push.Development
host = push.Production
deviceToken = "5cbf2c60e4907c2c3c168575d05c80a3f4cde2bbb2ff05bbbed8e3a4f25c615f"
)
func main() {
// load a certificate and use it to connect to the APN service:
cert, err := certificate.Load(filename, password)
exitOnError(err)
topic := ""
if err == nil{
fmt.Printf("err is nil ****\n");
fmt.Printf("@@@@@@@@@@@@@@@@@@@????????????????????\n");
topic = certificate.TopicFromCert(cert);
fmt.Printf("topic: %s\n", topic);
}
client, err := push.NewClient(cert)
exitOnError(err)
service := push.NewService(client, host)
// construct a payload to send to the device:
p := payload.APS{
Alert: payload.Alert{Body: "Hello HTTP/2"},
Badge: badge.New(42),
}
b, err := json.Marshal(p)
fmt.Println("json.Marshal")
exitOnError(err)
// push the notification:
/*
headers := Headers{
ID: "uuid",
CollapseID: "game1.score.identifier",
Expiration: time.Unix(12622780800, 0),
LowPriority: true,
Topic: "bundle-id",
}
*/
/*
headers := push.Headers{};
fmt.Printf("Topic is %s\n", headers.Topic)
//push.ShowInfo();
reqHeader := http.Header{}
headers.showInfo()
headers.set(reqHeader);
*/
var headers push.Headers
fmt.Printf("topic: %s\n", topic);
if topic != "" {
headers = push.Headers{
Topic: topic,
}
fmt.Printf("the Topic is *** %s\n", headers.Topic)
}
id, err := service.Push(deviceToken, &headers, b)
//id, err := service.Push(deviceToken, nil, b)
fmt.Println("service.Push")
exitOnError(err)
fmt.Println("apns-id:", id)
}
func exitOnError(err error) {
if err != nil {
fmt.Println(err)
os.Exit(1)
}
}
//service.go 代码不用修改即可
// Package push sends notifications over HTTP/2 to
// Apple's Push Notification Service.
package push
import (
"bytes"
"crypto/tls"
"encoding/json"
"fmt"
"io"
"net/http"
"net/url"
"strings"
"time"
"golang.org/x/net/http2"
)
// Apple host locations for configuring Service.
const (
Development = "https://api.development.push.apple.com"
Development2197 = "https://api.development.push.apple.com:2197"
Production = "https://api.push.apple.com"
Production2197 = "https://api.push.apple.com:2197"
)
const maxPayload = 4096 // 4KB at most
// Service is the Apple Push Notification Service that you send notifications to.
type Service struct {
Host string
Client *http.Client
}
// NewService creates a new service to connect to APN.
func NewService(client *http.Client, host string) *Service {
return &Service{
Client: client,
Host: host,
}
}
// NewClient sets up an HTTP/2 client for a certificate.
func NewClient(cert tls.Certificate) (*http.Client, error) {
config := &tls.Config{
Certificates: []tls.Certificate{cert},
}
config.BuildNameToCertificate()
transport := &http.Transport{TLSClientConfig: config}
if err := http2.ConfigureTransport(transport); err != nil {
return nil, err
}
return &http.Client{Transport: transport}, nil
}
// Push sends a notification and waits for a response.
func (s *Service) Push(deviceToken string, headers *Headers, payload []byte) (string, error) {
// check payload length before even hitting Apple.
if len(payload) > maxPayload {
return "", &Error{
Reason: ErrPayloadTooLarge,
Status: http.StatusRequestEntityTooLarge,
}
}
urlStr := fmt.Sprintf("%v/3/device/%v", s.Host, deviceToken)
fmt.Printf("the urlStr: %s\n", urlStr)
fmt.Printf("the payload: %s\n", payload)
fmt.Printf("the headers: %s\n", headers)
req, err := http.NewRequest("POST", urlStr, bytes.NewReader(payload))
if err != nil {
return "", err
}
req.Header.Set("Content-Type", "application/json")
//req.Header.Set("apns-topic", "com.zzcs.mengliao")
headers.set(req.Header)
resp, err := s.Client.Do(req)
if err != nil {
if e, ok := err.(*url.Error); ok {
if e, ok := e.Err.(http2.GoAwayError); ok {
// parse DebugData as JSON. no status code known (0)
return "", parseErrorResponse(strings.NewReader(e.DebugData), 0)
}
}
return "", err
}
defer resp.Body.Close()
if resp.StatusCode == http.StatusOK {
return resp.Header.Get("apns-id"), nil
}
return "", parseErrorResponse(resp.Body, resp.StatusCode)
}
func parseErrorResponse(body io.Reader, statusCode int) error {
var response struct {
// Reason for failure
Reason string `json:"reason"`
// Timestamp for 410 StatusGone (ErrUnregistered)
Timestamp int64 `json:"timestamp"`
}
err := json.NewDecoder(body).Decode(&response)
if err != nil {
return err
}
es := &Error{
Reason: mapErrorReason(response.Reason),
Status: statusCode,
}
if response.Timestamp != 0 {
// the response.Timestamp is Milliseconds, but time.Unix() requires seconds
es.Timestamp = time.Unix(response.Timestamp/1000, 0).UTC()
}
return es
}
另外参考了下面的文章:但是没有直接指出我所遇到的问题:
http://blog.csdn.net/rodgexue/article/details/54290676
在linux环境下执行上面的语句。需要改几个参数,
这个http2的ios push推送真的是要了我的老命啊,足足用了两个礼拜的时间,从零基础的go语言开始,一步步的学习和找对应的例子,终于掌握了其中的使用技巧。从此,多了一项生存之道啊。哈哈!!
好的,直接进入主题吧,首先第一步,需要安装一个go语言的环境,这个我之前的博客上写过了。来个跳转地址:http://blog.csdn.net/Rodgexue/article/details/53789635。然后需要将你服务器的curl升级到支持http2的版本,好的这些是准备工作。
一、使用最基本的curl命令来发送apns push。
从头开始,如果校验你的服务器能不能发送push呢?通过curl测试是否发送成功是最简单的方式。
- 1
- 一个是-d后面的内容,这个是需要根据你自己的应用,修改对应的结构。正常来说,上面的格式是能正常发送出来的,但是在使用voip的push的情况下,一般voip(voice over ip) 是要ios8.0以上能使用的。使用这类push的时候,ios工程师可能会对push内容进行屏蔽等。这也是可能导致你接口返回值为200,但是手机上没有收到push的问题的原因
- 一个是curl命令的 -i,表示展示返回展示curl返回的header头,因为push的返回结果是在header里面的,如果是下面这个200,表示是成功,的,如果错误可以去官网查看对应的编码,正常curl也会展示出来的。最常见的错误就是
- 1
- 2
-
最常见的错误是两个,一个是 bad device token, 一个是Topic disallowed。这两个问题的时候,先是Topic Disallowed这个问题,这个是你的topic不被允许,你再和ios开发确认下应用的bundle id,如果他确定的话,然后你可以在你的bundle id的后面加上 .voip ,然后再去试一下,如bundle id为com.a.a, 然后试着将上面的curl的语句修改下,
-H "apns-topic: com.a.a.voip
然后再去试下 -
还有个最长见的错误是 bad device token,这个的话,你要确保你的证书是通用版证书,这个可以百度查下或者问ios开发。生成后,要放在你执行curl命令的地址下面,因为我
--cert "push.pem":""
这条命令后面是相对路径,然后名字也需要修改成你要的名字,然后要保证你的device_token是debug包还是生产包生成。如果是debug包生成的device token的话,就发送到https://api.development.push.apple.com/3/device/你要发送的设备的device_token
这个苹果的沙盒地址,如果你的device token是生产包的话,需要发送到https://api.push.apple.com/3/device/你要发送的设备的device_token
这个苹果的生产环境地址。
二、使用go语言的apns的开源程序来发送push
我的go语言的push找的是GitHub上开源的,下载地址如下:https://github.com/RobotsAndPencils/buford
1.需要修改的地方是src/github.com/RobotsAndPencils/buford/push/service.go
这个路径下的service.go文件需要修改。
在设置header的地方增加一行,不然会因为topic没有报错的
- 1
- 2
2.然后可以将以.p12结尾的通用版证书放在这个目录下面src/github.com/RobotsAndPencils/buford/example/push
。然后在这个目录下,编译打码,用go build main.go,会生成一个main的可执行文件,然后输入命令 ./main -d 设备的device_token -c push.p12 -p 证书密码 -e development
,这样就能给debug版本的发送push了。
3.src/github.com/RobotsAndPencils/buford/example/concurrent
这个目录下下的main.go的话,是用go的并行的方式来发送push,增加push的效率。核心逻辑就是在主线程里面,用for循环不断的去redis去数据,然后取到数据后,放入chennel里面,然后在代码中,go func后面的代码表示子进程。读取这个chennel来发送push
更多推荐
所有评论(0)