diff --git a/README.md b/README.md index 9d60786..9651c27 100644 --- a/README.md +++ b/README.md @@ -17,7 +17,7 @@ Let's clone this repository and execute the following commands. ```` zsh # docker build -$ docker build -t git-http-xfer . +$ docker build -t go-git-http-xfer . # test $ docker run --rm -v $PWD:/go/src/github.com/nulab/go-git-http-xfer go-git-http-xfer \ @@ -91,12 +91,14 @@ func main() { // You can add some custom route. ghx.Router.Add(githttpxfer.NewRoute( http.MethodGet, - func (path string) (match string) { + func (u *url.URL) *Match { suffix := "/hello" - if strings.HasSuffix(path, suffix) { - match = suffix + if !strings.HasSuffix(u.Path, suffix) { + return nil } - return + repoPath := strings.Replace(u.Path, suffix, "", 1) + filePath := strings.Replace(u.Path, repoPath+"/", "", 1) + return &Match{repoPath, filePath} }, func(ctx githttpxfer.Context) { resp, req := ctx.Response(), ctx.Request() diff --git a/addon/handler/archive/archive.go b/addon/handler/archive/archive.go index 918cbc4..d89d00e 100644 --- a/addon/handler/archive/archive.go +++ b/addon/handler/archive/archive.go @@ -7,16 +7,22 @@ import ( "regexp" "strings" + "net/url" + "github.com/nulab/go-git-http-xfer/githttpxfer" ) var ( archiveRegexp = regexp.MustCompile(".*?(/archive/.*?\\.(zip|tar))$") - Pattern = func(path string) (match string) { - if m := archiveRegexp.FindStringSubmatch(path); m != nil { - match = m[1] + Pattern = func(u *url.URL) *githttpxfer.Match { + m := archiveRegexp.FindStringSubmatch(u.Path) + if m == nil { + return nil } - return + suffix := m[1] + repoPath := strings.Replace(u.Path, suffix, "", 1) + filePath := strings.Replace(u.Path, repoPath+"/", "", 1) + return &githttpxfer.Match{repoPath, filePath} } Method = http.MethodGet ) diff --git a/example/main.go b/example/main.go index 30bbb00..e2bc062 100644 --- a/example/main.go +++ b/example/main.go @@ -10,6 +10,8 @@ import ( "strings" + "net/url" + "github.com/nulab/go-git-http-xfer/addon/handler/archive" "github.com/nulab/go-git-http-xfer/githttpxfer" ) @@ -41,12 +43,14 @@ func main() { // You can add some custom route. ghx.Router.Add(githttpxfer.NewRoute( http.MethodGet, - func(path string) (match string) { + func(u *url.URL) *githttpxfer.Match { suffix := "/hello" - if strings.HasSuffix(path, suffix) { - match = suffix + if !strings.HasSuffix(u.Path, suffix) { + return nil } - return + repoPath := strings.Replace(u.Path, suffix, "", 1) + filePath := strings.Replace(u.Path, repoPath+"/", "", 1) + return &githttpxfer.Match{repoPath, filePath} }, func(ctx githttpxfer.Context) { resp, req := ctx.Response(), ctx.Request() diff --git a/githttpxfer/githttpxfer.go b/githttpxfer/githttpxfer.go index d2d0a8e..ef2d665 100644 --- a/githttpxfer/githttpxfer.go +++ b/githttpxfer/githttpxfer.go @@ -5,72 +5,83 @@ import ( "fmt" "io" "net/http" + "net/url" "os" "regexp" "strings" ) var ( - serviceRPCUpload = func(path string) (match string) { - return hasSuffix(path, "/git-upload-pack") + serviceRPCUpload = func(u *url.URL) *Match { + return matchSuffix(u.Path, "/git-upload-pack") } - serviceRPCReceive = func(path string) (match string) { - return hasSuffix(path, "/git-receive-pack") + serviceRPCReceive = func(u *url.URL) *Match { + return matchSuffix(u.Path, "/git-receive-pack") } - getInfoRefs = func(path string) (match string) { - return hasSuffix(path, "/info/refs") + getInfoRefs = func(u *url.URL) *Match { + return matchSuffix(u.Path, "/info/refs") } - getHead = func(path string) (match string) { - return hasSuffix(path, "/HEAD") + getHead = func(u *url.URL) *Match { + return matchSuffix(u.Path, "/HEAD") } - getAlternates = func(path string) (match string) { - return hasSuffix(path, "/objects/info/alternates") + getAlternates = func(u *url.URL) *Match { + return matchSuffix(u.Path, "/objects/info/alternates") } - getHTTPAlternates = func(path string) (match string) { - return hasSuffix(path, "/objects/info/http-alternates") + getHTTPAlternates = func(u *url.URL) *Match { + return matchSuffix(u.Path, "/objects/info/http-alternates") } - getInfoPacks = func(path string) (match string) { - return hasSuffix(path, "/objects/info/packs") + getInfoPacks = func(u *url.URL) *Match { + return matchSuffix(u.Path, "/objects/info/packs") } getInfoFileRegexp = regexp.MustCompile(".*?(/objects/info/[^/]*)$") - getInfoFile = func(path string) (match string) { - return findStringSubmatch(path, getInfoFileRegexp) + getInfoFile = func(u *url.URL) *Match { + return findStringSubmatch(u.Path, getInfoFileRegexp) } getLooseObjectRegexp = regexp.MustCompile(".*?(/objects/[0-9a-f]{2}/[0-9a-f]{38})$") - getLooseObject = func(path string) (match string) { - return findStringSubmatch(path, getLooseObjectRegexp) + getLooseObject = func(u *url.URL) *Match { + return findStringSubmatch(u.Path, getLooseObjectRegexp) } getPackFileRegexp = regexp.MustCompile(".*?(/objects/pack/pack-[0-9a-f]{40}\\.pack)$") - getPackFile = func(path string) (match string) { - return findStringSubmatch(path, getPackFileRegexp) + getPackFile = func(u *url.URL) *Match { + return findStringSubmatch(u.Path, getPackFileRegexp) } getIdxFileRegexp = regexp.MustCompile(".*?(/objects/pack/pack-[0-9a-f]{40}\\.idx)$") - getIdxFile = func(path string) (match string) { - return findStringSubmatch(path, getIdxFileRegexp) + getIdxFile = func(u *url.URL) *Match { + return findStringSubmatch(u.Path, getIdxFileRegexp) } ) -func hasSuffix(path, suffix string) (match string) { - if strings.HasSuffix(path, suffix) { - match = suffix +type Match struct { + RepoPath, FilePath string +} + +func matchSuffix(path, suffix string) *Match { + if !strings.HasSuffix(path, suffix) { + return nil } - return + repoPath := strings.Replace(path, suffix, "", 1) + filePath := strings.Replace(path, repoPath+"/", "", 1) + return &Match{repoPath, filePath} } -func findStringSubmatch(path string, prefix *regexp.Regexp) (match string) { - if m := prefix.FindStringSubmatch(path); m != nil { - match = m[1] +func findStringSubmatch(path string, prefix *regexp.Regexp) *Match { + m := prefix.FindStringSubmatch(path) + if m == nil { + return nil } - return + suffix := m[1] + repoPath := strings.Replace(path, suffix, "", 1) + filePath := strings.Replace(path, repoPath+"/", "", 1) + return &Match{repoPath, filePath} } type options struct { @@ -150,7 +161,7 @@ func (ghx *GitHTTPXfer) SetLogger(logger Logger) { } func (ghx *GitHTTPXfer) ServeHTTP(rw http.ResponseWriter, r *http.Request) { - repoPath, filePath, handler, err := ghx.matchRouting(r.Method, r.URL.Path) + repoPath, filePath, handler, err := ghx.matchRouting(r.Method, r.URL) switch err.(type) { case *URLNotFoundError: RenderNotFound(rw) @@ -172,11 +183,12 @@ func (ghx *GitHTTPXfer) ServeHTTP(rw http.ResponseWriter, r *http.Request) { handler(ctx) } -func (ghx *GitHTTPXfer) matchRouting(method, path string) (repoPath string, filePath string, handler HandlerFunc, err error) { - match, route, err := ghx.Router.Match(method, path) +func (ghx *GitHTTPXfer) matchRouting(method string, u *url.URL) (repoPath string, filePath string, handler HandlerFunc, err error) { + match, route, err := ghx.Router.Match(method, u) + if err == nil { - repoPath = strings.Replace(path, match, "", 1) - filePath = strings.Replace(path, repoPath+"/", "", 1) + repoPath = match.RepoPath + filePath = match.FilePath handler = route.Handler } return @@ -292,7 +304,6 @@ func (ghx *GitHTTPXfer) serviceRPC(ctx Context, rpc string) { return } - if err = cmd.Wait(); err != nil { ghx.logger.Error("specified command fails to run or doesn't complete successfully. ", err.Error()) } diff --git a/githttpxfer/githttpxfer_test.go b/githttpxfer/githttpxfer_test.go index 95bbc98..ba77588 100644 --- a/githttpxfer/githttpxfer_test.go +++ b/githttpxfer/githttpxfer_test.go @@ -4,6 +4,7 @@ import ( "log" "net/http" "net/http/httptest" + "net/url" "os/exec" "testing" ) @@ -77,8 +78,10 @@ func Test_GitHTTPXfer_MatchRouting_should_not_match(t *testing.T) { return } m := http.MethodGet - p := "/base/foo/git-upload-pack" - _, _, _, err = ghx.matchRouting(m, p) + u := &url.URL{ + Path: "/base/foo/git-upload-pack", + } + _, _, _, err = ghx.matchRouting(m, u) if err == nil { t.Error("Allowed.") return @@ -99,70 +102,70 @@ func Test_GitHTTPXfer_MatchRouting_should_match(t *testing.T) { tests := []struct { description string method string - path string + u *url.URL expectedRepoPath string expectedFilePath string }{ { description: "it should match git-upload-pack", method: http.MethodPost, - path: "/base/foo/git-upload-pack", + u: &url.URL{Path: "/base/foo/git-upload-pack"}, expectedRepoPath: "/base/foo", expectedFilePath: "git-upload-pack", }, { description: "it should match get-info-refs", method: http.MethodGet, - path: "/base/foo/info/refs", + u: &url.URL{Path: "/base/foo/info/refs"}, expectedRepoPath: "/base/foo", expectedFilePath: "info/refs", }, { description: "it should match get-head", method: http.MethodGet, - path: "/base/foo/HEAD", + u: &url.URL{Path: "/base/foo/HEAD"}, expectedRepoPath: "/base/foo", expectedFilePath: "HEAD", }, { description: "it should match get-alternates", method: http.MethodGet, - path: "/base/foo/objects/info/alternates", + u: &url.URL{Path: "/base/foo/objects/info/alternates"}, expectedRepoPath: "/base/foo", expectedFilePath: "objects/info/alternates", }, { description: "it should match get-http-alternates", method: http.MethodGet, - path: "/base/foo/objects/info/http-alternates", + u: &url.URL{Path: "/base/foo/objects/info/http-alternates"}, expectedRepoPath: "/base/foo", expectedFilePath: "objects/info/http-alternates", }, { description: "it should match get-info-packs", method: http.MethodGet, - path: "/base/foo/objects/info/packs", + u: &url.URL{Path: "/base/foo/objects/info/packs"}, expectedRepoPath: "/base/foo", expectedFilePath: "objects/info/packs", }, { description: "it should match get-loose-object", method: http.MethodGet, - path: "/base/foo/objects/3b/aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaacccccc", + u: &url.URL{Path: "/base/foo/objects/3b/aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaacccccc"}, expectedRepoPath: "/base/foo", expectedFilePath: "objects/3b/aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaacccccc", }, { description: "it should match get-pack-file", method: http.MethodGet, - path: "/base/foo/objects/pack/pack-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbb.pack", + u: &url.URL{Path: "/base/foo/objects/pack/pack-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbb.pack"}, expectedRepoPath: "/base/foo", expectedFilePath: "objects/pack/pack-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbb.pack", }, { description: "it should match get-idx-file", method: http.MethodGet, - path: "/base/foo/objects/pack/pack-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbb.idx", + u: &url.URL{Path: "/base/foo/objects/pack/pack-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbb.idx"}, expectedRepoPath: "/base/foo", expectedFilePath: "objects/pack/pack-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbb.idx", }, @@ -170,7 +173,7 @@ func Test_GitHTTPXfer_MatchRouting_should_match(t *testing.T) { for _, tc := range tests { t.Log(tc.description) - repoPath, filePath, _, err := ghx.matchRouting(tc.method, tc.path) + repoPath, filePath, _, err := ghx.matchRouting(tc.method, tc.u) if err != nil { t.Errorf("error is %s", err.Error()) return diff --git a/githttpxfer/router.go b/githttpxfer/router.go index b7df27b..1b1ac6e 100644 --- a/githttpxfer/router.go +++ b/githttpxfer/router.go @@ -1,5 +1,7 @@ package githttpxfer +import "net/url" + type router struct { routes []*Route } @@ -11,13 +13,13 @@ func (r *router) Add(route *Route) { r.routes = append(r.routes, route) } -func (r *router) Match(method string, path string) (match string, route *Route, err error) { +func (r *router) Match(method string, u *url.URL) (match *Match, route *Route, err error) { for _, v := range r.routes { - if m := v.Pattern(path); m != "" { + if m := v.Pattern(u); m != nil { if v.Method != method { err = &MethodNotAllowedError{ Method: method, - Path: path, + Path: u.Path, } return } @@ -29,7 +31,7 @@ func (r *router) Match(method string, path string) (match string, route *Route, err = &URLNotFoundError{ Method: method, - Path: path, + Path: u.Path, } return } @@ -38,7 +40,7 @@ func newRouter() *router { return &router{routes: []*Route{}} } -type Pattern = func(path string) (match string) +type Pattern = func(u *url.URL) *Match type Route struct { Method string diff --git a/githttpxfer/router_test.go b/githttpxfer/router_test.go index b587ce8..587feb2 100644 --- a/githttpxfer/router_test.go +++ b/githttpxfer/router_test.go @@ -2,6 +2,7 @@ package githttpxfer import ( "net/http" + "net/url" "testing" ) @@ -9,15 +10,15 @@ func Test_Router_Append_should_append_route(t *testing.T) { router := &router{} router.Add(&Route{ http.MethodPost, - func(path string) (match string) { - return hasSuffix(path, "/foo") + func(u *url.URL) *Match { + return matchSuffix(u.Path, "/foo") }, func(ctx Context) {}, }) router.Add(&Route{ http.MethodPost, - func(path string) (match string) { - return hasSuffix(path, "/bar") + func(u *url.URL) *Match { + return matchSuffix(u.Path, "/bar") }, func(ctx Context) {}, }) @@ -32,20 +33,20 @@ func Test_Router_Match_should_match_route(t *testing.T) { router := &router{} router.Add(&Route{ http.MethodPost, - func(path string) (match string) { - return hasSuffix(path, "/foo") + func(u *url.URL) *Match { + return matchSuffix(u.Path, "/foo") }, func(ctx Context) {}, }) - match, route, err := router.Match(http.MethodPost, "/base/foo") + match, route, err := router.Match(http.MethodPost, &url.URL{Path: "/base/foo"}) if err != nil { t.Errorf("error is %s", err.Error()) } if http.MethodPost != route.Method { t.Errorf("http method is not %s . result: %s", http.MethodPost, route.Method) } - if "/foo" != match { - t.Errorf("match is not %s . result: %s", "/foo", match) + if "foo" != match.FilePath { + t.Errorf("match is not %s . result: %s", "foo", match.FilePath) } } @@ -53,16 +54,16 @@ func Test_Router_Match_should_return_UrlNotFound_error(t *testing.T) { router := &router{} router.Add(&Route{ http.MethodPost, - func(path string) (match string) { - return hasSuffix(path, "/foo") + func(u *url.URL) *Match { + return matchSuffix(u.Path, "/foo") }, func(ctx Context) {}, }) - match, route, err := router.Match(http.MethodPost, "/base/hoge") + match, route, err := router.Match(http.MethodPost, &url.URL{Path: "/base/hoge"}) if err == nil { t.Error("error is nil.") } - if match != "" { + if match != nil { t.Error("match is not empty.") } if route != nil { @@ -79,16 +80,16 @@ func Test_Router_Match_should_return_MethodNotAllowed_error(t *testing.T) { router := &router{} router.Add(&Route{ http.MethodPost, - func(path string) (match string) { - return hasSuffix(path, "/foo") + func(u *url.URL) *Match { + return matchSuffix(u.Path, "/foo") }, func(ctx Context) {}, }) - match, route, err := router.Match(http.MethodGet, "/base/foo") + match, route, err := router.Match(http.MethodGet, &url.URL{Path: "/base/foo"}) if err == nil { t.Error("error is nil.") } - if match != "" { + if match != nil { t.Error("match is not empty.") } if route != nil {