~emersion/public-inbox

This thread contains a patchset. You're looking at the original emails, but you may wish to use the patch review UI. Review patch
2 2

[PATCH kimchi v3] allow wildcard sites

Details
Message ID
<20201125003252.118411-1-slowjo@halmen.xyz>
DKIM signature
missing
Download raw message
Patch: +52 -2
closes: https://todo.sr.ht/~emersion/kimchi/14
---
This now works better. I'm unsure whether the NewServeMux function is
the best way to actually allocate a new Mux, or if it could be done more
easily as well. Also the naming of the new types could be improved.

 server.go | 54 ++++++++++++++++++++++++++++++++++++++++++++++++++++--
 1 file changed, 52 insertions(+), 2 deletions(-)

diff --git a/server.go b/server.go
index 18b8520..b169d83 100644
--- a/server.go
+++ b/server.go
@@ -7,6 +7,7 @@ import (
	"log"
	"net"
	"net/http"
	"strings"
	"sync"

	"github.com/pires/go-proxyproto"
@@ -65,10 +66,59 @@ func (srv *Server) AddListener(network, addr string) *Listener {
	return ln
}

type wildcardHandler map[string]http.Handler

func (w wildcardHandler) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
	reqHost, _, err := net.SplitHostPort(req.Host)
	if err != nil {
		http.NotFound(rw, req)
		return
	}
	reqPath := req.URL.EscapedPath()

	for pattern, handler := range w {
		i := strings.Index(pattern, "/")
		host, path := pattern[:i], pattern[i:]
		if strings.HasSuffix(reqHost, host) &&
			strings.HasPrefix(reqPath, path) {
			handler.ServeHTTP(rw, req)
			return
		}
	}
	http.NotFound(rw, req)
}

type wildServeMux struct {
	Mux *http.ServeMux
	Map wildcardHandler
}

func NewServeMux() *wildServeMux {
	m := new(wildServeMux)
	m.Mux = http.NewServeMux()
	m.Map = wildcardHandler{}
	return m
}

func (m wildServeMux) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
	m.Mux.ServeHTTP(rw, req)
}

func (m wildServeMux) Handle(pattern string, handler http.Handler) {
	if !strings.HasPrefix(pattern, "*") {
		m.Mux.Handle(pattern, handler)
	} else {
		if len(m.Map) == 0 {
			m.Mux.Handle("/", m.Map)
		}
		m.Map[pattern[1:]] = handler
	}
}

type Listener struct {
	Network  string
	Address  string
	Mux      *http.ServeMux
	Mux      *wildServeMux
	Insecure bool
	Server   *Server

@@ -92,7 +142,7 @@ func newListener(srv *Server, network, addr string) *Listener {
		},
	}
	ln.h2Server = &http2.Server{}
	ln.Mux = http.NewServeMux()
	ln.Mux = NewServeMux()
	return ln
}

-- 
2.29.2
Details
Message ID
<HVWCvI29qceYEMVsY_xZfBN9NGVnXb1bEFm3pPb6GzW6mTisRoELCqpDd8tVpfVRnEJuFM0u3h0dv-YFQ07sccy295i5Ji1XnYlgjF4MKjM=@emersion.fr>
In-Reply-To
<20201125003252.118411-1-slowjo@halmen.xyz> (view parent)
DKIM signature
fail
Download raw message
DKIM signature: fail
We want to properly handle fallbacks to more general patterns if we
don't find a handler. For instance with this config:

    foo.example.org/foo {
        file_server /srv/http/foo.example.org
    }
    *.example.org/bar {
        file_server /srv/http/fallback.example.org
    }
    :80/asdf {
        file_server /srv/http/fallback
    }

We'll want to route:

* http://foo.example.org/foo to the first handler
* http://foo.example.org/bar to the second one
* http://foo.example.org/asdf to the third one

I think that would be achievable with something like:

    type hostServeMux map[string]*http.ServeMux

And in the hostServeMux.ServeHTTP method, first try to find a handler
for the full host:

    if pathMux, ok := mux[req.Host]; ok {
        if h, pattern := pathMux.Handler(r); pattern != "" {
            h.ServeHTTP(w, r)
            return
        }
    }

Then repeat for a potential wildcard name, then repeat with an empty
host.
Details
Message ID
<C7HUII9H7YT4.1PM76URNBBK1Q@len>
In-Reply-To
<HVWCvI29qceYEMVsY_xZfBN9NGVnXb1bEFm3pPb6GzW6mTisRoELCqpDd8tVpfVRnEJuFM0u3h0dv-YFQ07sccy295i5Ji1XnYlgjF4MKjM=@emersion.fr> (view parent)
DKIM signature
missing
Download raw message
I haven't had the time to implement your proposed solution, wanted to do
that today, but I got stuck setting up working test-cases.

> foo.example.org/foo {
> file_server /srv/http/foo.example.org
> }
> *.example.org/bar {
> file_server /srv/http/fallback.example.org
> }
> :80/asdf {
> file_server /srv/http/fallback
> }
>
> We'll want to route:
>
> * http://foo.example.org/foo to the first handler
> * http://foo.example.org/bar to the second one
> * http://foo.example.org/asdf to the third one

If I understand this correctly, the two non-wildcard cases should work
in kimchi as it is today. However I could not get it to actually serve me
any files as soon as I added a path to the site configs.

I now found a fix* for this. With both that fix and this patch, the
routing actually already works as you propose.

* yet again, only tested with the file_server, I'm unsure how to test
the other directives correctly


The only open issue with this patch (apart from this solution's style,
and the chosen naming) would be if both a wildcard site and a fallback
site with path '/' were specified in the config file. This would end up
registering that path twice with the ServeMux. This causes a panic.

I'll fix this in the next version of this patch.


> type hostServeMux map[string]*http.ServeMux
>
> And in the hostServeMux.ServeHTTP method, first try to find a handler
> for the full host:
>
> if pathMux, ok := mux[req.Host]; ok {
> if h, pattern := pathMux.Handler(r); pattern != "" {
> h.ServeHTTP(w, r)
> return
> }
> }
>
> Then repeat for a potential wildcard name, then repeat with an empty
> host.

I'm sure this would work, but I imagine it would be more work than the
current implementation. The ServeMux basically is a (smart) string to
handler map. Turning this around to map a string to a mux just in order
for this mux to return the handler for the same string seems a bit
convoluted to me.
Also I'm unsure how the 'repeat for a potential wildcard name' could be
done efficiently. What I'm thinking of now is to split the hostname on
'.', remove the first bit, and try again, and again until there is no
hostname left. I wouldn't quite like this solution.
Reply to thread Export thread (mbox)