Reconnect and retry IRC connections

This commit is contained in:
Ken-Håvard Lieng 2015-06-01 05:44:30 +02:00
parent 5cf2822c34
commit c325168a20
3 changed files with 163 additions and 98 deletions

View File

@ -4,7 +4,6 @@ import (
"bufio" "bufio"
"crypto/tls" "crypto/tls"
"fmt" "fmt"
"log"
"net" "net"
"strings" "strings"
"sync" "sync"
@ -59,13 +58,19 @@ type Message struct {
} }
type IRC struct { type IRC struct {
conn net.Conn conn net.Conn
reader *bufio.Reader connected bool
out chan string dialer *net.Dialer
ready sync.WaitGroup reader *bufio.Reader
once sync.Once out chan string
nick string
nickLock sync.Mutex quit chan struct{}
reconnect chan struct{}
ready sync.WaitGroup
once sync.Once
lock sync.Mutex
nick string
Messages chan *Message Messages chan *Message
Server string Server string
@ -79,15 +84,17 @@ type IRC struct {
func NewIRC(nick, username string) *IRC { func NewIRC(nick, username string) *IRC {
return &IRC{ return &IRC{
nick: nick, nick: nick,
Username: username, Username: username,
Realname: nick, Realname: nick,
Messages: make(chan *Message, 32), Messages: make(chan *Message, 32),
out: make(chan string, 32), out: make(chan string, 32),
quit: make(chan struct{}),
reconnect: make(chan struct{}),
} }
} }
func (i *IRC) Connect(address string) error { func (i *IRC) Connect(address string) {
if idx := strings.Index(address, ":"); idx < 0 { if idx := strings.Index(address, ":"); idx < 0 {
i.Host = address i.Host = address
@ -100,40 +107,16 @@ func (i *IRC) Connect(address string) error {
i.Host = address[:idx] i.Host = address[:idx]
} }
i.Server = address i.Server = address
i.dialer = &net.Dialer{Timeout: 10 * time.Second}
dialer := &net.Dialer{Timeout: 30 * time.Second} go i.run()
}
if i.TLS { func (i *IRC) Connected() bool {
if i.TLSConfig == nil { i.lock.Lock()
i.TLSConfig = &tls.Config{InsecureSkipVerify: true} defer i.lock.Unlock()
}
if conn, err := tls.DialWithDialer(dialer, "tcp", address, i.TLSConfig); err != nil { return i.connected
return err
} else {
i.conn = conn
}
} else {
if conn, err := dialer.Dial("tcp", address); err != nil {
return err
} else {
i.conn = conn
}
}
i.reader = bufio.NewReader(i.conn)
if i.Password != "" {
i.Pass(i.Password)
}
i.Nick(i.nick)
i.User(i.Username, i.Realname)
i.ready.Add(1)
go i.send()
go i.recv()
return nil
} }
func (i *IRC) Pass(password string) { func (i *IRC) Pass(password string) {
@ -141,11 +124,11 @@ func (i *IRC) Pass(password string) {
} }
func (i *IRC) Nick(nick string) { func (i *IRC) Nick(nick string) {
i.write("NICK " + nick) i.Write("NICK " + nick)
i.nickLock.Lock() i.lock.Lock()
i.nick = nick i.nick = nick
i.nickLock.Unlock() i.lock.Unlock()
} }
func (i *IRC) User(username, realname string) { func (i *IRC) User(username, realname string) {
@ -162,9 +145,10 @@ func (i *IRC) Mode(target, modes, params string) {
func (i *IRC) Quit() { func (i *IRC) Quit() {
go func() { go func() {
i.ready.Wait() if i.Connected() {
i.write("QUIT") i.write("QUIT")
i.conn.Close() }
close(i.quit)
}() }()
} }
@ -205,8 +189,8 @@ func (i *IRC) Away(message string) {
} }
func (i *IRC) GetNick() string { func (i *IRC) GetNick() string {
i.nickLock.Lock() i.lock.Lock()
defer i.nickLock.Unlock() defer i.lock.Unlock()
return i.nick return i.nick
} }
@ -227,25 +211,117 @@ func (i *IRC) writef(format string, a ...interface{}) {
fmt.Fprintf(i.conn, format+"\r\n", a...) fmt.Fprintf(i.conn, format+"\r\n", a...)
} }
func (i *IRC) send() { func (i *IRC) run() {
i.ready.Wait() i.tryConnect()
for { for {
_, err := i.conn.Write([]byte(<-i.out)) select {
if err != nil { case <-i.quit:
i.close()
i.lock.Lock()
i.connected = false
i.lock.Unlock()
return
case <-i.reconnect:
i.reconnect = make(chan struct{})
i.once = sync.Once{}
i.tryConnect()
}
}
}
func (i *IRC) tryConnect() {
// TODO: backoff
for {
select {
case <-i.quit:
return
default:
}
err := i.connect()
if err == nil {
return return
} }
} }
} }
func (i *IRC) connect() error {
if i.TLS {
if i.TLSConfig == nil {
i.TLSConfig = &tls.Config{InsecureSkipVerify: true}
}
if conn, err := tls.DialWithDialer(i.dialer, "tcp", i.Server, i.TLSConfig); err != nil {
return err
} else {
i.conn = conn
}
} else {
if conn, err := i.dialer.Dial("tcp", i.Server); err != nil {
return err
} else {
i.conn = conn
}
}
i.lock.Lock()
i.connected = true
i.lock.Unlock()
i.reader = bufio.NewReader(i.conn)
if i.Password != "" {
i.Pass(i.Password)
}
i.write("NICK " + i.nick)
i.User(i.Username, i.Realname)
i.ready.Add(1)
go i.send()
go i.recv()
return nil
}
func (i *IRC) send() {
i.ready.Wait()
for {
select {
case <-i.quit:
return
case <-i.reconnect:
return
case msg := <-i.out:
_, err := i.conn.Write([]byte(msg))
if err != nil {
return
}
}
}
}
func (i *IRC) recv() { func (i *IRC) recv() {
defer i.close() defer i.conn.Close()
for { for {
line, err := i.reader.ReadString('\n') line, err := i.reader.ReadString('\n')
if err != nil { if err != nil {
log.Println("IRC connection to", i.Server, "died") i.lock.Lock()
i.connected = false
i.lock.Unlock()
i.once.Do(i.ready.Done)
close(i.reconnect)
return return
} }
select {
case <-i.quit:
return
default:
}
msg := parseMessage(line) msg := parseMessage(line)
i.Messages <- msg i.Messages <- msg
@ -260,8 +336,9 @@ func (i *IRC) recv() {
} }
func (i *IRC) close() { func (i *IRC) close() {
i.conn.Close() if i.Connected() {
i.once.Do(i.ready.Done) i.once.Do(i.ready.Done)
}
close(i.out) close(i.out)
close(i.Messages) close(i.Messages)
} }
@ -275,7 +352,12 @@ func parseMessage(line string) *Message {
if strings.HasPrefix(line, ":") { if strings.HasPrefix(line, ":") {
cmdStart = strings.Index(line, " ") + 1 cmdStart = strings.Index(line, " ") + 1
msg.Prefix = line[1 : cmdStart-1] msg.Prefix = line[1 : cmdStart-1]
msg.Nick = parseNick(msg.Prefix)
if i := strings.Index(msg.Prefix, "!"); i > 0 {
msg.Nick = msg.Prefix[:i]
} else {
msg.Nick = msg.Prefix
}
} }
if i := strings.Index(line, " :"); i > 0 { if i := strings.Index(line, " :"); i > 0 {
@ -295,10 +377,3 @@ func parseMessage(line string) *Message {
return &msg return &msg
} }
func parseNick(prefix string) string {
if i := strings.Index(prefix, "!"); i > 0 {
return prefix[:i]
}
return prefix
}

View File

@ -69,22 +69,18 @@ func reconnect() {
irc.Realname = server.Realname irc.Realname = server.Realname
go func(server storage.Server) { go func(server storage.Server) {
err := irc.Connect(server.Address) irc.Connect(server.Address)
if err != nil { session.setIRC(irc.Host, irc)
log.Println(err)
} else {
session.setIRC(irc.Host, irc)
go handleMessages(irc, session) go handleMessages(irc, session)
var joining []string var joining []string
for _, channel := range channels { for _, channel := range channels {
if channel.Server == server.Address { if channel.Server == server.Address {
joining = append(joining, channel.Name) joining = append(joining, channel.Name)
}
} }
irc.Join(joining...)
} }
irc.Join(joining...)
}(server) }(server)
} }
} }

View File

@ -95,24 +95,18 @@ func handleWS(ws *websocket.Conn) {
} }
go func() { go func() {
err := irc.Connect(data.Server) irc.Connect(data.Server)
if err != nil { go handleMessages(irc, session)
session.deleteIRC(irc.Host)
session.sendError(err, irc.Host)
log.Println(err)
} else {
go handleMessages(irc, session)
session.user.AddServer(storage.Server{ session.user.AddServer(storage.Server{
Name: data.Name, Name: data.Name,
Address: irc.Host, Address: irc.Host,
TLS: data.TLS, TLS: data.TLS,
Password: data.Password, Password: data.Password,
Nick: data.Nick, Nick: data.Nick,
Username: data.Username, Username: data.Username,
Realname: data.Realname, Realname: data.Realname,
}) })
}
}() }()
} else { } else {
log.Println(addr, "already connected to", data.Server) log.Println(addr, "already connected to", data.Server)