Skip to content

Commit

Permalink
Add Pattern-based request multiplexer
Browse files Browse the repository at this point in the history
  • Loading branch information
yugui committed May 4, 2015
1 parent 0ad57f7 commit d8bcb11
Showing 1 changed file with 79 additions and 0 deletions.
79 changes: 79 additions & 0 deletions runtime/mux.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
package runtime

import (
"net/http"
"strings"

"github.com/golang/glog"
)

// A HandlerFunc handles a specific pair of path pattern and HTTP method.
type HandlerFunc func(w http.ResponseWriter, r *http.Request, pathParams map[string]string)

// ServeMux is a request multiplexer for grpc-gateway.
// It matches http requests to patterns and invokes the corresponding handler.
type ServeMux struct {
// handlers maps HTTP method to a list of handlers.
handlers map[string][]handler
}

// NewServeMux returns a new MuxHandler whose internal mapping is empty.
func NewServeMux() *ServeMux {
return &ServeMux{
handlers: make(map[string][]handler),
}
}

// Handle associates "h" to the pair of HTTP method and path pattern.
func (s *ServeMux) Handle(meth string, pat Pattern, h HandlerFunc) {
s.handlers[meth] = append(s.handlers[meth], handler{pat: pat, h: h})
}

// ServeHTTP dispatches the request to the first handler whose pattern matches to r.Method and r.Path.
func (s *ServeMux) ServeHTTP(w http.ResponseWriter, r *http.Request) {
path := r.URL.Path
if !strings.HasPrefix(path, "/") {
http.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest)
return
}

components := strings.Split(path[1:], "/")
l := len(components)
var verb string
if idx := strings.LastIndex(components[l-1], ":"); idx == 0 {
http.Error(w, http.StatusText(http.StatusNotFound), http.StatusNotFound)
return
} else if idx > 0 {
c := components[l-1]
verb, components[l-1] = c[:idx], c[idx+1:]
}

for _, h := range s.handlers[r.Method] {
pathParams, err := h.pat.Match(components, verb)
if err != nil {
glog.V(3).Infof("path mismatch: %q to %q", path, h.pat)
continue
}
h.h(w, r, pathParams)
return
}

// lookup other methods to determine if it is MethodNotAllowed
for m, handlers := range s.handlers {
if m == r.Method {
continue
}
for _, h := range handlers {
if _, err := h.pat.Match(components, verb); err == nil {
http.Error(w, http.StatusText(http.StatusMethodNotAllowed), http.StatusMethodNotAllowed)
return
}
}
}
http.Error(w, http.StatusText(http.StatusNotFound), http.StatusNotFound)
}

type handler struct {
pat Pattern
h HandlerFunc
}

3 comments on commit d8bcb11

@wild-endeavor
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@yugui - Hi, I'm using this code and I'm running into a weird issue where the code won't parse URLs with a : in the path. It's not a huge deal, I can just remove the colon from my path, but I'm curious, could you help me understand why we do this strings.LastIndex(components[l-1], ":")? Is this part of some protocol I'm not familiar with?

@johanbrandhorst
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, please see #224

@wild-endeavor
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Got it, thank you!

Please sign in to comment.