influxdb/messaging/client.go

243 lines
5.6 KiB
Go
Raw Normal View History

2014-10-21 02:42:03 +00:00
package messaging
2014-10-17 04:11:28 +00:00
import (
2014-10-24 00:54:12 +00:00
"bytes"
2014-10-17 04:11:28 +00:00
"errors"
2014-10-24 00:54:12 +00:00
"fmt"
2014-11-13 05:32:42 +00:00
"log"
2014-10-17 04:11:28 +00:00
"math/rand"
"net/http"
"net/url"
2014-11-13 05:32:42 +00:00
"os"
2014-10-24 00:54:12 +00:00
"strconv"
2014-10-17 04:11:28 +00:00
"sync"
"time"
)
2014-10-17 15:53:10 +00:00
// DefaultReconnectTimeout is the default time to wait between when a broker
// stream disconnects and another connection is retried.
const DefaultReconnectTimeout = 100 * time.Millisecond
2014-10-17 04:11:28 +00:00
// Client represents a client for the broker's HTTP API.
// Once opened, the client will stream down all messages that
type Client struct {
mu sync.Mutex
name string // the name of the client connecting.
urls []*url.URL // list of URLs for all known brokers.
opened bool
2014-10-17 15:53:10 +00:00
done chan chan struct{} // disconnection notification
2014-10-17 04:11:28 +00:00
// Channel streams messages from the broker.
2014-10-22 05:32:19 +00:00
c chan *Message
2014-10-17 15:53:10 +00:00
// The amount of time to wait before reconnecting to a broker stream.
ReconnectTimeout time.Duration
2014-11-13 05:32:42 +00:00
// The logging interface used by the client for out-of-band errors.
Logger *log.Logger
2014-10-17 04:11:28 +00:00
}
// NewClient returns a new instance of Client.
func NewClient(name string) *Client {
return &Client{
2014-10-17 15:53:10 +00:00
name: name,
ReconnectTimeout: DefaultReconnectTimeout,
2014-11-13 05:32:42 +00:00
Logger: log.New(os.Stderr, "[messaging] ", log.LstdFlags),
2014-10-17 04:11:28 +00:00
}
}
// Name returns the replica name that the client was opened with.
func (c *Client) Name() string { return c.name }
2014-10-22 05:32:19 +00:00
// C returns streaming channel.
// Messages can be duplicated so it is important to check the index
// of the incoming message index to make sure it has not been processed.
func (c *Client) C() <-chan *Message { return c.c }
2014-10-17 04:11:28 +00:00
// URLs returns a list of broker URLs to connect to.
func (c *Client) URLs() []*url.URL {
c.mu.Lock()
defer c.mu.Unlock()
return c.urls
}
2014-10-24 00:54:12 +00:00
// LeaderURL returns the URL of the broker leader.
func (c *Client) LeaderURL() *url.URL {
c.mu.Lock()
defer c.mu.Unlock()
// TODO(benbjohnson): Actually keep track of the leader.
// HACK(benbjohnson): For testing, just grab a url.
return c.urls[0]
}
2014-10-17 04:11:28 +00:00
// Open initializes and opens the connection to the broker cluster.
func (c *Client) Open(urls []*url.URL) error {
c.mu.Lock()
defer c.mu.Unlock()
// Return error if the client is already open.
// Require at least one broker URL.
if c.opened {
return ErrClientOpen
} else if len(urls) == 0 {
return ErrBrokerURLRequired
}
// Set the URLs to connect to on the client.
c.urls = urls
// Create a channel for streaming messages.
2014-10-22 05:32:19 +00:00
c.c = make(chan *Message, 0)
2014-10-17 04:11:28 +00:00
// Open the streamer.
2014-10-17 15:53:10 +00:00
c.done = make(chan chan struct{})
2014-10-17 04:11:28 +00:00
go c.streamer(c.done)
// Set open flag.
c.opened = true
return nil
}
// Close disconnects the client from the broker cluster.
func (c *Client) Close() error {
c.mu.Lock()
defer c.mu.Unlock()
// Return error if the client is already closed.
if !c.opened {
return ErrClientClosed
}
2014-10-17 15:53:10 +00:00
// Shutdown streamer.
ch := make(chan struct{})
c.done <- ch
<-ch
c.done = nil
2014-10-17 04:11:28 +00:00
// Close message stream.
2014-10-22 05:32:19 +00:00
close(c.c)
c.c = nil
2014-10-17 04:11:28 +00:00
// Unset open flag.
c.opened = false
return nil
}
2014-10-24 00:54:12 +00:00
// Publish sends a message to the broker and returns an index or error.
2014-11-11 05:25:03 +00:00
func (c *Client) Publish(m *Message) (uint64, error) {
2014-10-24 00:54:12 +00:00
// Send the message to the messages endpoint.
2014-11-13 05:32:42 +00:00
u := *c.LeaderURL()
u.Path = "/messages"
2014-11-11 05:25:03 +00:00
u.RawQuery = url.Values{
"type": {strconv.FormatUint(uint64(m.Type), 10)},
"topicID": {strconv.FormatUint(m.TopicID, 10)},
}.Encode()
resp, err := http.Post(u.String(), "application/octet-stream", bytes.NewReader(m.Data))
2014-10-24 00:54:12 +00:00
if err != nil {
return 0, err
}
defer func() { _ = resp.Body.Close() }()
// If a non-200 status is returned then an error occurred.
if resp.StatusCode != http.StatusOK {
return 0, errors.New(resp.Header.Get("X-Broker-Error"))
}
// Parse broker index.
index, err := strconv.ParseUint(resp.Header.Get("X-Broker-Index"), 10, 64)
if err != nil {
return 0, fmt.Errorf("invalid index: %s", err)
}
return index, nil
}
2014-10-17 04:11:28 +00:00
// streamer connects to a broker server and streams the replica's messages.
2014-10-17 15:53:10 +00:00
func (c *Client) streamer(done chan chan struct{}) {
2014-10-17 04:11:28 +00:00
for {
// Check for the client disconnection.
select {
2014-10-17 15:53:10 +00:00
case ch := <-done:
close(ch)
2014-10-17 04:11:28 +00:00
return
default:
}
// TODO: Validate that there is at least one broker URL.
// Choose a random broker URL.
urls := c.URLs()
u := *urls[rand.Intn(len(urls))]
// Connect to broker and stream.
2014-11-13 05:32:42 +00:00
u.Path = "/messages"
2014-10-17 04:11:28 +00:00
if err := c.streamFromURL(&u, done); err == errDone {
return
2014-11-13 05:32:42 +00:00
} else if err != nil {
c.Logger.Print(err)
2014-10-17 04:11:28 +00:00
}
}
}
// streamFromURL connects to a broker server and streams the replica's messages.
2014-10-17 15:53:10 +00:00
func (c *Client) streamFromURL(u *url.URL, done chan chan struct{}) error {
// Set the replica name on the URL and open the stream.
2014-10-17 04:11:28 +00:00
u.RawQuery = url.Values{"name": {c.name}}.Encode()
resp, err := http.Get(u.String())
if err != nil {
2014-10-17 15:53:10 +00:00
time.Sleep(c.ReconnectTimeout)
2014-10-17 04:11:28 +00:00
return nil
}
defer func() { _ = resp.Body.Close() }()
// Ensure that we received a 200 OK from the server before streaming.
if resp.StatusCode != http.StatusOK {
2014-10-17 15:53:10 +00:00
time.Sleep(c.ReconnectTimeout)
return nil
2014-10-17 04:11:28 +00:00
}
2014-10-17 15:53:10 +00:00
// Continuously decode messages from request body in a separate goroutine.
errNotify := make(chan error, 0)
go func() {
dec := NewMessageDecoder(resp.Body)
for {
// Decode message from the stream.
m := &Message{}
if err := dec.Decode(m); err != nil {
errNotify <- err
return
}
// Write message to streaming channel.
2014-10-22 05:32:19 +00:00
c.c <- m
2014-10-17 04:11:28 +00:00
}
2014-10-17 15:53:10 +00:00
}()
2014-10-17 04:11:28 +00:00
2014-10-17 15:53:10 +00:00
// Check for the client disconnect or error from the stream.
select {
case ch := <-done:
// Close body.
2014-11-13 05:32:42 +00:00
_ = resp.Body.Close()
2014-10-17 04:11:28 +00:00
2014-10-17 15:53:10 +00:00
// Clear message buffer.
2014-10-17 04:11:28 +00:00
select {
2014-10-22 05:32:19 +00:00
case <-c.c:
2014-10-17 04:11:28 +00:00
default:
}
2014-10-17 15:53:10 +00:00
// Notify the close function and return marker error.
close(ch)
return errDone
case err := <-errNotify:
return err
2014-10-17 04:11:28 +00:00
}
}
// marker error for the streamer.
var errDone = errors.New("done")