apnd.go (3949B)
1 package main 2 3 import ( 4 "bytes" 5 "crypto/tls" 6 //"crypto/x509" 7 "encoding/json" 8 "flag" 9 "fmt" 10 "log" 11 "net/http" 12 "net" 13 "os" 14 "os/signal" 15 "syscall" 16 17 "golang.org/x/net/http2" 18 ) 19 20 // Object that comes from the main server 21 type NotifyRequest struct { 22 Devices [][]string `json:"devices"` 23 MsgType string `json:"msg_type"` 24 Payload interface{} `json:"payload"` 25 } 26 27 // Object that matches the format for badge changes 28 type Badge struct { 29 Count int `json:"badge"` 30 } 31 32 // Object that we serialize and send as the POST payload to APN servers 33 type APNRequest struct { 34 Aps interface{} `json:"aps"` 35 MsgType string `json:"msg_type"` 36 Payload interface{} `json:"payload"` 37 } 38 39 func process_client(c net.Conn, h http.Client) { 40 // Read data from connection 41 buf := make([]byte, 4096) 42 nr, err := c.Read(buf); 43 if err != nil { 44 return 45 } 46 c.Close() 47 48 data := buf[0:nr] 49 50 // Parse JSON 51 var notify_request NotifyRequest 52 err = json.Unmarshal(data, ¬ify_request) 53 if err != nil { 54 log.Printf("error parsing json:", err) 55 return 56 } 57 58 total_devices := len(notify_request.Devices) 59 log.Printf("sending message type '%s' to %d device(s)", notify_request.MsgType, total_devices) 60 61 if total_devices == 0 { 62 return 63 } 64 65 var badge Badge 66 badge.Count = 17 67 68 var apn_request APNRequest 69 apn_request.Aps = badge 70 apn_request.Payload = notify_request.Payload 71 apn_request.MsgType = notify_request.MsgType 72 73 // Create the POST request body, only needs to be done once because we 74 // send the same message to all devices 75 request_body, err := json.Marshal(apn_request) 76 if err != nil { 77 log.Printf("error re-marshaling payload:", err) 78 return 79 } 80 81 // APN documentation says this is where we request stuff from 82 base_url := "https://api.development.push.apple.com/3/device/" 83 84 // Loop over all devices, check if they are ios and send a message 85 for i, d := range notify_request.Devices { 86 87 // Order defined by SQL statement in main server 88 os, hex_token := d[0], d[1] 89 log_header := fmt.Sprintf("%3d %s", i + 1, hex_token) 90 91 // Filter out any non iOS devices 92 if os != "ios" { 93 log.Printf("%s: not an ios device", log_header) 94 continue 95 } 96 97 // Construct entire post URL by adding hexadecimal device token 98 // to base URL 99 post_url := base_url + hex_token 100 101 // Make new POST request 102 req, err := http.NewRequest("POST", post_url, bytes.NewBuffer(request_body)) 103 if err != nil { 104 log.Printf("%s: new request error: %s", log_header, err) 105 continue 106 } 107 108 // This delivers messages to our iOS application only 109 req.Header.Set("apns-topic", "com.octopus.shlist") 110 111 // Make request over existing transport 112 resp, err := h.Do(req) 113 if err != nil { 114 log.Printf("%s: %s", log_header, err) 115 continue 116 } 117 log.Printf("%s: %s", log_header, resp.Status) 118 } 119 } 120 121 func main() { 122 var sock_path = flag.String("p", "../apnd.socket", "path to control socket") 123 flag.Parse() 124 125 // These keys are provided by Apple through their Developer program 126 cert, err := tls.LoadX509KeyPair("certs/aps.pem", "certs/aps.key") 127 if err != nil { 128 log.Fatalf("loadkeys: %s", err) 129 } 130 config := tls.Config{Certificates: []tls.Certificate{cert}, InsecureSkipVerify: true} 131 132 // Create new http client with http2 TLS transport underneath 133 client := http.Client { 134 Transport: &http2.Transport{TLSClientConfig: &config}, 135 } 136 137 // Create socket that listens for connections from the main server 138 l, err := net.Listen("unix", *sock_path) 139 if err != nil { 140 log.Fatal("listen error:", err) 141 } 142 143 // Close (and unlink) listener socket on shutdown signals 144 sigc := make(chan os.Signal, 1) 145 signal.Notify(sigc, os.Interrupt, os.Kill, syscall.SIGTERM) 146 go func(c chan os.Signal) { 147 // Wait for signal 148 sig := <-c 149 log.Printf("caught signal %s: shutting down", sig) 150 151 l.Close() 152 os.Exit(0) 153 }(sigc) 154 155 // Main loop, service new main server connections 156 for { 157 fd, err := l.Accept() 158 if err != nil { 159 log.Fatal("accept error:", err) 160 continue 161 } 162 163 go process_client(fd, client) 164 } 165 }