由于要用到viop和远程推送通知 并使用go作为后台语言 就找到了开源的 https://github.com/RobotsAndPencils/buford 作为代码使用,但是 发现 这个开源代码在 生产环境下 无法推送消息 一直提示 :
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
}
在linux环境下执行上面的语句。需要改几个参数,
这个http2的ios push推送真的是要了我的老命啊,足足用了两个礼拜的时间,从零基础的go语言开始,一步步的学习和找对应的例子,终于掌握了其中的使用技巧。从此,多了一项生存之道啊。哈哈!!
好的,直接进入主题吧,首先第一步,需要安装一个go语言的环境,这个我之前的博客上写过了。然后需要将你服务器的curl升级到支持http2的版本,好的这些是准备工作。
一、使用最基本的curl命令来发送apns push。
从头开始,如果校验你的服务器能不能发送push呢?通过curl测试是否发送成功是最简单的方式。
curl -i -d '{"aps":{"alert":"Hello http2.0","sound":"msg_high.m4a","badgeNum":1}}' --cert "push.pem":"" -H "apns-topic: 你的top,就是应用的bundle-id" --http2 https://api.development.push.apple.com/3/device/你要发送的设备的device_token
- 一个是-d后面的内容,这个是需要根据你自己的应用,修改对应的结构。正常来说,上面的格式是能正常发送出来的,但是在使用voip的push的情况下,一般voip(voice over ip) 是要ios8.0以上能使用的。使用这类push的时候,ios工程师可能会对push内容进行屏蔽等。这也是可能导致你接口返回值为200,但是手机上没有收到push的问题的原因
- 一个是curl命令的 -i,表示展示返回展示curl返回的header头,因为push的返回结果是在header里面的,如果是下面这个200,表示是成功,的,如果错误可以去官网查看对应的编码,正常curl也会展示出来的。最常见的错误就是
HTTP/2 200
apns-id: 65743B1B-BB3A-3CD6-BD27-D566660642D8
-H "apns-topic: com.a.a.voip
--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没有报错的
req.Header.Set("apns-topic", "com.a.a.voip")
headers.set(req.Header)
- 1
- 2
src/github.com/RobotsAndPencils/buford/example/push
。然后在这个目录下,编译打码,用go build main.go,会生成一个main的可执行文件,然后输入命令 ./main -d 设备的device_token -c push.p12 -p 证书密码 -e development
,这样就能给debug版本的发送push了。src/github.com/RobotsAndPencils/buford/example/concurrent
这个目录下下的main.go的话,是用go的并行的方式来发送push,增加push的效率。核心逻辑就是在主线程里面,用for循环不断的去redis去数据,然后取到数据后,放入chennel里面,然后在代码中,go func后面的代码表示子进程。读取这个chennel来发送push