influxdb/messaging/handler.go

175 lines
4.4 KiB
Go
Raw Normal View History

2014-10-21 02:42:03 +00:00
package messaging
2014-10-02 02:49:21 +00:00
import (
"io"
2014-10-24 00:54:12 +00:00
"io/ioutil"
2014-10-02 02:49:21 +00:00
"net/http"
2015-03-08 21:28:43 +00:00
"net/url"
2014-10-24 00:54:12 +00:00
"strconv"
2014-10-02 02:49:21 +00:00
"strings"
"github.com/influxdb/influxdb/raft"
)
2014-10-17 04:11:28 +00:00
// Handler represents an HTTP handler by the broker.
type Handler struct {
2015-03-08 21:28:43 +00:00
Broker interface {
LeaderURL() *url.URL
TopicReader(topicID, index uint64, streaming bool) io.ReadCloser
Publish(m *Message) (uint64, error)
SetTopicMaxIndex(topicID, index uint64) error
}
2015-03-08 21:28:43 +00:00
RaftHandler http.Handler
}
2014-10-02 02:49:21 +00:00
// ServeHTTP serves an HTTP request.
2014-10-17 04:11:28 +00:00
func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
2014-10-02 02:49:21 +00:00
// Delegate raft requests to its own handler.
if strings.HasPrefix(r.URL.Path, "/raft") {
2015-03-08 21:28:43 +00:00
h.RaftHandler.ServeHTTP(w, r)
2014-10-02 02:49:21 +00:00
return
}
2014-10-03 03:13:42 +00:00
// Route all InfluxDB broker requests.
2014-10-02 02:49:21 +00:00
switch r.URL.Path {
case "/messaging/messages":
2014-10-24 00:54:12 +00:00
if r.Method == "GET" {
h.getMessages(w, r)
2014-11-13 05:32:42 +00:00
} else if r.Method == "POST" {
h.postMessages(w, r)
2015-01-10 15:48:50 +00:00
} else {
http.Error(w, "Method Not Allowed", http.StatusMethodNotAllowed)
}
2015-03-01 14:06:25 +00:00
case "/messaging/heartbeat":
if r.Method == "POST" {
h.postHeartbeat(w, r)
2015-03-01 14:06:25 +00:00
} else {
http.Error(w, "Method Not Allowed", http.StatusMethodNotAllowed)
}
2014-11-13 05:32:42 +00:00
default:
http.NotFound(w, r)
2014-10-02 02:49:21 +00:00
}
}
2014-10-17 04:11:28 +00:00
// getMessages streams messages from a topic.
func (h *Handler) getMessages(w http.ResponseWriter, req *http.Request) {
2014-10-24 00:54:12 +00:00
// Read the topic ID.
topicID, err := strconv.ParseUint(req.URL.Query().Get("topicID"), 10, 64)
if err != nil {
2014-10-24 00:54:12 +00:00
h.error(w, ErrTopicRequired, http.StatusBadRequest)
return
}
// Read the index to start from.
index, err := strconv.ParseUint(req.URL.Query().Get("index"), 10, 64)
2014-10-24 00:54:12 +00:00
if err != nil {
h.error(w, ErrIndexRequired, http.StatusBadRequest)
2014-10-24 00:54:12 +00:00
return
}
// Read the streaming flag.
streaming := (req.URL.Query().Get("streaming") == "true")
2014-10-24 00:54:12 +00:00
// Create a topic reader.
2015-03-08 21:28:43 +00:00
r := h.Broker.TopicReader(topicID, index, streaming)
defer r.Close()
// Ensure we close the topic reader if the connection is disconnected.
if w, ok := w.(http.CloseNotifier); ok {
go func() {
select {
case <-w.CloseNotify():
}
}()
}
// Write out all data from the topic reader.
io.Copy(w, r)
}
// postMessages publishes a message to the broker.
func (h *Handler) postMessages(w http.ResponseWriter, r *http.Request) {
// Read the message type.
typ, err := strconv.ParseUint(r.URL.Query().Get("type"), 10, 16)
if err != nil {
h.error(w, ErrMessageTypeRequired, http.StatusBadRequest)
2015-01-10 15:48:50 +00:00
return
}
// Read the topic ID.
topicID, err := strconv.ParseUint(r.URL.Query().Get("topicID"), 10, 64)
if err != nil {
2015-01-10 15:48:50 +00:00
h.error(w, ErrTopicRequired, http.StatusBadRequest)
return
}
// Read the request body.
data, err := ioutil.ReadAll(r.Body)
if err != nil {
2015-01-10 15:48:50 +00:00
h.error(w, err, http.StatusInternalServerError)
return
}
// Publish message to the broker.
2015-03-08 21:28:43 +00:00
index, err := h.Broker.Publish(&Message{Type: MessageType(typ), TopicID: topicID, Data: data})
if err == raft.ErrNotLeader {
h.redirectToLeader(w, r)
return
2015-01-10 15:48:50 +00:00
} else if err != nil {
h.error(w, err, http.StatusInternalServerError)
return
}
// Return index.
w.Header().Set("X-Broker-Index", strconv.FormatUint(index, 10))
2015-01-10 15:48:50 +00:00
}
// postHearbeat receives a heartbeat from a client reporting the highest
// replicated index for a given topic.
func (h *Handler) postHeartbeat(w http.ResponseWriter, r *http.Request) {
// Read the topic id.
topicID, err := strconv.ParseUint(r.URL.Query().Get("topicID"), 10, 16)
2015-03-01 14:06:25 +00:00
if err != nil {
h.error(w, ErrTopicRequired, http.StatusBadRequest)
2015-03-01 14:06:25 +00:00
return
}
// Read the index.
index, err := strconv.ParseUint(r.URL.Query().Get("index"), 10, 16)
if err != nil {
h.error(w, ErrIndexRequired, http.StatusBadRequest)
return
}
// Update the topic's highest replicated index.
2015-03-08 21:28:43 +00:00
if err := h.Broker.SetTopicMaxIndex(topicID, index); err == raft.ErrNotLeader {
2015-03-01 14:06:25 +00:00
h.redirectToLeader(w, r)
return
} else if err != nil {
h.error(w, err, http.StatusInternalServerError)
return
}
}
2014-10-24 00:54:12 +00:00
// error writes an error to the client and sets the status code.
func (h *Handler) error(w http.ResponseWriter, err error, code int) {
s := err.Error()
w.Header().Set("X-Broker-Error", s)
http.Error(w, s, code)
}
// redirects to the current known leader.
// If no leader is found then returns a 500.
func (h *Handler) redirectToLeader(w http.ResponseWriter, r *http.Request) {
2015-03-08 21:28:43 +00:00
if u := h.Broker.LeaderURL(); u != nil {
redirectURL := *r.URL
redirectURL.Scheme = u.Scheme
redirectURL.Host = u.Host
http.Redirect(w, r, redirectURL.String(), http.StatusTemporaryRedirect)
return
}
h.error(w, raft.ErrNotLeader, http.StatusInternalServerError)
}