How do I unregister a Handler in net/http?

2019-03-26 06:36发布

问题:

I am writing a web server wherein I need to register handlers at runtime. E.g. "/create" would create a new handler for all URLs like "/123/*" and so on. I need a corresponding "/destroy/123" which would unregister the handler for "/123/*".

Here's the code for handling "/create"

package main
import (
    "fmt"
    "net/http"
)

type MyHandler struct {
    id int
}
func (hf *MyHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
    fmt.Fprintln(w, r.URL.Path)
}

// Creates MyHandler instances and registers them as handlers at runtime
type HandlerFactory struct {
    handler_id int
}
func (hf *HandlerFactory) ServeHTTP(w http.ResponseWriter, r *http.Request) {
    hf.handler_id++
    handler := MyHandler{hf.handler_id}
    handle := fmt.Sprintf("/%d/", hf.handler_id)
    http.Handle(handle, &handler)
}

func main() {
    factory := HandlerFactory{0}
    http.Handle("/create", &factory)
    http.ListenAndServe("localhost:8080", nil)
}

I tried implementing my own multiplexer by embedding http.ServeMux but it holds its pattern-to-Handler mapping in a private variable (ServeMux.m)

回答1:

What I would do is create a custom ServerMux. Copy the code from GOROOT/src/pkg/net/http/server.go. It starts on line 837 and ends at 939.

The custom ServerMux would need a method for deregistration. This should be easy to implement. Just grab the lock and del() the map entry. For example (all code untested):

// TODO: check if registered and return error if not.
// TODO: possibly remove the automatic permanent link between /dir and /dir/.
func (mux *MyMux) Deregister(pattern string) error {
    mux.mu.Lock()
    defer mux.mu.Unlock()
    del(mux.m, pattern)
    return nil
}

In order to use this new mux, you would do something like this:

mux := newMux()
mux.Handle("/create", &factory)

srv := &http.Server {
    Addr: localhost:8080
    Handler: mux,
}
srv.ListenAndServe()

Modifying mux by calling deregister() from another goroutine is completely safe and will modify the way ListenAndServe() routes messages.



回答2:

It appears you've already accepted an answer, but I wanted to propose an alternate solution.

I question the need for adding a custom muxer. In this example I'm using the gorilla muxer, however that's only because I'm familiar with its pattern matching. In theory you could match the pattern from the incoming URL without the need for replacing the default muxer.

My code maintains the handler functions in a map (string: the handler name => function literal)... This is suitable for using the default muxers HandleFunc method.

Sample input/output:

GET /register/123

GET /123
hello from123.

GET /destroy/123

GET /123
[nothing]

package main

import (
    "code.google.com/p/gorilla/mux"
    "flag"
    "log"
    "net/http"
)

// Wraps server muxer, dynamic map of handlers, and listen port.
type Server struct {
    Dispatcher *mux.Router
    Urls       map[string]func(w http.ResponseWriter, r *http.Request)
    Port       string
}

// May the signal never stop.
func main() {
    //Initialize Server
    server := &Server{Port: "3000", Dispatcher: mux.NewRouter(), Urls: make(map[string]func(w http.ResponseWriter, r *http.Request))}

    var port = flag.String("port", "3000", "Default: 3000; Set the port for the web-server to accept incoming requests")
    flag.Parse()

    server.Port = *port
    log.Printf("Starting server on port: %s \n", server.Port)

    server.InitDispatch()
    log.Printf("Initializing request routes...\n")

    server.Start() //Launch server; blocks goroutine.
}

func (s *Server) Start() {

    http.ListenAndServe(":"+s.Port, s.Dispatcher)
}

// Initialize Dispatcher's routes.
func (s *Server) InitDispatch() {
    d := s.Dispatcher

    // Add handler to server's map.
    d.HandleFunc("/register/{name}", func(w http.ResponseWriter, r *http.Request) {
        //somewhere somehow you create the handler to be used; i'll just make an echohandler
        vars := mux.Vars(r)
        name := vars["name"]

        s.AddFunction(w, r, name)
    }).Methods("GET")

    d.HandleFunc("/destroy/{name}", func(w http.ResponseWriter, r *http.Request) {
        vars := mux.Vars(r)
        name := vars["name"]
        s.Destroy(name)
    }).Methods("GET")

    d.HandleFunc("/{name}", func(w http.ResponseWriter, r *http.Request) {
        //Lookup handler in map and call it, proxying this writer and request
        vars := mux.Vars(r)
        name := vars["name"]

        s.ProxyCall(w, r, name)
    }).Methods("GET")
}

func (s *Server) Destroy(fName string) {
    s.Urls[fName] = nil //remove handler
}

func (s *Server) ProxyCall(w http.ResponseWriter, r *http.Request, fName string) {
    if s.Urls[fName] != nil {
        s.Urls[fName](w, r) //proxy the call
    }
}

func (s *Server) AddFunction(w http.ResponseWriter, r *http.Request, fName string) {
    f := func(w http.ResponseWriter, r *http.Request) {
        w.Write([]byte("hello from" + fName))
    }

    s.Urls[fName] = f // Add the handler to our map
}


回答3:

Maybe the unregistering can be "done" by registering a handler returning nothing (not writing anything to the ResponseWriter) or generating a 'not found' kind of response. Depends on your requirements and/or purpose/effect of the unregistering of a previously registered handler.



标签: http go