featurebase/httpbroadcast/messenger.go
2017-04-19 10:36:27 -05:00

187 lines
4.1 KiB
Go

package httpbroadcast
import (
"bytes"
"errors"
"fmt"
"io"
"io/ioutil"
"net/http"
"net/url"
"golang.org/x/sync/errgroup"
"net"
"github.com/gogo/protobuf/proto"
"github.com/pilosa/pilosa"
)
// HTTPBroadcaster represents a NodeSet that broadcasts messages over HTTP.
type HTTPBroadcaster struct {
server *pilosa.Server
internalPort string
}
// NewHTTPBroadcaster returns a new instance of HTTPBroadcaster.
func NewHTTPBroadcaster(s *pilosa.Server, internalPort string) *HTTPBroadcaster {
return &HTTPBroadcaster{server: s}
}
// SendSync sends a protobuf message to all nodes simultaneously.
// It waits for all nodes to respond before the function returns (and returns any errors).
func (h *HTTPBroadcaster) SendSync(pb proto.Message) error {
// Marshal the pb to []byte
buf, err := pilosa.MarshalMessage(pb)
if err != nil {
return err
}
nodes, err := h.nodes()
if err != nil {
return err
}
var g errgroup.Group
for _, n := range nodes {
// Don't send the message to the local node.
if n.Host == h.server.Host {
continue
}
node := n
g.Go(func() error {
return h.sendNodeMessage(node, buf)
})
}
return g.Wait()
}
// SendAsync exists to implement the Broadcaster interface, but just calls
// SendSync.
func (h *HTTPBroadcaster) SendAsync(pb proto.Message) error {
return h.SendSync(pb)
}
func (h *HTTPBroadcaster) nodes() ([]*pilosa.Node, error) {
if h.server == nil {
return nil, errors.New("HTTPBroadcaster has no reference to Server.")
}
nodeset, ok := h.server.Cluster.NodeSet.(*HTTPNodeSet)
if !ok {
return nil, errors.New("NodeSet cannot be caste to HTTPNodeSet.")
}
return nodeset.Nodes(), nil
}
func (h *HTTPBroadcaster) sendNodeMessage(node *pilosa.Node, msg []byte) error {
var client *http.Client
client = http.DefaultClient
host, _, err := net.SplitHostPort(node.Host)
// Create HTTP request.
req, err := http.NewRequest("POST", (&url.URL{
Scheme: "http",
Host: host + ":" + h.internalPort,
}).String(), bytes.NewReader(msg))
if err != nil {
return err
}
// Require protobuf encoding.
req.Header.Set("Content-Type", "application/x-protobuf")
// Send request to remote node.
resp, err := client.Do(req)
if err != nil {
return err
}
defer resp.Body.Close()
// Read response into buffer.
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
return err
}
// Check status code.
if resp.StatusCode != http.StatusOK {
return fmt.Errorf("invalid status: code=%d, err=%s", resp.StatusCode, body)
}
return nil
}
type HTTPBroadcastReceiver struct {
port string
handler pilosa.BroadcastHandler
logOutput io.Writer
}
func NewHTTPBroadcastReceiver(port string, logOutput io.Writer) *HTTPBroadcastReceiver {
return &HTTPBroadcastReceiver{
port: port,
logOutput: logOutput,
}
}
func (rec *HTTPBroadcastReceiver) Start(b pilosa.BroadcastHandler) error {
rec.handler = b
go func() {
err := http.ListenAndServe(":"+rec.port, rec)
if err != nil {
fmt.Fprintf(rec.logOutput, "Error listening on %v for HTTPBroadcastReceiver: %v\n", ":"+rec.port, err)
}
}()
return nil
}
func (rec *HTTPBroadcastReceiver) ServeHTTP(w http.ResponseWriter, r *http.Request) {
if r.Header.Get("Content-Type") != "application/x-protobuf" {
http.Error(w, "Unsupported media type", http.StatusUnsupportedMediaType)
return
}
// Read entire body.
body, err := ioutil.ReadAll(r.Body)
if err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
// Unmarshal message to specific proto type.
m, err := pilosa.UnmarshalMessage(body)
if err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
if err := rec.handler.ReceiveMessage(m); err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
}
// HTTPNodeSet represents a NodeSet that broadcasts messages over HTTP.
type HTTPNodeSet struct {
nodes []*pilosa.Node
}
// NewHTTPNodeSet returns a new instance of HTTPNodeSet.
func NewHTTPNodeSet() *HTTPNodeSet {
return &HTTPNodeSet{}
}
func (h *HTTPNodeSet) Nodes() []*pilosa.Node {
return h.nodes
}
func (h *HTTPNodeSet) Open() error {
return nil
}
func (h *HTTPNodeSet) Join(nodes []*pilosa.Node) error {
h.nodes = nodes
return nil
}