Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add streaming endpoints for DEEP, Last and TOPS. #32

Open
wants to merge 26 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
3206188
Create SocketioEncoder and SubscribeMessage.
husafan Sep 23, 2019
97b16b6
Start work on the SocketioClient.
husafan Sep 26, 2019
076cc75
Add a type for dealing with IEX URLs.
husafan Oct 1, 2019
152907b
Added a decoder and implemented processing of HTTP responses.
husafan Oct 3, 2019
be3310a
Add encoding and standardize naming.
husafan Oct 4, 2019
aac1600
Added the transport layer.
husafan Oct 5, 2019
2322542
Fix comment and remove obsolete encoders.
husafan Oct 5, 2019
7efb218
Modify test flags to allow "go test".
husafan Oct 6, 2019
2957c20
Update decoder to parse namespaces.
husafan Oct 6, 2019
f0b9f0d
Reverse PacketType and MessageType in encoding and decoding.
husafan Oct 6, 2019
7b947ca
Fix decoding to parse PacketType before MessageType.
husafan Oct 6, 2019
b8a01f8
Fixed decoder to be able to deal with arrays of different JSON objects.
husafan Oct 7, 2019
6c9162b
Update transport to be thread safe.
husafan Oct 10, 2019
43ee483
Update transport tests.
husafan Oct 13, 2019
3b734a0
Expose pieces of the decoder for individual use.
husafan Oct 20, 2019
67c7188
Add generic namespace for handling different types of subscriptions.
husafan Oct 25, 2019
4110992
Make the namespace exported since they are returned by the client.
husafan Oct 26, 2019
fa9281c
Have the transport decode a message into PacketData.
husafan Oct 26, 2019
40dbd5b
Finish implementing and testing the Client.
husafan Oct 27, 2019
946235e
Remove POST requests from transport initialization.
husafan Oct 27, 2019
cd4a50b
Remove erroneous log line.
husafan Oct 27, 2019
da6930c
Remove complexity, aka channels.
husafan Nov 2, 2019
6968a9e
Uppercase subscribed symbols.
husafan Nov 2, 2019
bce2718
Move examples into previous examples package.
husafan Nov 2, 2019
0a35d5c
Move the multiple main examples into different sub packages.
husafan Nov 2, 2019
c11974b
Change the namespace to subscribe to single symbols at a time.
husafan Nov 2, 2019
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 20 additions & 0 deletions examples/socketio_client/socketio_client.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package main

import (
"flag"
"fmt"
"time"

"github.com/timpalpant/go-iex"
"github.com/timpalpant/go-iex/socketio"
)

func main() {
flag.Parse()
client := socketio.NewClient()
ns := client.GetTOPSNamespace()
go ns.SubscribeTo(func(msg iex.TOPS) {
fmt.Printf("Received message: %+v\n", msg)
}, "fb", "goog")
time.Sleep(30 * time.Second)
}
93 changes: 93 additions & 0 deletions examples/socketio_protocol/iex_socketio_protocol.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
package main

import (
"encoding/json"
"flag"
"io"
"io/ioutil"
"net/http"
"net/http/cookiejar"
"net/url"
"strings"

"github.com/chilts/sid"
"github.com/golang/glog"
"github.com/gorilla/websocket"
)

type handshake struct {
Sid string
}

func makeRequest(client *http.Client, method string,
uri *url.URL, bodyData *string) []byte {
glog.Infof("Making %s request:> %v", method, uri)

var reader io.Reader
if bodyData != nil {
data := *bodyData
glog.Infof("With data:> %s", data)
reader = strings.NewReader(data)
}
req, _ := http.NewRequest(method, uri.String(), reader)
resp, _ := client.Do(req)

body, _ := ioutil.ReadAll(resp.Body)
resp.Body.Close()
glog.Infof("Response:> %v", string(body))
return body
}

func wsMessage(conn *websocket.Conn, msg []byte) {
glog.Infof("Writing WS message:> %s", string(msg))
conn.WriteMessage(websocket.TextMessage, msg)
}

func wsReadMessage(conn *websocket.Conn) {
_, message, err := conn.ReadMessage()
if err != nil {
glog.Fatal(err)
}
glog.Infof("WS Response: %s", string(message))
}

