-
-
Save nowa/4363472 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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