golang 实现 iOS http2 推送 Apns通知

由于要用到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,b)
    //id,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://www.jb51.cc/article/p-ejnmigde-bnx.html。然后需要将你服务器的curl升级支持http2的版本,好的这些是准备工作。

一、使用最基本的curl命令来发送apns push。

从头开始,如果校验你的服务器能不能发送push呢?通过curl测试是否发送成功是最简单的方式。
 
 
  • 1
curl -i -d '{"aps":{"alert":"Hello http2.0","sound":"msg_high.m4abadgeNum":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也会展示出来的。最常见的错误就是
  
  
  • 1
  • 2
  • HTTP/2 200 apns-id: 65743B1B-BB3A-3CD6-BD27-D566660642D8
    • 最常见的错误是两个,一个是 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
  • req.Header.Set("apns-topic","com.a.a.voip") headers.set(req.Header)

    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

    版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

    相关推荐


    1、Golang指针 在介绍Golang指针隐式间接引用前,先简单说下Go 语言的指针 (Pointer),一个指针可以指向任何一个值的内存地址 它指向那个值的内存地址,在 32 位机器上占用 4 个字节,在 64 位机器上占用 8 个字节,并且与它所指向的值的大小无关。大致上理解如下: 变量名前的
    1、概述 1.1 Protocol buffers定义 Protocol buffers 是语言中立、平台中立、可扩展的结构化数据序列化机制,就像 XML,但是它更小、更快、更简单。你只需定义一次数据的结构化方式,然后就可以使用特殊生成的源代码轻松地将结构化数据写入和读取各种数据流,支持各
    判断文件是否存在,需要用到"os"包中的两个函数: os.Stat()和os.IsNotExit() func Stat(name string) (FileInfo, error) Stat返回描述文件f的FileInfo类型值。如果出错,错误底层类型是*PathError。 func IsNot
    1、编译环境 OS :Loongnix-Server Linux release 8.3 CPU指令集 : loongarch64 平台 : 龙芯 go版本 : go version go1.15.6 linux/loong64 2、go和docker安装 docker安装: y
    1、概述 Golang是一种强类型语言,虽然在代码中经常看到i:=12这种写法,这其实是编译器在编译期间自动做了类型推断。编译器会对数据进行类型检查,不同类型的数据不能赋值,不能在函数中传参。强类型语言有一些优势,很多的错误会在编译期间被检查出来,不像php和python等弱类型语言,很多错误只有运
    1、概述 在《Golang常用语法糖》这篇博文中我们讲解Golang中常用的12种语法糖,在本文我们主要讲解下接收者方法语法糖。 在介绍Golang接收者方法语法糖前,先简单说下Go 语言的指针 (Pointer),大致上理解如下: 变量名前的 & 符号,是取变量的内存地址,不是取
    1、概述 1.1 什么是gRPC RPC的全称是Remote Procedure Call,远程过程调用。RPC是一种协议,它实际是提供了一套机制,使得应用程序之间可以进行通信,而且也遵从server/client模型。使用的时候客户端调用server端提供的接口就像是调用本地的函数一样。 而gRP
    1、概述 在Golang语言中,函数也是一种数据类型,可以赋值给一个变量,则该变量就是一个函数类型的变量了。通过该变量可以对函数调用。 2、Go语言函数变量详解 定义 func fun() { } var f func() f = fun 说明 我们首先定义了一个 fun 的函数,接着我们声明了一个
    1、概述 Swagger是全球最大的OpenAPI规范(OAS)API开发工具框架,支持从设计和文档到测试和部署的整个API生命周期的开发。Swagger是目前最受欢迎的RESTful Api文档生成工具之一,主要的原因如下: 跨平台、跨语言的支持 强大的社区 生态圈 Swagger Tools(S
    1、 概述 Protocol buffers 是语言中立、平台中立、可扩展的结构化数据序列化机制,就像 XML,但是它更小、更快、更简单。你只需定义一次数据的结构化方式,然后就可以使用特殊生成的源代码轻松地将结构化数据写入和读取各种数据流,支持各种语言。因为profobuf是二进制数据格式,需要编码
    1、名字由来 语法糖(Syntactic sugar)的概念是由英国计算机科学家彼得·兰丁提出的,用于表示编程语言中的某种类型的语法,这些语法不会影响功能,但使用起来却很方便。语法糖,也称糖语法,这些语法不仅不会影响功能,编译后的结果跟不使用语法糖也一样。语法糖,有可能让代码编写变得简单,
    一、for循环 循环:让程序多次执行相同的代码块for循环是Go语言中唯一一个循环结构for循环经典语法先执行表达式1执行表达式2判断是否成立,如果成立执行循环体循环体执行完成后,执行表达式3再次执行表达式2,判断是否成立.for循环用的最多的地方就是遍历数组或切片等 for 表达式1;表达式2;表
    1、概述 在Go语言中,函数可以有命名返回值和普通(匿名)返回值。命名返回值会被视为定义在函数顶部的变量,并且在使用 return 语句返回时,不再必须在其后面指定参数名,也就是支持“裸”返回;而使用普通返回值时,使用 return 语句返回时,需要在其后面指定与普通返回值相同类型的参数名。 实际上
    1、概述 sync.Once 是 Golang package 中使方法只执行一次的对象实现,作用与 init 函数类似。但也有所不同。 init 函数是在文件包首次被加载的时候执行,且只执行一次 sync.Once是在代码运行中需要的时候执行,且只执行一次
    1、概述 gRPC常用于服务端之间的相互调用,如果想把服务暴露给前端,虽然动手修改服务端也能实现,但似乎增加了不少工作量,此时还可以选择gRPC-Gateway方式来快速将gRPC服务以http的方式暴露出来; gRPC-Gateway 是 Google protocol buffers compi
    1、初识 errgroup WaitGroup 主要用于控制任务组下的并发子任务。它的具体做法就是,子任务 goroutine 执行前通过 Add 方法添加任务数目,子任务 goroutine 结束时调用 Done 标记已完成任务数,主任务 goroutine 通过 Wait 方法等待所有的任务完成
    在 Golang 里, _ (下划线)是个特殊的标识符,有以下三种用法。 1、忽略返回值 这个应该是最简单的用途,比如某个函数返回三个参数,但是我们只需要其中的两个,另外一个参数可以忽略,这样的话代码可以这样写: v1, v2, _ := function(...) 2、用在变量
    1、问题 构建fluentbit-operator工程manager模块docker镜像时报如下错误: ....... Step 5/15 : RUN go mod download > Running in c54961171660 go: github.com/fsnotify/fsnot
    1、自定义类型 在Go语言中有一些基本的数据类型,如string、整型、浮点型、布尔等数据类型,Go语言中可以使用type关键字来定义自定义类型。 自定义类型是定义了一个全新的类型。我们可以基于内置的基本类型定义,也可以通过struct或者函数类型来定义。 //将KubeInt定义为int类型 ty
    GO语言heap剖析 本节内容 heap使用 heap提供的方法 heap源码剖析 利用heap实现优先级队列 1. heap使用 在go语言的标准库container中,实现了三种数据类型:heap,list,ring,list在前面一篇文章中已经写了,现在要写的是heap(堆)的源码剖析。 首先