func main() {
flag.Parse()
glog.Info("Starting handshake sequence")

jar, _ := cookiejar.New(nil)
client := &http.Client{Jar: jar}

uri, _ := url.Parse("https://ws-api.iextrading.com/socket.io/")
values := uri.Query()
values.Set("t", sid.IdBase64())
values.Set("EIO", "3")
values.Set("transport", "polling")
uri.RawQuery = values.Encode()

resp := makeRequest(client, "GET", uri, nil)

var hs handshake
json.Unmarshal(resp[4:], &hs)
values.Set("sid", hs.Sid)
uri.RawQuery = values.Encode()

makeRequest(client, "GET", uri, nil)

uri, _ = url.Parse("wss://ws-api.iextrading.com/socket.io/")
values.Set("transport", "websocket")
uri.RawQuery = values.Encode()
glog.Infof("Websocket connecting to:> %s", uri.String())
conn, _, err := websocket.DefaultDialer.Dial(uri.String(), nil)
if err != nil {
glog.Fatal(err)
}
wsMessage(conn, []byte("5"))
wsMessage(conn, []byte("2"))
wsReadMessage(conn)
wsMessage(conn, []byte("40/1.0/last,"))
wsReadMessage(conn)
wsMessage(conn, []byte("42/1.0/last,[\"subscribe\",\"fb,goog\"]"))
wsReadMessage(conn)
wsReadMessage(conn)
}
9 changes: 7 additions & 2 deletions go.mod
Original file line number Diff line number Diff line change
@@ -1,11 +1,16 @@
module github.com/timpalpant/go-iex

require (
github.com/cheekybits/genny v1.0.0
github.com/chilts/sid v0.0.0-20190607042430-660e94789ec9
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b
github.com/google/go-cmp v0.2.0 // indirect
github.com/google/go-querystring v0.0.0-20170111101155-53e6ce116135
github.com/google/gopacket v1.1.16-0.20181023151400-a35e09f9f224
github.com/gorilla/websocket v1.4.1
github.com/johnmccabe/go-bitbar v0.4.0
github.com/mdlayher/raw v0.0.0-20181016155347-fa5ef3332ca9 // indirect
golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519 // indirect
golang.org/x/sys v0.0.0-20181024145615-5cd93ef61a7c // indirect
github.com/smartystreets/goconvey v0.0.0-20190731233626-505e41936337
)

go 1.13
27 changes: 23 additions & 4 deletions go.sum
Original file line number Diff line number Diff line change
@@ -1,14 +1,33 @@
github.com/cheekybits/genny v1.0.0 h1:uGGa4nei+j20rOSeDeP5Of12XVm7TGUd4dJA9RDitfE=
github.com/cheekybits/genny v1.0.0/go.mod h1:+tQajlRqAUrPI7DOSpB0XAqZYtQakVtB7wXkRAgjxjQ=
github.com/chilts/sid v0.0.0-20190607042430-660e94789ec9 h1:z0uK8UQqjMVYzvk4tiiu3obv2B44+XBsvgEJREQfnO8=
github.com/chilts/sid v0.0.0-20190607042430-660e94789ec9/go.mod h1:Jl2neWsQaDanWORdqZ4emBl50J4/aRBBS4FyyG9/PFo=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b h1:VKtxabqXZkF25pY9ekfRL6a582T4P37/31XEstQ5p58=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
github.com/google/go-cmp v0.2.0 h1:+dTQ8DZQJz0Mb/HjFlkptS1FeQ4cWSnN941F8aEG4SQ=
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
github.com/google/go-querystring v0.0.0-20170111101155-53e6ce116135 h1:zLTLjkaOFEFIOxY5BWLFLwh+cL8vOBW4XJ2aqLE/Tf0=
github.com/google/go-querystring v0.0.0-20170111101155-53e6ce116135/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck=
github.com/google/gopacket v1.1.16-0.20181023151400-a35e09f9f224 h1:78xLKlzgK/iEGI5iyrSMXEZu+kRRT+s08QqpSXonq7o=
github.com/google/gopacket v1.1.16-0.20181023151400-a35e09f9f224/go.mod h1:UCLx9mCmAwsVbn6qQl1WIEt2SO7Nd2fD0th1TBAsqBw=
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8=
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
github.com/gorilla/websocket v1.4.1 h1:q7AeDBpnBk8AogcD4DSag/Ukw/KV+YhzLj2bP5HvKCM=
github.com/gorilla/websocket v1.4.1/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/johnmccabe/go-bitbar v0.4.0 h1:n2vBc0btNbDkdyEfovT9YjZE/QJvNUKCSASevTperhg=
github.com/johnmccabe/go-bitbar v0.4.0/go.mod h1:i67T2iQ7Ql/v6x4NbPLlW7eTs+3d/vZgVDl12pr03C8=
github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo=
github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
github.com/mdlayher/raw v0.0.0-20181016155347-fa5ef3332ca9 h1:tOtO8DXiNGj9NshRKHWiZuGlSldPFzFCFYhNtsKTBCs=
github.com/mdlayher/raw v0.0.0-20181016155347-fa5ef3332ca9/go.mod h1:rC/yE65s/DoHB6BzVOUBNYBGTg772JVytyAytffIZkY=
golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519 h1:x6rhz8Y9CjbgQkccRGmELH6K+LJj7tOoh3XWeC1yaQM=
golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/sys v0.0.0-20181024145615-5cd93ef61a7c h1:8QwKN2PcBeeHEiYIX6348SzigNWH9uHHP1EOEs5ExSc=
golang.org/x/sys v0.0.0-20181024145615-5cd93ef61a7c/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykElWQ6/NYmHa3jpm/yHnI4xSofP+UP6SpjHcSeM=
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
github.com/smartystreets/goconvey v0.0.0-20190731233626-505e41936337 h1:WN9BUFbdyOsSH/XohnWpXOlq9NBD5sGAB2FciQMUEe8=
github.com/smartystreets/goconvey v0.0.0-20190731233626-505e41936337/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/net v0.0.0-20190311183353-d8887717615a h1:oWX7TPOiFAMXLq8o0ikBYfCJVlRHBcsciT5bXOrH628=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a h1:1BGLXjeY4akVXGgbC9HugT3Jv3hCI0z56oJR5vAMgBU=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
1 change: 1 addition & 0 deletions socketio/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
*~
134 changes: 134 additions & 0 deletions socketio/client.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
package socketio

