Wrapper around mux to allow runtime route changes

We want to be able to update routes without restarting the http
server.  mutableRouter is a very thin wrapper around
github.com/gorilla/mux that enables safely switching to a new mux
router without pausing requests.
This commit is contained in:
Soam Vasani
2016-08-26 13:50:51 -07:00
parent a45ec1448c
commit ad961d4f4d
2 changed files with 161 additions and 0 deletions
+54
View File
@@ -0,0 +1,54 @@
/*
Copyright 2016 The Fission Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package router
import (
"net/http"
"sync/atomic"
"github.com/gorilla/mux"
"log"
)
//
// mutableRouter wraps the mux router, and allows the router to be
// atomically changed.
//
type mutableRouter struct {
router atomic.Value // mux.Router
}
func NewMutableRouter(handler *mux.Router) (*mutableRouter) {
mr := mutableRouter{};
mr.router.Store(handler)
return &mr
}
func (mr *mutableRouter) ServeHTTP(responseWriter http.ResponseWriter, request *http.Request) {
// Atomically grab the underlying mux router and call it.
routerValue := mr.router.Load()
router, ok := routerValue.(*mux.Router)
if (!ok) {
log.Panic("Invalid router type")
}
router.ServeHTTP(responseWriter, request)
}
func (mr *mutableRouter) UpdateRouter(newHandler *mux.Router) {
log.Print("Updating router")
mr.router.Store(newHandler)
}
+107
View File
@@ -0,0 +1,107 @@
/*
Copyright 2016 The Fission Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package router
import (
"testing"
"net/http"
"github.com/gorilla/mux"
"log"
"io/ioutil"
"time"
)
func OldHandler(responseWriter http.ResponseWriter, request *http.Request) {
responseWriter.Write([]byte("old handler"))
}
func NewHandler(responseWriter http.ResponseWriter, request *http.Request) {
responseWriter.Write([]byte("new handler"))
}
func verifyRequest(expectedResponse string) {
resp, err := http.Get("http://localhost:3333")
if (err != nil) {
log.Panic("failed make get request")
}
defer resp.Body.Close()
body, err := ioutil.ReadAll(resp.Body)
if (err != nil) {
log.Panic("failed to read response")
}
bodyStr := string(body)
log.Printf("Server responded with %v", bodyStr)
if (bodyStr != expectedResponse) {
log.Panic("Unexpected response")
}
}
func startServer(mr *mutableRouter) {
http.ListenAndServe(":3333", mr)
}
func spamServer() {
i := 0
for {
i = i + 1
resp, err := http.Get("http://localhost:3333")
if (err != nil) {
log.Panicf("failed make get request %v", i)
}
resp.Body.Close()
log.Printf("request count = %v", i)
}
}
func TestMutableMux(t *testing.T) {
// make a simple mutable router
log.Print("Create mutable router")
muxRouter := mux.NewRouter()
muxRouter.HandleFunc("/", OldHandler)
mr := NewMutableRouter(muxRouter)
// start http server
log.Print("Start http server")
go startServer(mr)
// continuously make requests, panic if any fails
go spamServer()
go spamServer()
go spamServer()
time.Sleep(5 * time.Millisecond)
// connect and verify old handler
log.Print("Verify old handler")
verifyRequest("old handler")
// change the muxer
log.Print("Change mux router")
newMuxRouter := mux.NewRouter()
newMuxRouter.HandleFunc("/", NewHandler)
mr.UpdateRouter(newMuxRouter)
// connect and verify the new handler
log.Print("Verify new handler")
verifyRequest("new handler")
time.Sleep(5 * time.Millisecond)
// all done
log.Print("ok")
}