Skip to content

Instantly share code, notes, and snippets.

@nowa
Forked from hugozhu/push.go
Created December 23, 2012 13:41
Show Gist options
  • Save nowa/4363472 to your computer and use it in GitHub Desktop.
Save nowa/4363472 to your computer and use it in GitHub Desktop.
package main
import (
"bytes"
"crypto/tls"
"encoding/binary"
"encoding/hex"
"encoding/json"
"io"
"log"
"net"
"net/http"
"os"
"time"
)
const (
GATEWAY_URL = "gateway.push.apple.com:2195"
CERTIFICATE = "production-cert.pem"
PRIVATE_KEY = "production-key-noenc.pem"
)
type Message struct {
UUID string
Body string
RetryCount int
}
type APNS struct {
tlsconn *tls.Conn
}
func (this *APNS) Connect() {
// connect to the APNS and wrap socket to tls client
if this.tlsconn != nil {
this.tlsconn = nil
}
conn, err := net.Dial("tcp", GATEWAY_URL)
if err != nil {
log.Panic("tcp error: %s", err)
}
tlsconn := tls.Client(conn, conf)
// Force handshake to verify successful authorization.
// Handshake is handled otherwise automatically on first
// Read/Write attempt
err = tlsconn.Handshake()
if err != nil {
log.Panic("tls error: %s", err)
}
// informational debugging stuff
state := tlsconn.ConnectionState()
if state.HandshakeComplete {
this.tlsconn = tlsconn
} else {
this.tlsconn = nil
}
}
func (this *APNS) Disconnect() {
if this.tlsconn != nil {
this.tlsconn.Close()
this.tlsconn = nil
}
}
func (this *APNS) Send(msg Message) bool {
defer func() {
if x := recover(); x != nil {
this.Disconnect()
log.Printf("failed to push %s %s %v", msg.UUID, msg.Body, x)
if msg.RetryCount == 0 {
msg.RetryCount++
this.Send(msg)
}
}
}()
if this.tlsconn == nil {
this.Connect()
}
if this.tlsconn != nil {
if len(msg.Body) > 127 {
msg.Body = msg.Body[0:128]
}
// prepare binary payload from JSON structure
payload := make(map[string]interface{})
payload["aps"] = map[string]string{"alert": msg.Body, "sound": "default", "badge": "1"}
bpayload, err := json.Marshal(payload)
// decode hexadecimal push device token to binary byte array
btoken, _ := hex.DecodeString(msg.UUID)
// build the actual pdu
buffer := bytes.NewBuffer([]byte{})
binary.Write(buffer, binary.BigEndian, uint8(0))
binary.Write(buffer, binary.BigEndian, uint8(0))
binary.Write(buffer, binary.BigEndian, uint8(32))
binary.Write(buffer, binary.BigEndian, btoken)
binary.Write(buffer, binary.BigEndian, uint16(len(bpayload)))
binary.Write(buffer, binary.BigEndian, bpayload)
pdu := buffer.Bytes()
// write pdu
this.tlsconn.SetWriteDeadline(time.Now().Add(10 * time.Second))
n, err := this.tlsconn.Write(pdu)
if err != nil {
log.Panic("write error: %v", err)
} else {
log.Printf("write success %d %v", n, msg)
return true
}
this.tlsconn.SetReadDeadline(time.Now().Add(1 * time.Second))
readb := [6]byte{}
n, err2 := this.tlsconn.Read(readb[:])
if err2 != nil {
log.Panic("read error: %s", err2)
}
if n > 0 {
log.Println("received: %s", hex.EncodeToString(readb[:n]))
}
}
return false
}
var conf *tls.Config
var msg_chan chan Message
var stop_chan chan bool
var complete_chan chan bool
func Push(msg Message) {
msg_chan <- msg
}
func start_sender(n int) {
msg_chan = make(chan Message, n)
stop_chan = make(chan bool, n)
for i := 0; i < n; i++ {
go func() {
apns := new(APNS)
L:
for {
msg := <-msg_chan
select {
case <-stop_chan:
break L
default:
apns.Send(msg)
}
}
}()
}
}
func handler(w http.ResponseWriter, r *http.Request) {
uuid := r.FormValue("uuid")
body := r.FormValue("body")
err := ""
if len(uuid) != 64 {
err = "invalid uuid"
}
if len(body) > 127 {
err = "message body is too long"
}
if err == "" {
msg := &Message{UUID: uuid, Body: body}
Push(*msg)
io.WriteString(w, "ok")
} else {
io.WriteString(w, err)
}
}
func init() {
cert, err := tls.LoadX509KeyPair(os.Getenv("PWD")+"/"+CERTIFICATE, os.Getenv("PWD")+"/"+PRIVATE_KEY)
if err != nil {
log.Fatal("error: %s", err)
}
conf = &tls.Config{
Certificates: []tls.Certificate{cert},
}
}
func main() {
start_sender(3)
http.HandleFunc("/", handler)
s := &http.Server{
Addr: ":8001",
Handler: nil,
ReadTimeout: 1000 * time.Millisecond,
WriteTimeout: 10000 * time.Microsecond,
MaxHeaderBytes: 1 << 20,
}
log.Fatal(s.ListenAndServe())
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment