如何在net/http中取消注册Handler?

huangapple go评论74阅读模式
英文:

How do I unregister a Handler in net/http?

问题

我正在编写一个Web服务器,在其中需要在运行时注册处理程序。例如,"/create"将为所有类似"/123/"的URL创建一个新的处理程序,等等。我需要一个相应的"/destroy/123",它将取消注册"/123/"的处理程序。

这是处理"/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)
}

// 在运行时创建MyHandler实例并将其注册为处理程序
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)
}

我尝试通过嵌入http.ServeMux来实现自己的多路复用器,但它将其模式到处理程序的映射保存在私有变量ServeMux.m中。

英文:

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

得分: 16

我要做的是创建一个自定义的ServerMux。从GOROOT/src/pkg/net/http/server.go中复制代码。它从第837行开始,到第939行结束。

自定义的ServerMux需要一个取消注册的方法。这个方法很容易实现。只需获取锁并删除映射条目。例如(所有代码未经测试):

// TODO: 检查是否已注册,如果没有则返回错误。
// TODO: 可能删除/dir和/dir/之间的自动永久链接。
func (mux *MyMux) Deregister(pattern string) error {
    mux.mu.Lock()
    defer mux.mu.Unlock()
    del(mux.m, pattern)
    return nil
}

要使用这个新的mux,你可以这样做:

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

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

通过从另一个goroutine调用deregister()来修改mux是完全安全的,并且会修改ListenAndServe()路由消息的方式。

英文:

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

得分: 9

看起来你已经接受了一个答案,但是我想提出一个替代的解决方案。

我对添加自定义muxer的必要性表示怀疑。在这个例子中,我使用了gorilla muxer,但这只是因为我熟悉它的模式匹配。理论上,你可以在不替换默认muxer的情况下匹配传入URL的模式。

我的代码将处理函数维护在一个映射中(字符串:处理程序名称=>函数字面量)... 这适用于使用默认muxer的HandleFunc方法。

示例输入/输出:

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"
)

// 封装服务器muxer、处理程序的动态映射和监听端口。
type Server struct {
    Dispatcher *mux.Router
    Urls       map[string]func(w http.ResponseWriter, r *http.Request)
    Port       string
}

// 愿信号永不停歇。
func main() {
    // 初始化服务器
    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() // 启动服务器;阻塞goroutine。
}

func (s *Server) Start() {

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

// 初始化Dispatcher的路由。
func (s *Server) InitDispatch() {
    d := s.Dispatcher

    // 将处理程序添加到服务器的映射中。
    d.HandleFunc("/register/{name}", func(w http.ResponseWriter, r *http.Request) {
        // 在某个地方创建要使用的处理程序;我只是创建了一个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) {
        // 在映射中查找处理程序并调用它,代理这个writer和请求
        vars := mux.Vars(r)
        name := vars["name"]

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

func (s *Server) Destroy(fName string) {
    s.Urls[fName] = nil // 移除处理程序
}

func (s *Server) ProxyCall(w http.ResponseWriter, r *http.Request, fName string) {
    if s.Urls[fName] != nil {
        s.Urls[fName](w, r) // 代理调用
    }
}

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 // 将处理程序添加到我们的映射中
}
英文:

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

得分: 0

也许可以通过注册一个返回空值的处理程序(不向ResponseWriter写入任何内容)或生成一个“未找到”类型的响应来“取消注册”。这取决于您的要求和/或取消注册先前注册的处理程序的目的/效果。

英文:

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.

huangapple
  • 本文由 发表于 2012年7月31日 18:25:01
  • 转载请务必保留本文链接:https://go.coder-hub.com/11738029.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定