adding quickfix library
This commit is contained in:
244
quickfix/initiator.go
Normal file
244
quickfix/initiator.go
Normal file
@ -0,0 +1,244 @@
|
||||
// Copyright (c) quickfixengine.org All rights reserved.
|
||||
//
|
||||
// This file may be distributed under the terms of the quickfixengine.org
|
||||
// license as defined by quickfixengine.org and appearing in the file
|
||||
// LICENSE included in the packaging of this file.
|
||||
//
|
||||
// This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING
|
||||
// THE WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A
|
||||
// PARTICULAR PURPOSE.
|
||||
//
|
||||
// See http://www.quickfixengine.org/LICENSE for licensing information.
|
||||
//
|
||||
// Contact ask@quickfixengine.org if any conditions of this licensing
|
||||
// are not clear to you.
|
||||
|
||||
package quickfix
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"golang.org/x/net/proxy"
|
||||
)
|
||||
|
||||
// Initiator initiates connections and processes messages for all sessions.
|
||||
type Initiator struct {
|
||||
app Application
|
||||
settings *Settings
|
||||
sessionSettings map[SessionID]*SessionSettings
|
||||
storeFactory MessageStoreFactory
|
||||
logFactory LogFactory
|
||||
globalLog Log
|
||||
stopChan chan interface{}
|
||||
wg sync.WaitGroup
|
||||
sessions map[SessionID]*session
|
||||
sessionFactory
|
||||
}
|
||||
|
||||
// Start Initiator.
|
||||
func (i *Initiator) Start() (err error) {
|
||||
i.stopChan = make(chan interface{})
|
||||
|
||||
for sessionID, settings := range i.sessionSettings {
|
||||
// TODO: move into session factory.
|
||||
var tlsConfig *tls.Config
|
||||
if tlsConfig, err = loadTLSConfig(settings); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
var dialer proxy.ContextDialer
|
||||
if dialer, err = loadDialerConfig(settings); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
i.wg.Add(1)
|
||||
go func(sessID SessionID) {
|
||||
i.handleConnection(i.sessions[sessID], tlsConfig, dialer)
|
||||
i.wg.Done()
|
||||
}(sessionID)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Stop Initiator.
|
||||
func (i *Initiator) Stop() {
|
||||
select {
|
||||
case <-i.stopChan:
|
||||
// Closed already.
|
||||
return
|
||||
default:
|
||||
}
|
||||
close(i.stopChan)
|
||||
|
||||
i.wg.Wait()
|
||||
|
||||
for sessionID := range i.sessionSettings {
|
||||
err := UnregisterSession(sessionID)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// NewInitiator creates and initializes a new Initiator.
|
||||
func NewInitiator(app Application, storeFactory MessageStoreFactory, appSettings *Settings, logFactory LogFactory) (*Initiator, error) {
|
||||
i := &Initiator{
|
||||
app: app,
|
||||
storeFactory: storeFactory,
|
||||
settings: appSettings,
|
||||
sessionSettings: appSettings.SessionSettings(),
|
||||
logFactory: logFactory,
|
||||
sessions: make(map[SessionID]*session),
|
||||
sessionFactory: sessionFactory{true},
|
||||
}
|
||||
|
||||
var err error
|
||||
i.globalLog, err = logFactory.Create()
|
||||
if err != nil {
|
||||
return i, err
|
||||
}
|
||||
|
||||
for sessionID, s := range i.sessionSettings {
|
||||
session, err := i.createSession(sessionID, storeFactory, s, logFactory, app)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
i.sessions[sessionID] = session
|
||||
}
|
||||
|
||||
return i, nil
|
||||
}
|
||||
|
||||
// waitForInSessionTime returns true if the session is in session, false if the handler should stop.
|
||||
func (i *Initiator) waitForInSessionTime(session *session) bool {
|
||||
inSessionTime := make(chan interface{})
|
||||
go func() {
|
||||
session.waitForInSessionTime()
|
||||
close(inSessionTime)
|
||||
}()
|
||||
|
||||
select {
|
||||
case <-inSessionTime:
|
||||
case <-i.stopChan:
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
// waitForReconnectInterval returns true if a reconnect should be re-attempted, false if handler should stop.
|
||||
func (i *Initiator) waitForReconnectInterval(reconnectInterval time.Duration) bool {
|
||||
select {
|
||||
case <-time.After(reconnectInterval):
|
||||
case <-i.stopChan:
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
func (i *Initiator) handleConnection(session *session, tlsConfig *tls.Config, dialer proxy.ContextDialer) {
|
||||
var wg sync.WaitGroup
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
session.run()
|
||||
wg.Done()
|
||||
}()
|
||||
|
||||
defer func() {
|
||||
session.stop()
|
||||
wg.Wait()
|
||||
}()
|
||||
|
||||
connectionAttempt := 0
|
||||
|
||||
for {
|
||||
if !i.waitForInSessionTime(session) {
|
||||
return
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
|
||||
// We start a goroutine in order to be able to cancel the dialer mid-connection
|
||||
// on receiving a stop signal to stop the initiator.
|
||||
go func() {
|
||||
select {
|
||||
case <-i.stopChan:
|
||||
cancel()
|
||||
case <-ctx.Done():
|
||||
return
|
||||
}
|
||||
}()
|
||||
|
||||
var disconnected chan interface{}
|
||||
var msgIn chan fixIn
|
||||
var msgOut chan []byte
|
||||
|
||||
address := session.SocketConnectAddress[connectionAttempt%len(session.SocketConnectAddress)]
|
||||
session.log.OnEventf("Connecting to: %v", address)
|
||||
|
||||
netConn, err := dialer.DialContext(ctx, "tcp", address)
|
||||
if err != nil {
|
||||
session.log.OnEventf("Failed to connect: %v", err)
|
||||
goto reconnect
|
||||
} else if tlsConfig != nil {
|
||||
// Unless InsecureSkipVerify is true, server name config is required for TLS
|
||||
// to verify the received certificate
|
||||
if !tlsConfig.InsecureSkipVerify && len(tlsConfig.ServerName) == 0 {
|
||||
serverName := address
|
||||
if c := strings.LastIndex(serverName, ":"); c > 0 {
|
||||
serverName = serverName[:c]
|
||||
}
|
||||
tlsConfig.ServerName = serverName
|
||||
}
|
||||
tlsConn := tls.Client(netConn, tlsConfig)
|
||||
if err = tlsConn.Handshake(); err != nil {
|
||||
session.log.OnEventf("Failed handshake: %v", err)
|
||||
goto reconnect
|
||||
}
|
||||
netConn = tlsConn
|
||||
}
|
||||
|
||||
msgIn = make(chan fixIn, session.InChanCapacity)
|
||||
msgOut = make(chan []byte)
|
||||
if err := session.connect(msgIn, msgOut); err != nil {
|
||||
session.log.OnEventf("Failed to initiate: %v", err)
|
||||
goto reconnect
|
||||
}
|
||||
|
||||
go readLoop(newParser(bufio.NewReader(netConn)), msgIn, session.log)
|
||||
disconnected = make(chan interface{})
|
||||
go func() {
|
||||
writeLoop(netConn, msgOut, session.log)
|
||||
if err := netConn.Close(); err != nil {
|
||||
session.log.OnEvent(err.Error())
|
||||
}
|
||||
close(disconnected)
|
||||
}()
|
||||
|
||||
// This ensures we properly cleanup the goroutine and context used for
|
||||
// dial cancelation after successful connection.
|
||||
cancel()
|
||||
|
||||
select {
|
||||
case <-disconnected:
|
||||
case <-i.stopChan:
|
||||
return
|
||||
}
|
||||
|
||||
reconnect:
|
||||
cancel()
|
||||
|
||||
connectionAttempt++
|
||||
session.log.OnEventf("Reconnecting in %v", session.ReconnectInterval)
|
||||
if !i.waitForReconnectInterval(session.ReconnectInterval) {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user