Implement matching of hostnames and schemes
This commit is contained in:
parent
90c9f5da5b
commit
2d11edaa4c
@ -54,8 +54,8 @@ func main() {
|
|||||||
|
|
||||||
server := &gmi.Server{
|
server := &gmi.Server{
|
||||||
Certificate: cert,
|
Certificate: cert,
|
||||||
Handler: handler,
|
|
||||||
}
|
}
|
||||||
|
server.Handle("localhost", handler)
|
||||||
|
|
||||||
if err := server.ListenAndServe(); err != nil {
|
if err := server.ListenAndServe(); err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
|
@ -24,8 +24,8 @@ func main() {
|
|||||||
mux.Handle("/", gmi.FileServer(gmi.Dir("/var/www")))
|
mux.Handle("/", gmi.FileServer(gmi.Dir("/var/www")))
|
||||||
|
|
||||||
server := gmi.Server{
|
server := gmi.Server{
|
||||||
Handler: mux,
|
|
||||||
Certificate: cert,
|
Certificate: cert,
|
||||||
}
|
}
|
||||||
|
server.Handle("localhost", mux)
|
||||||
server.ListenAndServe()
|
server.ListenAndServe()
|
||||||
}
|
}
|
||||||
|
110
server.go
110
server.go
@ -34,9 +34,29 @@ type Server struct {
|
|||||||
// Using a self-signed certificate is recommended.
|
// Using a self-signed certificate is recommended.
|
||||||
Certificate tls.Certificate
|
Certificate tls.Certificate
|
||||||
|
|
||||||
// Handler specifies the Handler for requests.
|
// registered handlers
|
||||||
// If Handler is not set, the server will error.
|
handlers []handlerEntry
|
||||||
Handler Handler
|
}
|
||||||
|
|
||||||
|
// Handle registers a handler for the given host.
|
||||||
|
// A default scheme of gemini:// is assumed.
|
||||||
|
func (s *Server) Handle(host string, h Handler) {
|
||||||
|
s.HandleScheme("gemini", host, h)
|
||||||
|
}
|
||||||
|
|
||||||
|
// HandleScheme registers a handler for the given scheme and host.
|
||||||
|
func (s *Server) HandleScheme(scheme string, host string, h Handler) {
|
||||||
|
s.handlers = append(s.handlers, handlerEntry{
|
||||||
|
scheme,
|
||||||
|
host,
|
||||||
|
h,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
type handlerEntry struct {
|
||||||
|
scheme string
|
||||||
|
host string
|
||||||
|
h Handler
|
||||||
}
|
}
|
||||||
|
|
||||||
// ListenAndServe listens for requests at the server's configured address.
|
// ListenAndServe listens for requests at the server's configured address.
|
||||||
@ -189,12 +209,25 @@ func (s *Server) respond(conn net.Conn) {
|
|||||||
RemoteAddr: conn.RemoteAddr(),
|
RemoteAddr: conn.RemoteAddr(),
|
||||||
TLS: conn.(*tls.Conn).ConnectionState(),
|
TLS: conn.(*tls.Conn).ConnectionState(),
|
||||||
}
|
}
|
||||||
s.Handler.Serve(rw, req)
|
s.handler(req).Serve(rw, req)
|
||||||
}
|
}
|
||||||
rw.w.Flush()
|
rw.w.Flush()
|
||||||
conn.Close()
|
conn.Close()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *Server) handler(req *Request) Handler {
|
||||||
|
for _, e := range s.handlers {
|
||||||
|
if req.URL.Scheme != e.scheme {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
// Allow host or bare hostname
|
||||||
|
if req.URL.Host == e.host || req.Hostname() == e.host {
|
||||||
|
return e.h
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return NotFoundHandler()
|
||||||
|
}
|
||||||
|
|
||||||
// A Handler responds to a Gemini request.
|
// A Handler responds to a Gemini request.
|
||||||
type Handler interface {
|
type Handler interface {
|
||||||
// Serve accepts a Request and constructs a Response.
|
// Serve accepts a Request and constructs a Response.
|
||||||
@ -444,20 +477,13 @@ func isSlashRune(r rune) bool { return r == '/' || r == '\\' }
|
|||||||
// to redirect a request for "/images" to "/images/", unless "/images" has
|
// to redirect a request for "/images" to "/images/", unless "/images" has
|
||||||
// been registered separately.
|
// been registered separately.
|
||||||
//
|
//
|
||||||
// Patterns may optionally begin with a host name, restricting matches to
|
|
||||||
// URLs on that host only. Host-specific patterns take precedence over
|
|
||||||
// general patterns, so that a handler might register for the two patterns
|
|
||||||
// "/codesearch" and "codesearch.google.com/" without also taking over
|
|
||||||
// requests for "http://www.google.com/".
|
|
||||||
//
|
|
||||||
// ServeMux also takes care of sanitizing the URL request path and the Host
|
// ServeMux also takes care of sanitizing the URL request path and the Host
|
||||||
// header, stripping the port number and redirecting any request containing . or
|
// header, stripping the port number and redirecting any request containing . or
|
||||||
// .. elements or repeated slashes to an equivalent, cleaner URL.
|
// .. elements or repeated slashes to an equivalent, cleaner URL.
|
||||||
type ServeMux struct {
|
type ServeMux struct {
|
||||||
mu sync.RWMutex
|
mu sync.RWMutex
|
||||||
m map[string]muxEntry
|
m map[string]muxEntry
|
||||||
es []muxEntry // slice of entries sorted from longest to shortest.
|
es []muxEntry // slice of entries sorted from longest to shortest.
|
||||||
hosts bool // whether any patterns contain hostnames
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type muxEntry struct {
|
type muxEntry struct {
|
||||||
@ -526,9 +552,9 @@ func (mux *ServeMux) match(path string) (h Handler, pattern string) {
|
|||||||
// This occurs when a handler for path + "/" was already registered, but
|
// This occurs when a handler for path + "/" was already registered, but
|
||||||
// not for path itself. If the path needs appending to, it creates a new
|
// not for path itself. If the path needs appending to, it creates a new
|
||||||
// URL, setting the path to u.Path + "/" and returning true to indicate so.
|
// URL, setting the path to u.Path + "/" and returning true to indicate so.
|
||||||
func (mux *ServeMux) redirectToPathSlash(host, path string, u *url.URL) (*url.URL, bool) {
|
func (mux *ServeMux) redirectToPathSlash(path string, u *url.URL) (*url.URL, bool) {
|
||||||
mux.mu.RLock()
|
mux.mu.RLock()
|
||||||
shouldRedirect := mux.shouldRedirectRLocked(host, path)
|
shouldRedirect := mux.shouldRedirectRLocked(path)
|
||||||
mux.mu.RUnlock()
|
mux.mu.RUnlock()
|
||||||
if !shouldRedirect {
|
if !shouldRedirect {
|
||||||
return u, false
|
return u, false
|
||||||
@ -541,31 +567,25 @@ func (mux *ServeMux) redirectToPathSlash(host, path string, u *url.URL) (*url.UR
|
|||||||
// shouldRedirectRLocked reports whether the given path and host should be redirected to
|
// shouldRedirectRLocked reports whether the given path and host should be redirected to
|
||||||
// path+"/". This should happen if a handler is registered for path+"/" but
|
// path+"/". This should happen if a handler is registered for path+"/" but
|
||||||
// not path -- see comments at ServeMux.
|
// not path -- see comments at ServeMux.
|
||||||
func (mux *ServeMux) shouldRedirectRLocked(host, path string) bool {
|
func (mux *ServeMux) shouldRedirectRLocked(path string) bool {
|
||||||
p := []string{path, host + path}
|
if _, exist := mux.m[path]; exist {
|
||||||
|
return false
|
||||||
for _, c := range p {
|
|
||||||
if _, exist := mux.m[c]; exist {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
n := len(path)
|
n := len(path)
|
||||||
if n == 0 {
|
if n == 0 {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
for _, c := range p {
|
if _, exist := mux.m[path+"/"]; exist {
|
||||||
if _, exist := mux.m[c+"/"]; exist {
|
return path[n-1] != '/'
|
||||||
return path[n-1] != '/'
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handler returns the handler to use for the given request,
|
// Handler returns the handler to use for the given request.
|
||||||
// consulting r.Method, r.Host, and r.URL.Path. It always returns
|
// It consults r.URL.Path. It always returns a non-nil handler.
|
||||||
// a non-nil handler. If the path is not in its canonical form, the
|
// If the path is not in its canonical form, the
|
||||||
// handler will be an internally-generated handler that redirects
|
// handler will be an internally-generated handler that redirects
|
||||||
// to the canonical path. If the host contains a port, it is ignored
|
// to the canonical path. If the host contains a port, it is ignored
|
||||||
// when matching handlers.
|
// when matching handlers.
|
||||||
@ -575,47 +595,33 @@ func (mux *ServeMux) shouldRedirectRLocked(host, path string) bool {
|
|||||||
// the pattern that will match after following the redirect.
|
// the pattern that will match after following the redirect.
|
||||||
//
|
//
|
||||||
// If there is no registered handler that applies to the request,
|
// If there is no registered handler that applies to the request,
|
||||||
// Handler returns a ``page not found'' handler and an empty pattern.
|
// Handler returns a "not found" handler and an empty pattern.
|
||||||
func (mux *ServeMux) Handler(r *Request) (h Handler, pattern string) {
|
func (mux *ServeMux) Handler(r *Request) (h Handler, pattern string) {
|
||||||
// Refuse requests for non-gemini schemes.
|
|
||||||
if r.URL.Scheme != "gemini" {
|
|
||||||
return NotFoundHandler(), ""
|
|
||||||
}
|
|
||||||
|
|
||||||
// All other requests have any port stripped and path cleaned
|
|
||||||
// before passing to mux.handler.
|
|
||||||
host := stripHostPort(r.Host)
|
|
||||||
path := cleanPath(r.URL.Path)
|
path := cleanPath(r.URL.Path)
|
||||||
|
|
||||||
// If the given path is /tree and its handler is not registered,
|
// If the given path is /tree and its handler is not registered,
|
||||||
// redirect for /tree/.
|
// redirect for /tree/.
|
||||||
if u, ok := mux.redirectToPathSlash(host, path, r.URL); ok {
|
if u, ok := mux.redirectToPathSlash(path, r.URL); ok {
|
||||||
return RedirectHandler(u.String()), u.Path
|
return RedirectHandler(u.String()), u.Path
|
||||||
}
|
}
|
||||||
|
|
||||||
if path != r.URL.Path {
|
if path != r.URL.Path {
|
||||||
_, pattern = mux.handler(host, path)
|
_, pattern = mux.handler(path)
|
||||||
url := *r.URL
|
url := *r.URL
|
||||||
url.Path = path
|
url.Path = path
|
||||||
return RedirectHandler(url.String()), pattern
|
return RedirectHandler(url.String()), pattern
|
||||||
}
|
}
|
||||||
|
|
||||||
return mux.handler(host, r.URL.Path)
|
return mux.handler(r.URL.Path)
|
||||||
}
|
}
|
||||||
|
|
||||||
// handler is the main implementation of Handler.
|
// handler is the main implementation of Handler.
|
||||||
// The path is known to be in canonical form.
|
// The path is known to be in canonical form.
|
||||||
func (mux *ServeMux) handler(host, path string) (h Handler, pattern string) {
|
func (mux *ServeMux) handler(path string) (h Handler, pattern string) {
|
||||||
mux.mu.RLock()
|
mux.mu.RLock()
|
||||||
defer mux.mu.RUnlock()
|
defer mux.mu.RUnlock()
|
||||||
|
|
||||||
// Host-specific pattern takes precedence over generic ones
|
h, pattern = mux.match(path)
|
||||||
if mux.hosts {
|
|
||||||
h, pattern = mux.match(host + path)
|
|
||||||
}
|
|
||||||
if h == nil {
|
|
||||||
h, pattern = mux.match(path)
|
|
||||||
}
|
|
||||||
if h == nil {
|
if h == nil {
|
||||||
h, pattern = NotFoundHandler(), ""
|
h, pattern = NotFoundHandler(), ""
|
||||||
}
|
}
|
||||||
@ -653,10 +659,6 @@ func (mux *ServeMux) Handle(pattern string, handler Handler) {
|
|||||||
if pattern[len(pattern)-1] == '/' {
|
if pattern[len(pattern)-1] == '/' {
|
||||||
mux.es = appendSorted(mux.es, e)
|
mux.es = appendSorted(mux.es, e)
|
||||||
}
|
}
|
||||||
|
|
||||||
if pattern[0] != '/' {
|
|
||||||
mux.hosts = true
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func appendSorted(es []muxEntry, e muxEntry) []muxEntry {
|
func appendSorted(es []muxEntry, e muxEntry) []muxEntry {
|
||||||
|
Loading…
Reference in New Issue
Block a user