commit 631cf31e15452e4910cdaea490693bf4a168f40c
parent b0f4154357620a22ea77d29a450659b80b9220e3
Author: Kyle Milz <kyle@0x30.net>
Date: Tue, 2 Feb 2016 23:10:08 -0700
apnd: get a go client working
- ironically it was quicker to learn a new language and implement a http2 client
than it was to just do it in perl
- this implementation has a unix socket server that accepts local connections
and then sends out requests over http2
- this should please the apple overlords
Diffstat:
D | apnd/apnd | | | 141 | ------------------------------------------------------------------------------- |
A | apnd/apnd.go | | | 97 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
2 files changed, 97 insertions(+), 141 deletions(-)
diff --git a/apnd/apnd b/apnd/apnd
@@ -1,141 +0,0 @@
-#!/usr/bin/perl
-use strict;
-use warnings;
-
-use AnyEvent;
-use AnyEvent::Socket;
-use AnyEvent::Handle;
-use Net::SSLeay;
-use AnyEvent::TLS;
-
-use Protocol::HTTP2;
-use Protocol::HTTP2::Client;
-use Protocol::HTTP2::Constants qw(const_name);
-
-Net::SSLeay::initialize();
-
-my $client = Protocol::HTTP2::Client->new(
- on_change_state => sub {
- my ( $stream_id, $previous_state, $current_state ) = @_;
- printf "Stream %i changed state from %s to %s\n",
- $stream_id, const_name( "states", $previous_state ),
- const_name( "states", $current_state );
- },
- on_push => sub {
- my ($push_headers) = @_;
-
- # If we accept PUSH_PROMISE
- # return callback to receive promised data
- # return undef otherwise
- print "Server want to push some resource to us\n";
-
- return sub {
- my ( $headers, $data ) = @_;
- print "Received promised resource\n";
- }
- },
- on_error => sub {
- my $error = shift;
- printf "Error occured: %s\n", const_name( "errors", $error );
- }
-);
-
-my $host = 'api.development.push.apple.com';
-my $port = 443;
-
-# Prepare http/2 request
-$client->request(
- ':scheme' => "https",
- ':authority' => $host . ":" . $port,
- ':path' => "/3/device/devid_goes_here",
- ':method' => "POST",
- # headers => [
- # 'accept' => '*/*',
- # 'user-agent' => 'perl-Protocol-HTTP2/0.01',
- # ],
- on_done => sub {
- my ( $headers, $data ) = @_;
- printf "Get headers. Count: %i\n", scalar(@$headers) / 2;
- printf "Get data. Length: %i\n", length($data);
- print $data;
- },
-
- data => "hello=world&test=done",
-);
-
-my $w = AnyEvent->condvar;
-
-tcp_connect $host, $port, sub {
- my ($fh) = @_ or do {
- print "connection failed: $!\n";
- $w->send;
- return;
- };
-
- my $tls;
- eval {
- $tls = AnyEvent::TLS->new(
- method => "TLSv1_2",
- cert_file => 'ssl/aps.pem',
- key_file => 'ssl/aps.key'
- );
-
- # ALPN (Net-SSLeay > 1.55, openssl >= 1.0.2)
- if ( exists &Net::SSLeay::CTX_set_alpn_protos ) {
- Net::SSLeay::CTX_set_alpn_protos( $tls->ctx,
- [Protocol::HTTP2::ident_tls] );
- }
-
- # NPN (Net-SSLeay > 1.45, openssl >= 1.0.1)
- elsif ( exists &Net::SSLeay::CTX_set_next_proto_select_cb ) {
- Net::SSLeay::CTX_set_next_proto_select_cb( $tls->ctx,
- [Protocol::HTTP2::ident_tls] );
- }
- else {
- die "ALPN and NPN is not supported\n";
- }
- };
- if ($@) {
- print "Some problem with SSL CTX: $@\n";
- $w->send;
- return;
- }
-
- my $handle;
- $handle = AnyEvent::Handle->new(
- fh => $fh,
- tls => "connect",
- tls_ctx => $tls,
- autocork => 1,
- on_error => sub {
- $_[0]->destroy;
- print "connection error\n";
- $w->send;
- },
- on_eof => sub {
- $handle->destroy;
- $w->send;
- }
- );
-
- # First write preface to peer
- while ( my $frame = $client->next_frame ) {
- $handle->push_write($frame);
- }
-
- $handle->on_read(
- sub {
- my $handle = shift;
-
- $client->feed( $handle->{rbuf} );
-
- $handle->{rbuf} = undef;
- while ( my $frame = $client->next_frame ) {
- $handle->push_write($frame);
- }
- $handle->push_shutdown if $client->shutdown;
- }
- );
-};
-
-$w->recv;
diff --git a/apnd/apnd.go b/apnd/apnd.go
@@ -0,0 +1,97 @@
+package main
+
+import (
+ "bytes"
+ "crypto/tls"
+ "crypto/x509"
+ "encoding/json"
+ "fmt"
+ //"io"
+ "log"
+ "net/http"
+ "net"
+
+ "golang.org/x/net/http2"
+)
+
+func echo_server(c net.Conn, h http.Client) {
+ buf := make([]byte, 4096)
+ nr, err := c.Read(buf);
+ if err != nil {
+ return
+ }
+
+ var f interface{}
+
+ data := buf[0:nr]
+ fmt.Printf("Received: %v", string(data))
+ err = json.Unmarshal(data, &f)
+
+ m := f.(map[string]interface{})
+
+ for k, v := range m {
+ switch vv := v.(type) {
+ case string:
+ fmt.Println(k, "is string", vv)
+ case int:
+ fmt.Println(k, "is int", vv)
+ case []interface{}:
+ fmt.Println(k, "is an array", vv)
+ for i, u := range vv {
+ fmt.Println(i, u)
+ }
+ default:
+ fmt.Println(k, "is of a type I don't know how to handle")
+ }
+ }
+
+ var jsonStr = []byte(`{"aps":{"badge":33},"other_key":"other_value"}`)
+
+ req, err := http.NewRequest("POST", "https://api.development.push.apple.com/3/device/DE2D368BB6C80E1D8BCB86D20CB6C2161BD5CEC5BA35A1E1AA0DB382849ED9B2", bytes.NewBuffer(jsonStr))
+ req.Header.Set("apns-topic", "com.octopus.shlist")
+ resp, err := h.Do(req)
+ fmt.Println("response was:", resp)
+
+ c.Close()
+}
+
+func main() {
+ cert, err := tls.LoadX509KeyPair("ssl/aps.pem", "ssl/aps.key")
+ if err != nil {
+ log.Fatalf("server: loadkeys: %s", err)
+ }
+
+ config := tls.Config{Certificates: []tls.Certificate{cert}, InsecureSkipVerify: true}
+ conn, err := tls.Dial("tcp", "api.development.push.apple.com:443", &config)
+ if err != nil {
+ log.Fatalf("client: dial: %s", err)
+ }
+ defer conn.Close()
+ log.Println("client: connected to: ", conn.RemoteAddr())
+
+ state := conn.ConnectionState()
+ for _, v := range state.PeerCertificates {
+ fmt.Println(x509.MarshalPKIXPublicKey(v.PublicKey))
+ fmt.Println(v.Subject)
+ }
+ log.Println("client: handshake: ", state.HandshakeComplete)
+ log.Println("client: mutual: ", state.NegotiatedProtocolIsMutual)
+
+ client := http.Client {
+ Transport: &http2.Transport{TLSClientConfig: &config},
+ }
+
+ l, err := net.Listen("unix", "../apnd.socket")
+ if err != nil {
+ log.Fatal("listen error:", err)
+ }
+
+ for {
+ fd, err := l.Accept()
+ if err != nil {
+ log.Fatal("accept error:", err)
+ }
+
+ go echo_server(fd, client)
+ }
+}