Permalink
Fetching contributors…
Cannot retrieve contributors at this time
461 lines (381 sloc) 11.8 KB
package toxiproxy
import (
"encoding/json"
"fmt"
"log"
"net"
"net/http"
"os"
"strings"
"github.com/Shopify/toxiproxy/toxics"
"github.com/gorilla/mux"
"github.com/sirupsen/logrus"
)
type ApiServer struct {
Collection *ProxyCollection
}
func NewServer() *ApiServer {
return &ApiServer{
Collection: NewProxyCollection(),
}
}
func (server *ApiServer) PopulateConfig(filename string) {
file, err := os.Open(filename)
if err != nil {
logrus.WithFields(logrus.Fields{
"config": filename,
"error": err,
}).Error("Error reading config file")
} else {
proxies, err := server.Collection.PopulateJson(file)
if err != nil {
logrus.WithFields(logrus.Fields{
"config": filename,
"error": err,
}).Error("Failed to populate proxies from file")
} else {
logrus.WithFields(logrus.Fields{
"config": filename,
"proxies": len(proxies),
}).Info("Populated proxies from file")
}
}
}
func StopBrowsersMiddleware(h http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if strings.HasPrefix(r.UserAgent(), "Mozilla/") {
http.Error(w, "User agent not allowed", 403)
} else {
h.ServeHTTP(w, r)
}
})
}
func (server *ApiServer) Listen(host string, port string) {
r := mux.NewRouter()
r.HandleFunc("/reset", server.ResetState).Methods("POST")
r.HandleFunc("/proxies", server.ProxyIndex).Methods("GET")
r.HandleFunc("/proxies", server.ProxyCreate).Methods("POST")
r.HandleFunc("/populate", server.Populate).Methods("POST")
r.HandleFunc("/proxies/{proxy}", server.ProxyShow).Methods("GET")
r.HandleFunc("/proxies/{proxy}", server.ProxyUpdate).Methods("POST")
r.HandleFunc("/proxies/{proxy}", server.ProxyDelete).Methods("DELETE")
r.HandleFunc("/proxies/{proxy}/toxics", server.ToxicIndex).Methods("GET")
r.HandleFunc("/proxies/{proxy}/toxics", server.ToxicCreate).Methods("POST")
r.HandleFunc("/proxies/{proxy}/toxics/{toxic}", server.ToxicShow).Methods("GET")
r.HandleFunc("/proxies/{proxy}/toxics/{toxic}", server.ToxicUpdate).Methods("POST")
r.HandleFunc("/proxies/{proxy}/toxics/{toxic}", server.ToxicDelete).Methods("DELETE")
r.HandleFunc("/version", server.Version).Methods("GET")
http.Handle("/", StopBrowsersMiddleware(r))
logrus.WithFields(logrus.Fields{
"host": host,
"port": port,
"version": Version,
}).Info("API HTTP server starting")
err := http.ListenAndServe(net.JoinHostPort(host, port), nil)
if err != nil {
log.Fatal("ListenAndServe: ", err)
}
}
func (server *ApiServer) ProxyIndex(response http.ResponseWriter, request *http.Request) {
proxies := server.Collection.Proxies()
marshalData := make(map[string]interface{}, len(proxies))
for name, proxy := range proxies {
marshalData[name] = proxyWithToxics(proxy)
}
data, err := json.Marshal(marshalData)
if apiError(response, err) {
return
}
response.Header().Set("Content-Type", "application/json")
_, err = response.Write(data)
if err != nil {
logrus.Warn("ProxyIndex: Failed to write response to client", err)
}
}
func (server *ApiServer) ResetState(response http.ResponseWriter, request *http.Request) {
proxies := server.Collection.Proxies()
for _, proxy := range proxies {
err := proxy.Start()
if err != ErrProxyAlreadyStarted && apiError(response, err) {
return
}
proxy.Toxics.ResetToxics()
}
response.WriteHeader(http.StatusNoContent)
_, err := response.Write(nil)
if err != nil {
logrus.Warn("ResetState: Failed to write headers to client", err)
}
}
func (server *ApiServer) ProxyCreate(response http.ResponseWriter, request *http.Request) {
// Default fields to enable the proxy right away
input := Proxy{Enabled: true}
err := json.NewDecoder(request.Body).Decode(&input)
if apiError(response, joinError(err, ErrBadRequestBody)) {
return
}
if len(input.Name) < 1 {
apiError(response, joinError(fmt.Errorf("name"), ErrMissingField))
return
}
if len(input.Upstream) < 1 {
apiError(response, joinError(fmt.Errorf("upstream"), ErrMissingField))
return
}
proxy := NewProxy()
proxy.Name = input.Name
proxy.Listen = input.Listen
proxy.Upstream = input.Upstream
err = server.Collection.Add(proxy, input.Enabled)
if apiError(response, err) {
return
}
data, err := json.Marshal(proxyWithToxics(proxy))
if apiError(response, err) {
return
}
response.Header().Set("Content-Type", "application/json")
response.WriteHeader(http.StatusCreated)
_, err = response.Write(data)
if err != nil {
logrus.Warn("ProxyCreate: Failed to write response to client", err)
}
}
func (server *ApiServer) Populate(response http.ResponseWriter, request *http.Request) {
proxies, err := server.Collection.PopulateJson(request.Body)
apiErr, ok := err.(*ApiError)
if !ok && err != nil {
logrus.Warn("Error did not include status code: ", err)
apiErr = &ApiError{err.Error(), http.StatusInternalServerError}
}
data, err := json.Marshal(struct {
*ApiError `json:",omitempty"`
Proxies []proxyToxics `json:"proxies"`
}{apiErr, proxiesWithToxics(proxies)})
if apiError(response, err) {
return
}
responseCode := http.StatusCreated
if apiErr != nil {
responseCode = apiErr.StatusCode
}
response.Header().Set("Content-Type", "application/json")
response.WriteHeader(responseCode)
_, err = response.Write(data)
if err != nil {
logrus.Warn("Populate: Failed to write response to client", err)
}
}
func (server *ApiServer) ProxyShow(response http.ResponseWriter, request *http.Request) {
vars := mux.Vars(request)
proxy, err := server.Collection.Get(vars["proxy"])
if apiError(response, err) {
return
}
data, err := json.Marshal(proxyWithToxics(proxy))
if apiError(response, err) {
return
}
response.Header().Set("Content-Type", "application/json")
_, err = response.Write(data)
if err != nil {
logrus.Warn("ProxyShow: Failed to write response to client", err)
}
}
func (server *ApiServer) ProxyUpdate(response http.ResponseWriter, request *http.Request) {
vars := mux.Vars(request)
proxy, err := server.Collection.Get(vars["proxy"])
if apiError(response, err) {
return
}
// Default fields are the same as existing proxy
input := Proxy{Listen: proxy.Listen, Upstream: proxy.Upstream, Enabled: proxy.Enabled}
err = json.NewDecoder(request.Body).Decode(&input)
if apiError(response, joinError(err, ErrBadRequestBody)) {
return
}
err = proxy.Update(&input)
if apiError(response, err) {
return
}
data, err := json.Marshal(proxyWithToxics(proxy))
if apiError(response, err) {
return
}
response.Header().Set("Content-Type", "application/json")
_, err = response.Write(data)
if err != nil {
logrus.Warn("ProxyUpdate: Failed to write response to client", err)
}
}
func (server *ApiServer) ProxyDelete(response http.ResponseWriter, request *http.Request) {
vars := mux.Vars(request)
err := server.Collection.Remove(vars["proxy"])
if apiError(response, err) {
return
}
response.WriteHeader(http.StatusNoContent)
_, err = response.Write(nil)
if err != nil {
logrus.Warn("ProxyDelete: Failed to write headers to client", err)
}
}
func (server *ApiServer) ToxicIndex(response http.ResponseWriter, request *http.Request) {
vars := mux.Vars(request)
proxy, err := server.Collection.Get(vars["proxy"])
if apiError(response, err) {
return
}
toxics := proxy.Toxics.GetToxicArray()
data, err := json.Marshal(toxics)
if apiError(response, err) {
return
}
response.Header().Set("Content-Type", "application/json")
_, err = response.Write(data)
if err != nil {
logrus.Warn("ToxicIndex: Failed to write response to client", err)
}
}
func (server *ApiServer) ToxicCreate(response http.ResponseWriter, request *http.Request) {
vars := mux.Vars(request)
proxy, err := server.Collection.Get(vars["proxy"])
if apiError(response, err) {
return
}
toxic, err := proxy.Toxics.AddToxicJson(request.Body)
if apiError(response, err) {
return
}
data, err := json.Marshal(toxic)
if apiError(response, err) {
return
}
response.Header().Set("Content-Type", "application/json")
_, err = response.Write(data)
if err != nil {
logrus.Warn("ToxicCreate: Failed to write response to client", err)
}
}
func (server *ApiServer) ToxicShow(response http.ResponseWriter, request *http.Request) {
vars := mux.Vars(request)
proxy, err := server.Collection.Get(vars["proxy"])
if apiError(response, err) {
return
}
toxic := proxy.Toxics.GetToxic(vars["toxic"])
if toxic == nil {
apiError(response, ErrToxicNotFound)
return
}
data, err := json.Marshal(toxic)
if apiError(response, err) {
return
}
response.Header().Set("Content-Type", "application/json")
_, err = response.Write(data)
if err != nil {
logrus.Warn("ToxicShow: Failed to write response to client", err)
}
}
func (server *ApiServer) ToxicUpdate(response http.ResponseWriter, request *http.Request) {
vars := mux.Vars(request)
proxy, err := server.Collection.Get(vars["proxy"])
if apiError(response, err) {
return
}
toxic, err := proxy.Toxics.UpdateToxicJson(vars["toxic"], request.Body)
if apiError(response, err) {
return
}
data, err := json.Marshal(toxic)
if apiError(response, err) {
return
}
response.Header().Set("Content-Type", "application/json")
_, err = response.Write(data)
if err != nil {
logrus.Warn("ToxicUpdate: Failed to write response to client", err)
}
}
func (server *ApiServer) ToxicDelete(response http.ResponseWriter, request *http.Request) {
vars := mux.Vars(request)
proxy, err := server.Collection.Get(vars["proxy"])
if apiError(response, err) {
return
}
err = proxy.Toxics.RemoveToxic(vars["toxic"])
if apiError(response, err) {
return
}
response.WriteHeader(http.StatusNoContent)
_, err = response.Write(nil)
if err != nil {
logrus.Warn("ToxicDelete: Failed to write headers to client", err)
}
}
func (server *ApiServer) Version(response http.ResponseWriter, request *http.Request) {
response.Header().Set("Content-Type", "text/plain;charset=utf-8")
_, err := response.Write([]byte(Version))
if err != nil {
logrus.Warn("Version: Failed to write response to client", err)
}
}
type ApiError struct {
Message string `json:"error"`
StatusCode int `json:"status"`
}
func (e *ApiError) Error() string {
return e.Message
}
func newError(msg string, status int) *ApiError {
return &ApiError{msg, status}
}
func joinError(err error, wrapper *ApiError) *ApiError {
if err != nil {
return &ApiError{wrapper.Message + ": " + err.Error(), wrapper.StatusCode}
}
return nil
}
var (
ErrBadRequestBody = newError("bad request body", http.StatusBadRequest)
ErrMissingField = newError("missing required field", http.StatusBadRequest)
ErrProxyNotFound = newError("proxy not found", http.StatusNotFound)
ErrProxyAlreadyExists = newError("proxy already exists", http.StatusConflict)
ErrInvalidStream = newError("stream was invalid, can be either upstream or downstream", http.StatusBadRequest)
ErrInvalidToxicType = newError("invalid toxic type", http.StatusBadRequest)
ErrToxicAlreadyExists = newError("toxic already exists", http.StatusConflict)
ErrToxicNotFound = newError("toxic not found", http.StatusNotFound)
)
func apiError(resp http.ResponseWriter, err error) bool {
obj, ok := err.(*ApiError)
if !ok && err != nil {
logrus.Warn("Error did not include status code: ", err)
obj = &ApiError{err.Error(), http.StatusInternalServerError}
}
if obj == nil {
return false
}
data, err2 := json.Marshal(obj)
if err2 != nil {
logrus.Warn("Error json encoding error (╯°□°)╯︵ ┻━┻ ", err2)
}
resp.Header().Set("Content-Type", "application/json")
http.Error(resp, string(data), obj.StatusCode)
return true
}
type proxyToxics struct {
*Proxy
Toxics []toxics.Toxic `json:"toxics"`
}
func proxyWithToxics(proxy *Proxy) (result proxyToxics) {
result.Proxy = proxy
result.Toxics = proxy.Toxics.GetToxicArray()
return
}
func proxiesWithToxics(proxies []*Proxy) (result []proxyToxics) {
for _, proxy := range proxies {
result = append(result, proxyWithToxics(proxy))
}
return
}