Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How do I unregister a Handler in net/http?

Tags:

http

go

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)

like image 246
code_martial Avatar asked Jul 31 '12 10:07

code_martial


People also ask

What is HTTP handler go?

Strictly speaking, what we mean by handler is an object which satisfies the http.Handler interface: type Handler interface { ServeHTTP(ResponseWriter, *Request) } In simple terms, this basically means that to be a handler an object must have a ServeHTTP() method with the exact signature: ServeHTTP(http.

What is ResponseWriter Golang?

type ResponseWriter interface { Header() Header Write([]byte) (int, error) WriteHeader(statusCode int) } The Golang net/http Handler interface has serveHTTP method that takes the Response Writer interface as input and this allows the Golang HTTP Server to construct HTTP Response.


2 Answers

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.

like image 107
Stephen Weinberg Avatar answered Nov 15 '22 20:11

Stephen Weinberg


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
}
like image 33
Robbie Avatar answered Nov 15 '22 20:11

Robbie



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!