import (
"io/ioutil"
"net/http"
"net/http/cookiejar"
"sync"

"github.com/golang/glog"
"github.com/gorilla/websocket"
)

const (
deep string = "/1.0/deep"
last string = "/1.0/last"
tops string = "/1.0/tops"
)

// Connects to IEX SocketIO endpoints and routes received messages back to the
// correct handlers.
type Client struct {
// Allows reference counting of open namespaces.
CountingSubscriber
// Protects access to namespaces.
sync.Mutex

// The Transport object used to send and receive SocketIO messages.
transport Transport
// Points to a DEEP namespace.
deepNamespace *IexDEEPNamespace
// Points to a Last namespace.
lastNamespace *IexLastNamespace
// Points to a TOPS namespace.
topsNamespace *IexTOPSNamespace
}

func (c *Client) closeNamespace(ns string) {
c.Lock()
defer c.Unlock()
c.Unsubscribe(ns)
if !c.Subscribed(ns) {
enc := NewWSEncoder(ns)
r, err := enc.EncodePacket(Message, Disconnect)
if err != nil {
glog.Errorf(
"Error disconnecting from %s: %s",
ns, err)
}
msg, err := ioutil.ReadAll(r)
if err != nil {
glog.Errorf(
"Error disconnecting from %s: %s",
ns, err)
}
if _, err = c.transport.Write(msg); err != nil {
glog.Errorf(
"Error disconnecting from %s: %s",
ns, err)
}
switch ns {
case deep:
c.deepNamespace = nil
case last:
c.lastNamespace = nil
case tops:
c.topsNamespace = nil
}
}
}

func (c *Client) GetDEEPNamespace() *IexDEEPNamespace {
if c.deepNamespace != nil {
return c.deepNamespace
}
c.deepNamespace = NewIexDEEPNamespace(
c.transport, deepSubUnsubFactory, c.closeNamespace)
return c.deepNamespace
}

func (c *Client) GetLastNamespace() *IexLastNamespace {
if c.lastNamespace != nil {
return c.lastNamespace
}
c.lastNamespace = NewIexLastNamespace(
c.transport, simpleSubUnsubFactory, c.closeNamespace)
return c.lastNamespace
}

func (c *Client) GetTOPSNamespace() *IexTOPSNamespace {
if c.topsNamespace != nil {
return c.topsNamespace
}
c.topsNamespace = NewIexTOPSNamespace(
c.transport, simpleSubUnsubFactory, c.closeNamespace)
return c.topsNamespace
}

type defaultDialerWrapper struct {
dialer *websocket.Dialer
}

func (d *defaultDialerWrapper) Dial(uri string, hdr http.Header) (
WSConn, *http.Response, error) {
return d.dialer.Dial(uri, hdr)
}

// Returns a SocketIO client that will use the passed in transport for
// communication. If it is nil, a default Transport will be created using an
// http.Client and websocket.DefaultDialer. The ability to inject a Tranport
// is mainly meant for testing.
func NewClientWithTransport(conn Transport) *Client {
toReturn := &Client{
transport: conn,
}
if conn == nil {
wrapper := &defaultDialerWrapper{websocket.DefaultDialer}
jar, err := cookiejar.New(nil)
if err != nil {
glog.Fatalf("Error creating cookie jar: %s", err)
}
transport, err := NewTransport(&http.Client{Jar: jar}, wrapper)
if err != nil {
glog.Fatalf(
"Failed to create default transport: %s",
err)
}
toReturn.transport = transport
}
return toReturn

}
func NewClient() *Client {
return NewClientWithTransport(nil)
}
Loading