From 0427d1efa114eb70f951d9bbebdc32cccc750d00 Mon Sep 17 00:00:00 2001 From: verokarhu Date: Wed, 6 Aug 2014 00:14:04 +0300 Subject: [PATCH 01/24] beginnings of an http gateway/api --- cmd/ipfs/ipfs.go | 2 ++ cmd/ipfs/serve.go | 24 ++++++++++++++++++++++++ http/http.go | 36 ++++++++++++++++++++++++++++++++++++ 3 files changed, 62 insertions(+) create mode 100644 cmd/ipfs/serve.go create mode 100644 http/http.go diff --git a/cmd/ipfs/ipfs.go b/cmd/ipfs/ipfs.go index 8365e532a31..d4904bd2c5e 100644 --- a/cmd/ipfs/ipfs.go +++ b/cmd/ipfs/ipfs.go @@ -35,6 +35,7 @@ Tool commands: Advanced Commands: mount Mount an ipfs read-only mountpoint. + serve Serve an http gateway into ipfs. Use "ipfs help " for more information about a command. `, @@ -49,6 +50,7 @@ Use "ipfs help " for more information about a command. cmdIpfsCommands, cmdIpfsMount, cmdIpfsInit, + cmdIpfsServe, }, Flag: *flag.NewFlagSet("ipfs", flag.ExitOnError), } diff --git a/cmd/ipfs/serve.go b/cmd/ipfs/serve.go new file mode 100644 index 00000000000..367ec6e71cd --- /dev/null +++ b/cmd/ipfs/serve.go @@ -0,0 +1,24 @@ +package main + +import ( + "github.com/gonuts/flag" + "github.com/jbenet/commander" + h "github.com/jbenet/go-ipfs/http" +) + +var cmdIpfsServe = &commander.Command{ + UsageLine: "serve", + Short: "Serve an HTTP API", + Long: `ipfs serve - Serve an http gateway into ipfs.`, + Run: serveCmd, + Flag: *flag.NewFlagSet("ipfs-serve", flag.ExitOnError), +} + +func serveCmd(c *commander.Command, _ []string) error { + n, err := localNode() + if err != nil { + return err + } + + return h.Serve("127.0.0.1", 80, n) +} diff --git a/http/http.go b/http/http.go new file mode 100644 index 00000000000..9d10c4673d6 --- /dev/null +++ b/http/http.go @@ -0,0 +1,36 @@ +package http + +import ( + "fmt" + "github.com/gorilla/mux" + core "github.com/jbenet/go-ipfs/core" + "net/http" + "strconv" +) + +type ipfsHandler struct { + node *core.IpfsNode +} + +func (i *ipfsHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { + path := r.URL.Path + + nd, err := i.node.Resolver.ResolvePath(path) + if err != nil { + w.WriteHeader(http.StatusInternalServerError) + return + } + + fmt.Fprintf(w, "%s", nd.Data) +} + +func Serve(host string, port uint16, node *core.IpfsNode) error { + r := mux.NewRouter() + r.PathPrefix("/").Handler(&ipfsHandler{node}).Methods("GET") + http.Handle("/", r) + + address := host + ":" + strconv.FormatUint(uint64(port), 10) + fmt.Printf("Serving on %s\n", address) + + return http.ListenAndServe(address, nil) +} From bb924cab5bd87a104bf24f73c3692cd8c6b37766 Mon Sep 17 00:00:00 2001 From: verokarhu Date: Wed, 6 Aug 2014 18:19:09 +0300 Subject: [PATCH 02/24] add port flag to serve --- cmd/ipfs/serve.go | 12 +++++++++++- http/http.go | 2 +- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/cmd/ipfs/serve.go b/cmd/ipfs/serve.go index 367ec6e71cd..fe094ffae91 100644 --- a/cmd/ipfs/serve.go +++ b/cmd/ipfs/serve.go @@ -1,6 +1,7 @@ package main import ( + "errors" "github.com/gonuts/flag" "github.com/jbenet/commander" h "github.com/jbenet/go-ipfs/http" @@ -14,11 +15,20 @@ var cmdIpfsServe = &commander.Command{ Flag: *flag.NewFlagSet("ipfs-serve", flag.ExitOnError), } +func init() { + cmdIpfsServe.Flag.Uint("port", 80, "Port number") +} + func serveCmd(c *commander.Command, _ []string) error { + port := c.Flag.Lookup("port").Value.Get().(uint) + if port < 1 || port > 65535 { + return errors.New("invalid port number") + } + n, err := localNode() if err != nil { return err } - return h.Serve("127.0.0.1", 80, n) + return h.Serve("127.0.0.1", port, n) } diff --git a/http/http.go b/http/http.go index 9d10c4673d6..5a235cb0cb8 100644 --- a/http/http.go +++ b/http/http.go @@ -24,7 +24,7 @@ func (i *ipfsHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { fmt.Fprintf(w, "%s", nd.Data) } -func Serve(host string, port uint16, node *core.IpfsNode) error { +func Serve(host string, port uint, node *core.IpfsNode) error { r := mux.NewRouter() r.PathPrefix("/").Handler(&ipfsHandler{node}).Methods("GET") http.Handle("/", r) From 8831427c82eacb86cb8c4093697529f407965f2d Mon Sep 17 00:00:00 2001 From: verokarhu Date: Wed, 6 Aug 2014 20:03:45 +0300 Subject: [PATCH 03/24] rudimentary HTTP POST support --- cmd/ipfs/serve.go | 7 ++++++- http/http.go | 34 ++++++++++++++++++++++++++-------- 2 files changed, 32 insertions(+), 9 deletions(-) diff --git a/cmd/ipfs/serve.go b/cmd/ipfs/serve.go index fe094ffae91..c80a4fd86f4 100644 --- a/cmd/ipfs/serve.go +++ b/cmd/ipfs/serve.go @@ -2,9 +2,11 @@ package main import ( "errors" + "fmt" "github.com/gonuts/flag" "github.com/jbenet/commander" h "github.com/jbenet/go-ipfs/http" + "strconv" ) var cmdIpfsServe = &commander.Command{ @@ -30,5 +32,8 @@ func serveCmd(c *commander.Command, _ []string) error { return err } - return h.Serve("127.0.0.1", port, n) + address := "127.0.0.1" + ":" + strconv.FormatUint(uint64(port), 10) + fmt.Printf("Serving on %s\n", address) + + return h.Serve(address, n) } diff --git a/http/http.go b/http/http.go index 5a235cb0cb8..e983e074c82 100644 --- a/http/http.go +++ b/http/http.go @@ -4,14 +4,24 @@ import ( "fmt" "github.com/gorilla/mux" core "github.com/jbenet/go-ipfs/core" + "github.com/jbenet/go-ipfs/importer" + mh "github.com/jbenet/go-multihash" "net/http" - "strconv" ) type ipfsHandler struct { node *core.IpfsNode } +func Serve(address string, node *core.IpfsNode) error { + r := mux.NewRouter() + r.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { ipfsPostHandler(w, r, node) }).Methods("POST") + r.PathPrefix("/").Handler(&ipfsHandler{node}).Methods("GET") + http.Handle("/", r) + + return http.ListenAndServe(address, nil) +} + func (i *ipfsHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { path := r.URL.Path @@ -24,13 +34,21 @@ func (i *ipfsHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { fmt.Fprintf(w, "%s", nd.Data) } -func Serve(host string, port uint, node *core.IpfsNode) error { - r := mux.NewRouter() - r.PathPrefix("/").Handler(&ipfsHandler{node}).Methods("GET") - http.Handle("/", r) +func ipfsPostHandler(w http.ResponseWriter, r *http.Request, node *core.IpfsNode) { - address := host + ":" + strconv.FormatUint(uint64(port), 10) - fmt.Printf("Serving on %s\n", address) + root, err := importer.NewDagFromReader(r.Body, 1) + if err != nil { + w.WriteHeader(http.StatusInternalServerError) + return + } - return http.ListenAndServe(address, nil) + k, err := node.DAG.Put(root) + if err != nil { + w.WriteHeader(http.StatusInternalServerError) + return + } + + //TODO: return json representation of list instead + w.WriteHeader(http.StatusCreated) + fmt.Fprintf(w, "%s", mh.Multihash(k).B58String()) } From e4941069effd36eeef39155bf6b43e697cbac290 Mon Sep 17 00:00:00 2001 From: verokarhu Date: Wed, 6 Aug 2014 20:10:24 +0300 Subject: [PATCH 04/24] hostname flag to serve command --- cmd/ipfs/serve.go | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/cmd/ipfs/serve.go b/cmd/ipfs/serve.go index c80a4fd86f4..4815f9e6a30 100644 --- a/cmd/ipfs/serve.go +++ b/cmd/ipfs/serve.go @@ -19,6 +19,7 @@ var cmdIpfsServe = &commander.Command{ func init() { cmdIpfsServe.Flag.Uint("port", 80, "Port number") + cmdIpfsServe.Flag.String("hostname", "localhost", "Hostname") } func serveCmd(c *commander.Command, _ []string) error { @@ -27,12 +28,14 @@ func serveCmd(c *commander.Command, _ []string) error { return errors.New("invalid port number") } + hostname := c.Flag.Lookup("hostname").Value.Get().(string) + n, err := localNode() if err != nil { return err } - address := "127.0.0.1" + ":" + strconv.FormatUint(uint64(port), 10) + address := hostname + ":" + strconv.FormatUint(uint64(port), 10) fmt.Printf("Serving on %s\n", address) return h.Serve(address, n) From 6338cac23ef4ccdba25800e166fff00f3ff4ab34 Mon Sep 17 00:00:00 2001 From: verokarhu Date: Wed, 6 Aug 2014 20:17:21 +0300 Subject: [PATCH 05/24] added comment to exported function --- http/http.go | 1 + 1 file changed, 1 insertion(+) diff --git a/http/http.go b/http/http.go index e983e074c82..bfb9dee15cb 100644 --- a/http/http.go +++ b/http/http.go @@ -13,6 +13,7 @@ type ipfsHandler struct { node *core.IpfsNode } +// Serve starts the http server func Serve(address string, node *core.IpfsNode) error { r := mux.NewRouter() r.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { ipfsPostHandler(w, r, node) }).Methods("POST") From 356ee0383fdb7b33a6aa9e4159d61263a0ae677b Mon Sep 17 00:00:00 2001 From: verokarhu Date: Wed, 6 Aug 2014 20:19:54 +0300 Subject: [PATCH 06/24] add serve to readme.md --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index c9f9f14fbfa..c03bcbf05aa 100644 --- a/README.md +++ b/README.md @@ -43,6 +43,7 @@ Tool commands: Advanced Commands: mount Mount an ipfs read-only mountpoint. + serve Serve an http gateway into ipfs. Use "ipfs help " for more information about a command. ``` From a82f814b0674f0bff889c07a98da1b6a0b00bfc9 Mon Sep 17 00:00:00 2001 From: verokarhu Date: Wed, 6 Aug 2014 20:31:01 +0300 Subject: [PATCH 07/24] get rid of fmt dependency in http --- http/http.go | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/http/http.go b/http/http.go index bfb9dee15cb..330c4503815 100644 --- a/http/http.go +++ b/http/http.go @@ -1,7 +1,6 @@ package http import ( - "fmt" "github.com/gorilla/mux" core "github.com/jbenet/go-ipfs/core" "github.com/jbenet/go-ipfs/importer" @@ -32,11 +31,10 @@ func (i *ipfsHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { return } - fmt.Fprintf(w, "%s", nd.Data) + w.Write(nd.Data) } func ipfsPostHandler(w http.ResponseWriter, r *http.Request, node *core.IpfsNode) { - root, err := importer.NewDagFromReader(r.Body, 1) if err != nil { w.WriteHeader(http.StatusInternalServerError) @@ -51,5 +49,5 @@ func ipfsPostHandler(w http.ResponseWriter, r *http.Request, node *core.IpfsNode //TODO: return json representation of list instead w.WriteHeader(http.StatusCreated) - fmt.Fprintf(w, "%s", mh.Multihash(k).B58String()) + w.Write([]byte(mh.Multihash(k).B58String())) } From 866ea4286bbe56346057fcb291b68ae152728980 Mon Sep 17 00:00:00 2001 From: verokarhu Date: Wed, 6 Aug 2014 20:42:51 +0300 Subject: [PATCH 08/24] add todo --- http/http.go | 1 + 1 file changed, 1 insertion(+) diff --git a/http/http.go b/http/http.go index 330c4503815..74f6b762444 100644 --- a/http/http.go +++ b/http/http.go @@ -31,6 +31,7 @@ func (i *ipfsHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { return } + // TODO: return json object containing the tree data if it's a folder w.Write(nd.Data) } From 0a25b64f0a139df629a6bfabe7adc39fd6d054a2 Mon Sep 17 00:00:00 2001 From: verokarhu Date: Thu, 7 Aug 2014 08:32:08 +0300 Subject: [PATCH 09/24] implemented suggestions by whyrusleeping --- cmd/ipfs/serve.go | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/cmd/ipfs/serve.go b/cmd/ipfs/serve.go index 4815f9e6a30..85d3cc7a470 100644 --- a/cmd/ipfs/serve.go +++ b/cmd/ipfs/serve.go @@ -6,7 +6,6 @@ import ( "github.com/gonuts/flag" "github.com/jbenet/commander" h "github.com/jbenet/go-ipfs/http" - "strconv" ) var cmdIpfsServe = &commander.Command{ @@ -18,7 +17,7 @@ var cmdIpfsServe = &commander.Command{ } func init() { - cmdIpfsServe.Flag.Uint("port", 80, "Port number") + cmdIpfsServe.Flag.Uint("port", 8080, "Port number") cmdIpfsServe.Flag.String("hostname", "localhost", "Hostname") } @@ -35,7 +34,7 @@ func serveCmd(c *commander.Command, _ []string) error { return err } - address := hostname + ":" + strconv.FormatUint(uint64(port), 10) + address := fmt.Sprintf("%s:%d", hostname, port) fmt.Printf("Serving on %s\n", address) return h.Serve(address, n) From 8eb95faf504de93ab3ef98891342bd205adf46f8 Mon Sep 17 00:00:00 2001 From: verokarhu Date: Thu, 7 Aug 2014 19:32:01 +0300 Subject: [PATCH 10/24] moved http to server/http and implemented serve http subcommand --- README.md | 2 +- cmd/ipfs/ipfs.go | 2 +- cmd/ipfs/serve.go | 23 ++++++++++++++++------- {http => server/http}/http.go | 0 4 files changed, 18 insertions(+), 9 deletions(-) rename {http => server/http}/http.go (100%) diff --git a/README.md b/README.md index c03bcbf05aa..075566701a7 100644 --- a/README.md +++ b/README.md @@ -43,7 +43,7 @@ Tool commands: Advanced Commands: mount Mount an ipfs read-only mountpoint. - serve Serve an http gateway into ipfs. + serve Serve an interface to ipfs. Use "ipfs help " for more information about a command. ``` diff --git a/cmd/ipfs/ipfs.go b/cmd/ipfs/ipfs.go index d4904bd2c5e..88dbacce5bb 100644 --- a/cmd/ipfs/ipfs.go +++ b/cmd/ipfs/ipfs.go @@ -35,7 +35,7 @@ Tool commands: Advanced Commands: mount Mount an ipfs read-only mountpoint. - serve Serve an http gateway into ipfs. + serve Serve an interface to ipfs. Use "ipfs help " for more information about a command. `, diff --git a/cmd/ipfs/serve.go b/cmd/ipfs/serve.go index 85d3cc7a470..af6d1fb416c 100644 --- a/cmd/ipfs/serve.go +++ b/cmd/ipfs/serve.go @@ -5,23 +5,32 @@ import ( "fmt" "github.com/gonuts/flag" "github.com/jbenet/commander" - h "github.com/jbenet/go-ipfs/http" + h "github.com/jbenet/go-ipfs/server/http" ) var cmdIpfsServe = &commander.Command{ UsageLine: "serve", + Short: "Serve an interface to ipfs", + Subcommands: []*commander.Command{ + cmdIpfsServeHttp, + }, + Flag: *flag.NewFlagSet("ipfs-serve", flag.ExitOnError), +} + +var cmdIpfsServeHttp = &commander.Command{ + UsageLine: "http", Short: "Serve an HTTP API", - Long: `ipfs serve - Serve an http gateway into ipfs.`, - Run: serveCmd, - Flag: *flag.NewFlagSet("ipfs-serve", flag.ExitOnError), + Long: `ipfs serve http - Serve an http gateway into ipfs.`, + Run: serveHttpCmd, + Flag: *flag.NewFlagSet("ipfs-serve-http", flag.ExitOnError), } func init() { - cmdIpfsServe.Flag.Uint("port", 8080, "Port number") - cmdIpfsServe.Flag.String("hostname", "localhost", "Hostname") + cmdIpfsServeHttp.Flag.Uint("port", 8080, "Port number") + cmdIpfsServeHttp.Flag.String("hostname", "localhost", "Hostname") } -func serveCmd(c *commander.Command, _ []string) error { +func serveHttpCmd(c *commander.Command, _ []string) error { port := c.Flag.Lookup("port").Value.Get().(uint) if port < 1 || port > 65535 { return errors.New("invalid port number") diff --git a/http/http.go b/server/http/http.go similarity index 100% rename from http/http.go rename to server/http/http.go From d894d6382ffe0a62d99c8e338240d345c9944c51 Mon Sep 17 00:00:00 2001 From: verokarhu Date: Tue, 16 Sep 2014 20:05:48 +0300 Subject: [PATCH 11/24] daemon listener to http serve code --- cmd/ipfs/serve.go | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/cmd/ipfs/serve.go b/cmd/ipfs/serve.go index af6d1fb416c..0d1fb1d3bbd 100644 --- a/cmd/ipfs/serve.go +++ b/cmd/ipfs/serve.go @@ -3,8 +3,10 @@ package main import ( "errors" "fmt" - "github.com/gonuts/flag" - "github.com/jbenet/commander" + + "github.com/jbenet/go-ipfs/Godeps/_workspace/src/github.com/gonuts/flag" + "github.com/jbenet/go-ipfs/Godeps/_workspace/src/github.com/jbenet/commander" + "github.com/jbenet/go-ipfs/daemon" h "github.com/jbenet/go-ipfs/server/http" ) @@ -36,13 +38,20 @@ func serveHttpCmd(c *commander.Command, _ []string) error { return errors.New("invalid port number") } - hostname := c.Flag.Lookup("hostname").Value.Get().(string) + n, err := localNode(true) + if err != nil { + return err + } - n, err := localNode() + fmt.Println("starting new daemon listener...") + dl, err := daemon.NewDaemonListener(n, "localhost:12345") if err != nil { return err } + go dl.Listen() + defer dl.Close() + hostname := c.Flag.Lookup("hostname").Value.Get().(string) address := fmt.Sprintf("%s:%d", hostname, port) fmt.Printf("Serving on %s\n", address) From c4d55c7bb741bd8143878c07cc2a423a57dd6747 Mon Sep 17 00:00:00 2001 From: verokarhu Date: Tue, 16 Sep 2014 20:41:13 +0300 Subject: [PATCH 12/24] fix compile errors --- server/http/http.go | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/server/http/http.go b/server/http/http.go index 74f6b762444..9ba2b3a6014 100644 --- a/server/http/http.go +++ b/server/http/http.go @@ -1,11 +1,12 @@ package http import ( + "net/http" + "github.com/gorilla/mux" core "github.com/jbenet/go-ipfs/core" "github.com/jbenet/go-ipfs/importer" mh "github.com/jbenet/go-multihash" - "net/http" ) type ipfsHandler struct { @@ -36,13 +37,13 @@ func (i *ipfsHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { } func ipfsPostHandler(w http.ResponseWriter, r *http.Request, node *core.IpfsNode) { - root, err := importer.NewDagFromReader(r.Body, 1) + root, err := importer.NewDagFromReader(r.Body) if err != nil { w.WriteHeader(http.StatusInternalServerError) return } - k, err := node.DAG.Put(root) + k, err := node.DAG.Add(root) if err != nil { w.WriteHeader(http.StatusInternalServerError) return From 436f14b45d0f3b59e7e0c7fd145f3c7240e3f24b Mon Sep 17 00:00:00 2001 From: verokarhu Date: Tue, 16 Sep 2014 23:56:36 +0300 Subject: [PATCH 13/24] test for serveHTTP --- server/http/http.go | 9 +++++-- server/http/http_test.go | 51 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 58 insertions(+), 2 deletions(-) create mode 100644 server/http/http_test.go diff --git a/server/http/http.go b/server/http/http.go index 9ba2b3a6014..c8061b19505 100644 --- a/server/http/http.go +++ b/server/http/http.go @@ -4,9 +4,10 @@ import ( "net/http" "github.com/gorilla/mux" + mh "github.com/jbenet/go-ipfs/Godeps/_workspace/src/github.com/jbenet/go-multihash" core "github.com/jbenet/go-ipfs/core" "github.com/jbenet/go-ipfs/importer" - mh "github.com/jbenet/go-multihash" + merkledag "github.com/jbenet/go-ipfs/merkledag" ) type ipfsHandler struct { @@ -26,7 +27,7 @@ func Serve(address string, node *core.IpfsNode) error { func (i *ipfsHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { path := r.URL.Path - nd, err := i.node.Resolver.ResolvePath(path) + nd, err := resolvePath(path, i.node) if err != nil { w.WriteHeader(http.StatusInternalServerError) return @@ -53,3 +54,7 @@ func ipfsPostHandler(w http.ResponseWriter, r *http.Request, node *core.IpfsNode w.WriteHeader(http.StatusCreated) w.Write([]byte(mh.Multihash(k).B58String())) } + +var resolvePath = func(path string, node *core.IpfsNode) (*merkledag.Node, error) { + return node.Resolver.ResolvePath(path) +} diff --git a/server/http/http_test.go b/server/http/http_test.go new file mode 100644 index 00000000000..83f8db0d12a --- /dev/null +++ b/server/http/http_test.go @@ -0,0 +1,51 @@ +package http + +import ( + "errors" + "net/http" + "net/http/httptest" + "testing" + + core "github.com/jbenet/go-ipfs/core" + merkledag "github.com/jbenet/go-ipfs/merkledag" +) + +type getTest struct { + url string + code int + body string +} + +func setup() { + resolvePath = func(path string, node *core.IpfsNode) (*merkledag.Node, error) { + if path == "/QmUxtEgtan9M7acwc8SXF3MGpgpD9Ya8ViLNGEXQ6n9vfA" { + return &merkledag.Node{Data: []byte("some fine data")}, nil + } + + return nil, errors.New("") + } +} + +func TestServeHTTP(t *testing.T) { + setup() + testhandler := &ipfsHandler{} + tests := []getTest{ + {"/", http.StatusInternalServerError, ""}, + {"/QmUxtEgtan9M7acwc8SXF3MGpgpD9Ya8ViLNGEXQ6n9vfA", http.StatusOK, "some fine data"}, + } + + for _, test := range tests { + req, _ := http.NewRequest("GET", test.url, nil) + resp := httptest.NewRecorder() + testhandler.ServeHTTP(resp, req) + + if resp.Code != test.code { + t.Error("expected status code", test.code, "received", resp.Code) + } + + if resp.Body.String() != test.body { + t.Error("expected body:", test.body) + t.Error("received body:", resp.Body) + } + } +} From 65f908768966629b29f19e27275f611b4555fc8e Mon Sep 17 00:00:00 2001 From: verokarhu Date: Wed, 17 Sep 2014 01:43:09 +0300 Subject: [PATCH 14/24] rewrote test to use separate test interface --- server/http/http.go | 22 +++++++++++++++------- server/http/http_test.go | 16 +++++++--------- 2 files changed, 22 insertions(+), 16 deletions(-) diff --git a/server/http/http.go b/server/http/http.go index c8061b19505..80391d8789f 100644 --- a/server/http/http.go +++ b/server/http/http.go @@ -10,6 +10,14 @@ import ( merkledag "github.com/jbenet/go-ipfs/merkledag" ) +type ipfs interface { + ResolvePath(string) (*merkledag.Node, error) +} + +type handler struct { + ipfs +} + type ipfsHandler struct { node *core.IpfsNode } @@ -17,17 +25,17 @@ type ipfsHandler struct { // Serve starts the http server func Serve(address string, node *core.IpfsNode) error { r := mux.NewRouter() - r.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { ipfsPostHandler(w, r, node) }).Methods("POST") - r.PathPrefix("/").Handler(&ipfsHandler{node}).Methods("GET") + r.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { postHandler(w, r, node) }).Methods("POST") + r.PathPrefix("/").Handler(&handler{&ipfsHandler{node}}).Methods("GET") http.Handle("/", r) return http.ListenAndServe(address, nil) } -func (i *ipfsHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { +func (i *handler) ServeHTTP(w http.ResponseWriter, r *http.Request) { path := r.URL.Path - nd, err := resolvePath(path, i.node) + nd, err := i.ResolvePath(path) if err != nil { w.WriteHeader(http.StatusInternalServerError) return @@ -37,7 +45,7 @@ func (i *ipfsHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { w.Write(nd.Data) } -func ipfsPostHandler(w http.ResponseWriter, r *http.Request, node *core.IpfsNode) { +func postHandler(w http.ResponseWriter, r *http.Request, node *core.IpfsNode) { root, err := importer.NewDagFromReader(r.Body) if err != nil { w.WriteHeader(http.StatusInternalServerError) @@ -55,6 +63,6 @@ func ipfsPostHandler(w http.ResponseWriter, r *http.Request, node *core.IpfsNode w.Write([]byte(mh.Multihash(k).B58String())) } -var resolvePath = func(path string, node *core.IpfsNode) (*merkledag.Node, error) { - return node.Resolver.ResolvePath(path) +func (i *ipfsHandler) ResolvePath(path string) (*merkledag.Node, error) { + return i.node.Resolver.ResolvePath(path) } diff --git a/server/http/http_test.go b/server/http/http_test.go index 83f8db0d12a..198147087c4 100644 --- a/server/http/http_test.go +++ b/server/http/http_test.go @@ -6,7 +6,6 @@ import ( "net/http/httptest" "testing" - core "github.com/jbenet/go-ipfs/core" merkledag "github.com/jbenet/go-ipfs/merkledag" ) @@ -16,19 +15,18 @@ type getTest struct { body string } -func setup() { - resolvePath = func(path string, node *core.IpfsNode) (*merkledag.Node, error) { - if path == "/QmUxtEgtan9M7acwc8SXF3MGpgpD9Ya8ViLNGEXQ6n9vfA" { - return &merkledag.Node{Data: []byte("some fine data")}, nil - } +type testIpfsHandler struct{} - return nil, errors.New("") +func (i *testIpfsHandler) ResolvePath(path string) (*merkledag.Node, error) { + if path == "/QmUxtEgtan9M7acwc8SXF3MGpgpD9Ya8ViLNGEXQ6n9vfA" { + return &merkledag.Node{Data: []byte("some fine data")}, nil } + + return nil, errors.New("") } func TestServeHTTP(t *testing.T) { - setup() - testhandler := &ipfsHandler{} + testhandler := &handler{&testIpfsHandler{}} tests := []getTest{ {"/", http.StatusInternalServerError, ""}, {"/QmUxtEgtan9M7acwc8SXF3MGpgpD9Ya8ViLNGEXQ6n9vfA", http.StatusOK, "some fine data"}, From f320023c7b1dfdf5f8c3bc0b15426ac83a669a30 Mon Sep 17 00:00:00 2001 From: verokarhu Date: Wed, 17 Sep 2014 21:16:54 +0300 Subject: [PATCH 15/24] test for posthandler --- server/http/http.go | 28 +++++--------- server/http/http_test.go | 83 +++++++++++++++++++++++++++++++--------- server/http/ipfs.go | 32 ++++++++++++++++ 3 files changed, 105 insertions(+), 38 deletions(-) create mode 100644 server/http/ipfs.go diff --git a/server/http/http.go b/server/http/http.go index 80391d8789f..aa0c5a4d673 100644 --- a/server/http/http.go +++ b/server/http/http.go @@ -1,32 +1,24 @@ package http import ( + "fmt" "net/http" "github.com/gorilla/mux" mh "github.com/jbenet/go-ipfs/Godeps/_workspace/src/github.com/jbenet/go-multihash" core "github.com/jbenet/go-ipfs/core" - "github.com/jbenet/go-ipfs/importer" - merkledag "github.com/jbenet/go-ipfs/merkledag" ) -type ipfs interface { - ResolvePath(string) (*merkledag.Node, error) -} - type handler struct { ipfs } -type ipfsHandler struct { - node *core.IpfsNode -} - // Serve starts the http server func Serve(address string, node *core.IpfsNode) error { r := mux.NewRouter() - r.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { postHandler(w, r, node) }).Methods("POST") - r.PathPrefix("/").Handler(&handler{&ipfsHandler{node}}).Methods("GET") + handler := &handler{&ipfsHandler{node}} + r.HandleFunc("/", handler.postHandler).Methods("POST") + r.PathPrefix("/").Handler(handler).Methods("GET") http.Handle("/", r) return http.ListenAndServe(address, nil) @@ -45,16 +37,18 @@ func (i *handler) ServeHTTP(w http.ResponseWriter, r *http.Request) { w.Write(nd.Data) } -func postHandler(w http.ResponseWriter, r *http.Request, node *core.IpfsNode) { - root, err := importer.NewDagFromReader(r.Body) +func (i *handler) postHandler(w http.ResponseWriter, r *http.Request) { + nd, err := i.NewDagFromReader(r.Body) if err != nil { w.WriteHeader(http.StatusInternalServerError) + fmt.Println(err) return } - k, err := node.DAG.Add(root) + k, err := i.AddNodeToDAG(nd) if err != nil { w.WriteHeader(http.StatusInternalServerError) + fmt.Println(err) return } @@ -62,7 +56,3 @@ func postHandler(w http.ResponseWriter, r *http.Request, node *core.IpfsNode) { w.WriteHeader(http.StatusCreated) w.Write([]byte(mh.Multihash(k).B58String())) } - -func (i *ipfsHandler) ResolvePath(path string) (*merkledag.Node, error) { - return i.node.Resolver.ResolvePath(path) -} diff --git a/server/http/http_test.go b/server/http/http_test.go index 198147087c4..f62ba6c774a 100644 --- a/server/http/http_test.go +++ b/server/http/http_test.go @@ -2,48 +2,93 @@ package http import ( "errors" + "io" + "io/ioutil" "net/http" "net/http/httptest" + "strings" "testing" - merkledag "github.com/jbenet/go-ipfs/merkledag" + dag "github.com/jbenet/go-ipfs/merkledag" + u "github.com/jbenet/go-ipfs/util" ) -type getTest struct { - url string - code int - body string +type test struct { + url string + code int + reqbody string + respbody string } -type testIpfsHandler struct{} - -func (i *testIpfsHandler) ResolvePath(path string) (*merkledag.Node, error) { - if path == "/QmUxtEgtan9M7acwc8SXF3MGpgpD9Ya8ViLNGEXQ6n9vfA" { - return &merkledag.Node{Data: []byte("some fine data")}, nil +func TestServeHTTP(t *testing.T) { + testhandler := &handler{&testIpfsHandler{}} + tests := []test{ + {"/", http.StatusInternalServerError, "", ""}, + {"/hash", http.StatusOK, "", "some fine data"}, } - return nil, errors.New("") + for _, test := range tests { + req, _ := http.NewRequest("GET", test.url, nil) + resp := httptest.NewRecorder() + testhandler.ServeHTTP(resp, req) + + if resp.Code != test.code { + t.Error("expected status code", test.code, "received", resp.Code) + } + + if resp.Body.String() != test.respbody { + t.Error("expected body:", test.respbody) + t.Error("received body:", resp.Body) + } + } } -func TestServeHTTP(t *testing.T) { +func TestPostHandler(t *testing.T) { testhandler := &handler{&testIpfsHandler{}} - tests := []getTest{ - {"/", http.StatusInternalServerError, ""}, - {"/QmUxtEgtan9M7acwc8SXF3MGpgpD9Ya8ViLNGEXQ6n9vfA", http.StatusOK, "some fine data"}, + tests := []test{ + {"/", http.StatusInternalServerError, "", ""}, + {"/", http.StatusInternalServerError, "something that causes an error in adding to DAG", ""}, + {"/", http.StatusCreated, "some fine data", "jSQBpNSebeYbPBjs1vp"}, } for _, test := range tests { - req, _ := http.NewRequest("GET", test.url, nil) + req, _ := http.NewRequest("POST", test.url, strings.NewReader(test.reqbody)) resp := httptest.NewRecorder() - testhandler.ServeHTTP(resp, req) + testhandler.postHandler(resp, req) if resp.Code != test.code { t.Error("expected status code", test.code, "received", resp.Code) } - if resp.Body.String() != test.body { - t.Error("expected body:", test.body) + if resp.Body.String() != test.respbody { + t.Error("expected body:", test.respbody) t.Error("received body:", resp.Body) } } } + +type testIpfsHandler struct{} + +func (i *testIpfsHandler) ResolvePath(path string) (*dag.Node, error) { + if path == "/hash" { + return &dag.Node{Data: []byte("some fine data")}, nil + } + + return nil, errors.New("") +} + +func (i *testIpfsHandler) NewDagFromReader(r io.Reader) (*dag.Node, error) { + if data, err := ioutil.ReadAll(r); err == nil { + return &dag.Node{Data: data}, nil + } + + return nil, errors.New("") +} + +func (i *testIpfsHandler) AddNodeToDAG(nd *dag.Node) (u.Key, error) { + if len(nd.Data) != 0 && string(nd.Data) != "something that causes an error in adding to DAG" { + return u.Key(nd.Data), nil + } + + return "", errors.New("") +} diff --git a/server/http/ipfs.go b/server/http/ipfs.go new file mode 100644 index 00000000000..e9c24399391 --- /dev/null +++ b/server/http/ipfs.go @@ -0,0 +1,32 @@ +package http + +import ( + "io" + + core "github.com/jbenet/go-ipfs/core" + "github.com/jbenet/go-ipfs/importer" + dag "github.com/jbenet/go-ipfs/merkledag" + u "github.com/jbenet/go-ipfs/util" +) + +type ipfs interface { + ResolvePath(string) (*dag.Node, error) + NewDagFromReader(io.Reader) (*dag.Node, error) + AddNodeToDAG(nd *dag.Node) (u.Key, error) +} + +type ipfsHandler struct { + node *core.IpfsNode +} + +func (i *ipfsHandler) ResolvePath(path string) (*dag.Node, error) { + return i.node.Resolver.ResolvePath(path) +} + +func (i *ipfsHandler) NewDagFromReader(r io.Reader) (*dag.Node, error) { + return importer.NewDagFromReader(r) +} + +func (i *ipfsHandler) AddNodeToDAG(nd *dag.Node) (u.Key, error) { + return i.node.DAG.Add(nd) +} From cf0100c97c667d7a024c37a73dd8b88df621d9dc Mon Sep 17 00:00:00 2001 From: verokarhu Date: Thu, 18 Sep 2014 00:04:43 +0300 Subject: [PATCH 16/24] vendor github.com/gorilla dependencies --- Godeps/Godeps.json | 8 + .../github.com/gorilla/context/.travis.yml | 7 + .../src/github.com/gorilla/context/LICENSE | 27 + .../src/github.com/gorilla/context/README.md | 7 + .../src/github.com/gorilla/context/context.go | 143 +++ .../gorilla/context/context_test.go | 161 +++ .../src/github.com/gorilla/context/doc.go | 82 ++ .../src/github.com/gorilla/mux/.travis.yml | 7 + .../src/github.com/gorilla/mux/LICENSE | 27 + .../src/github.com/gorilla/mux/README.md | 7 + .../src/github.com/gorilla/mux/bench_test.go | 21 + .../src/github.com/gorilla/mux/doc.go | 199 ++++ .../src/github.com/gorilla/mux/mux.go | 353 +++++++ .../src/github.com/gorilla/mux/mux_test.go | 943 ++++++++++++++++++ .../src/github.com/gorilla/mux/old_test.go | 714 +++++++++++++ .../src/github.com/gorilla/mux/regexp.go | 274 +++++ .../src/github.com/gorilla/mux/route.go | 524 ++++++++++ server/http/http.go | 2 +- 18 files changed, 3505 insertions(+), 1 deletion(-) create mode 100644 Godeps/_workspace/src/github.com/gorilla/context/.travis.yml create mode 100644 Godeps/_workspace/src/github.com/gorilla/context/LICENSE create mode 100644 Godeps/_workspace/src/github.com/gorilla/context/README.md create mode 100644 Godeps/_workspace/src/github.com/gorilla/context/context.go create mode 100644 Godeps/_workspace/src/github.com/gorilla/context/context_test.go create mode 100644 Godeps/_workspace/src/github.com/gorilla/context/doc.go create mode 100644 Godeps/_workspace/src/github.com/gorilla/mux/.travis.yml create mode 100644 Godeps/_workspace/src/github.com/gorilla/mux/LICENSE create mode 100644 Godeps/_workspace/src/github.com/gorilla/mux/README.md create mode 100644 Godeps/_workspace/src/github.com/gorilla/mux/bench_test.go create mode 100644 Godeps/_workspace/src/github.com/gorilla/mux/doc.go create mode 100644 Godeps/_workspace/src/github.com/gorilla/mux/mux.go create mode 100644 Godeps/_workspace/src/github.com/gorilla/mux/mux_test.go create mode 100644 Godeps/_workspace/src/github.com/gorilla/mux/old_test.go create mode 100644 Godeps/_workspace/src/github.com/gorilla/mux/regexp.go create mode 100644 Godeps/_workspace/src/github.com/gorilla/mux/route.go diff --git a/Godeps/Godeps.json b/Godeps/Godeps.json index 613f484e2fa..985b0bccf28 100644 --- a/Godeps/Godeps.json +++ b/Godeps/Godeps.json @@ -46,6 +46,14 @@ "ImportPath": "github.com/gonuts/flag", "Rev": "741a6cbd37a30dedc93f817e7de6aaf0ca38a493" }, + { + "ImportPath": "github.com/gorilla/context", + "Rev": "14f550f51af52180c2eefed15e5fd18d63c0a64a" + }, + { + "ImportPath": "github.com/gorilla/mux", + "Rev": "4b8fbc56f3b2400a7c7ea3dba9b3539787c486b6" + }, { "ImportPath": "github.com/jbenet/commander", "Rev": "e0cf317891f0ab6f1ac64dfcb754b4fb5e69f7df" diff --git a/Godeps/_workspace/src/github.com/gorilla/context/.travis.yml b/Godeps/_workspace/src/github.com/gorilla/context/.travis.yml new file mode 100644 index 00000000000..d87d4657686 --- /dev/null +++ b/Godeps/_workspace/src/github.com/gorilla/context/.travis.yml @@ -0,0 +1,7 @@ +language: go + +go: + - 1.0 + - 1.1 + - 1.2 + - tip diff --git a/Godeps/_workspace/src/github.com/gorilla/context/LICENSE b/Godeps/_workspace/src/github.com/gorilla/context/LICENSE new file mode 100644 index 00000000000..0e5fb872800 --- /dev/null +++ b/Godeps/_workspace/src/github.com/gorilla/context/LICENSE @@ -0,0 +1,27 @@ +Copyright (c) 2012 Rodrigo Moraes. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the following disclaimer +in the documentation and/or other materials provided with the +distribution. + * Neither the name of Google Inc. nor the names of its +contributors may be used to endorse or promote products derived from +this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/Godeps/_workspace/src/github.com/gorilla/context/README.md b/Godeps/_workspace/src/github.com/gorilla/context/README.md new file mode 100644 index 00000000000..c60a31b053b --- /dev/null +++ b/Godeps/_workspace/src/github.com/gorilla/context/README.md @@ -0,0 +1,7 @@ +context +======= +[![Build Status](https://travis-ci.org/gorilla/context.png?branch=master)](https://travis-ci.org/gorilla/context) + +gorilla/context is a general purpose registry for global request variables. + +Read the full documentation here: http://www.gorillatoolkit.org/pkg/context diff --git a/Godeps/_workspace/src/github.com/gorilla/context/context.go b/Godeps/_workspace/src/github.com/gorilla/context/context.go new file mode 100644 index 00000000000..81cb128b19c --- /dev/null +++ b/Godeps/_workspace/src/github.com/gorilla/context/context.go @@ -0,0 +1,143 @@ +// Copyright 2012 The Gorilla Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package context + +import ( + "net/http" + "sync" + "time" +) + +var ( + mutex sync.RWMutex + data = make(map[*http.Request]map[interface{}]interface{}) + datat = make(map[*http.Request]int64) +) + +// Set stores a value for a given key in a given request. +func Set(r *http.Request, key, val interface{}) { + mutex.Lock() + if data[r] == nil { + data[r] = make(map[interface{}]interface{}) + datat[r] = time.Now().Unix() + } + data[r][key] = val + mutex.Unlock() +} + +// Get returns a value stored for a given key in a given request. +func Get(r *http.Request, key interface{}) interface{} { + mutex.RLock() + if ctx := data[r]; ctx != nil { + value := ctx[key] + mutex.RUnlock() + return value + } + mutex.RUnlock() + return nil +} + +// GetOk returns stored value and presence state like multi-value return of map access. +func GetOk(r *http.Request, key interface{}) (interface{}, bool) { + mutex.RLock() + if _, ok := data[r]; ok { + value, ok := data[r][key] + mutex.RUnlock() + return value, ok + } + mutex.RUnlock() + return nil, false +} + +// GetAll returns all stored values for the request as a map. Nil is returned for invalid requests. +func GetAll(r *http.Request) map[interface{}]interface{} { + mutex.RLock() + if context, ok := data[r]; ok { + result := make(map[interface{}]interface{}, len(context)) + for k, v := range context { + result[k] = v + } + mutex.RUnlock() + return result + } + mutex.RUnlock() + return nil +} + +// GetAllOk returns all stored values for the request as a map and a boolean value that indicates if +// the request was registered. +func GetAllOk(r *http.Request) (map[interface{}]interface{}, bool) { + mutex.RLock() + context, ok := data[r] + result := make(map[interface{}]interface{}, len(context)) + for k, v := range context { + result[k] = v + } + mutex.RUnlock() + return result, ok +} + +// Delete removes a value stored for a given key in a given request. +func Delete(r *http.Request, key interface{}) { + mutex.Lock() + if data[r] != nil { + delete(data[r], key) + } + mutex.Unlock() +} + +// Clear removes all values stored for a given request. +// +// This is usually called by a handler wrapper to clean up request +// variables at the end of a request lifetime. See ClearHandler(). +func Clear(r *http.Request) { + mutex.Lock() + clear(r) + mutex.Unlock() +} + +// clear is Clear without the lock. +func clear(r *http.Request) { + delete(data, r) + delete(datat, r) +} + +// Purge removes request data stored for longer than maxAge, in seconds. +// It returns the amount of requests removed. +// +// If maxAge <= 0, all request data is removed. +// +// This is only used for sanity check: in case context cleaning was not +// properly set some request data can be kept forever, consuming an increasing +// amount of memory. In case this is detected, Purge() must be called +// periodically until the problem is fixed. +func Purge(maxAge int) int { + mutex.Lock() + count := 0 + if maxAge <= 0 { + count = len(data) + data = make(map[*http.Request]map[interface{}]interface{}) + datat = make(map[*http.Request]int64) + } else { + min := time.Now().Unix() - int64(maxAge) + for r := range data { + if datat[r] < min { + clear(r) + count++ + } + } + } + mutex.Unlock() + return count +} + +// ClearHandler wraps an http.Handler and clears request values at the end +// of a request lifetime. +func ClearHandler(h http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + defer Clear(r) + h.ServeHTTP(w, r) + }) +} diff --git a/Godeps/_workspace/src/github.com/gorilla/context/context_test.go b/Godeps/_workspace/src/github.com/gorilla/context/context_test.go new file mode 100644 index 00000000000..6ada8ec31f6 --- /dev/null +++ b/Godeps/_workspace/src/github.com/gorilla/context/context_test.go @@ -0,0 +1,161 @@ +// Copyright 2012 The Gorilla Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package context + +import ( + "net/http" + "testing" +) + +type keyType int + +const ( + key1 keyType = iota + key2 +) + +func TestContext(t *testing.T) { + assertEqual := func(val interface{}, exp interface{}) { + if val != exp { + t.Errorf("Expected %v, got %v.", exp, val) + } + } + + r, _ := http.NewRequest("GET", "http://localhost:8080/", nil) + emptyR, _ := http.NewRequest("GET", "http://localhost:8080/", nil) + + // Get() + assertEqual(Get(r, key1), nil) + + // Set() + Set(r, key1, "1") + assertEqual(Get(r, key1), "1") + assertEqual(len(data[r]), 1) + + Set(r, key2, "2") + assertEqual(Get(r, key2), "2") + assertEqual(len(data[r]), 2) + + //GetOk + value, ok := GetOk(r, key1) + assertEqual(value, "1") + assertEqual(ok, true) + + value, ok = GetOk(r, "not exists") + assertEqual(value, nil) + assertEqual(ok, false) + + Set(r, "nil value", nil) + value, ok = GetOk(r, "nil value") + assertEqual(value, nil) + assertEqual(ok, true) + + // GetAll() + values := GetAll(r) + assertEqual(len(values), 3) + + // GetAll() for empty request + values = GetAll(emptyR) + if values != nil { + t.Error("GetAll didn't return nil value for invalid request") + } + + // GetAllOk() + values, ok = GetAllOk(r) + assertEqual(len(values), 3) + assertEqual(ok, true) + + // GetAllOk() for empty request + values, ok = GetAllOk(emptyR) + assertEqual(value, nil) + assertEqual(ok, false) + + // Delete() + Delete(r, key1) + assertEqual(Get(r, key1), nil) + assertEqual(len(data[r]), 2) + + Delete(r, key2) + assertEqual(Get(r, key2), nil) + assertEqual(len(data[r]), 1) + + // Clear() + Clear(r) + assertEqual(len(data), 0) +} + +func parallelReader(r *http.Request, key string, iterations int, wait, done chan struct{}) { + <-wait + for i := 0; i < iterations; i++ { + Get(r, key) + } + done <- struct{}{} + +} + +func parallelWriter(r *http.Request, key, value string, iterations int, wait, done chan struct{}) { + <-wait + for i := 0; i < iterations; i++ { + Get(r, key) + } + done <- struct{}{} + +} + +func benchmarkMutex(b *testing.B, numReaders, numWriters, iterations int) { + + b.StopTimer() + r, _ := http.NewRequest("GET", "http://localhost:8080/", nil) + done := make(chan struct{}) + b.StartTimer() + + for i := 0; i < b.N; i++ { + wait := make(chan struct{}) + + for i := 0; i < numReaders; i++ { + go parallelReader(r, "test", iterations, wait, done) + } + + for i := 0; i < numWriters; i++ { + go parallelWriter(r, "test", "123", iterations, wait, done) + } + + close(wait) + + for i := 0; i < numReaders+numWriters; i++ { + <-done + } + + } + +} + +func BenchmarkMutexSameReadWrite1(b *testing.B) { + benchmarkMutex(b, 1, 1, 32) +} +func BenchmarkMutexSameReadWrite2(b *testing.B) { + benchmarkMutex(b, 2, 2, 32) +} +func BenchmarkMutexSameReadWrite4(b *testing.B) { + benchmarkMutex(b, 4, 4, 32) +} +func BenchmarkMutex1(b *testing.B) { + benchmarkMutex(b, 2, 8, 32) +} +func BenchmarkMutex2(b *testing.B) { + benchmarkMutex(b, 16, 4, 64) +} +func BenchmarkMutex3(b *testing.B) { + benchmarkMutex(b, 1, 2, 128) +} +func BenchmarkMutex4(b *testing.B) { + benchmarkMutex(b, 128, 32, 256) +} +func BenchmarkMutex5(b *testing.B) { + benchmarkMutex(b, 1024, 2048, 64) +} +func BenchmarkMutex6(b *testing.B) { + benchmarkMutex(b, 2048, 1024, 512) +} diff --git a/Godeps/_workspace/src/github.com/gorilla/context/doc.go b/Godeps/_workspace/src/github.com/gorilla/context/doc.go new file mode 100644 index 00000000000..73c7400311e --- /dev/null +++ b/Godeps/_workspace/src/github.com/gorilla/context/doc.go @@ -0,0 +1,82 @@ +// Copyright 2012 The Gorilla Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +/* +Package context stores values shared during a request lifetime. + +For example, a router can set variables extracted from the URL and later +application handlers can access those values, or it can be used to store +sessions values to be saved at the end of a request. There are several +others common uses. + +The idea was posted by Brad Fitzpatrick to the go-nuts mailing list: + + http://groups.google.com/group/golang-nuts/msg/e2d679d303aa5d53 + +Here's the basic usage: first define the keys that you will need. The key +type is interface{} so a key can be of any type that supports equality. +Here we define a key using a custom int type to avoid name collisions: + + package foo + + import ( + "github.com/gorilla/context" + ) + + type key int + + const MyKey key = 0 + +Then set a variable. Variables are bound to an http.Request object, so you +need a request instance to set a value: + + context.Set(r, MyKey, "bar") + +The application can later access the variable using the same key you provided: + + func MyHandler(w http.ResponseWriter, r *http.Request) { + // val is "bar". + val := context.Get(r, foo.MyKey) + + // returns ("bar", true) + val, ok := context.GetOk(r, foo.MyKey) + // ... + } + +And that's all about the basic usage. We discuss some other ideas below. + +Any type can be stored in the context. To enforce a given type, make the key +private and wrap Get() and Set() to accept and return values of a specific +type: + + type key int + + const mykey key = 0 + + // GetMyKey returns a value for this package from the request values. + func GetMyKey(r *http.Request) SomeType { + if rv := context.Get(r, mykey); rv != nil { + return rv.(SomeType) + } + return nil + } + + // SetMyKey sets a value for this package in the request values. + func SetMyKey(r *http.Request, val SomeType) { + context.Set(r, mykey, val) + } + +Variables must be cleared at the end of a request, to remove all values +that were stored. This can be done in an http.Handler, after a request was +served. Just call Clear() passing the request: + + context.Clear(r) + +...or use ClearHandler(), which conveniently wraps an http.Handler to clear +variables at the end of a request lifetime. + +The Routers from the packages gorilla/mux and gorilla/pat call Clear() +so if you are using either of them you don't need to clear the context manually. +*/ +package context diff --git a/Godeps/_workspace/src/github.com/gorilla/mux/.travis.yml b/Godeps/_workspace/src/github.com/gorilla/mux/.travis.yml new file mode 100644 index 00000000000..d87d4657686 --- /dev/null +++ b/Godeps/_workspace/src/github.com/gorilla/mux/.travis.yml @@ -0,0 +1,7 @@ +language: go + +go: + - 1.0 + - 1.1 + - 1.2 + - tip diff --git a/Godeps/_workspace/src/github.com/gorilla/mux/LICENSE b/Godeps/_workspace/src/github.com/gorilla/mux/LICENSE new file mode 100644 index 00000000000..0e5fb872800 --- /dev/null +++ b/Godeps/_workspace/src/github.com/gorilla/mux/LICENSE @@ -0,0 +1,27 @@ +Copyright (c) 2012 Rodrigo Moraes. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the following disclaimer +in the documentation and/or other materials provided with the +distribution. + * Neither the name of Google Inc. nor the names of its +contributors may be used to endorse or promote products derived from +this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/Godeps/_workspace/src/github.com/gorilla/mux/README.md b/Godeps/_workspace/src/github.com/gorilla/mux/README.md new file mode 100644 index 00000000000..e60301b0336 --- /dev/null +++ b/Godeps/_workspace/src/github.com/gorilla/mux/README.md @@ -0,0 +1,7 @@ +mux +=== +[![Build Status](https://travis-ci.org/gorilla/mux.png?branch=master)](https://travis-ci.org/gorilla/mux) + +gorilla/mux is a powerful URL router and dispatcher. + +Read the full documentation here: http://www.gorillatoolkit.org/pkg/mux diff --git a/Godeps/_workspace/src/github.com/gorilla/mux/bench_test.go b/Godeps/_workspace/src/github.com/gorilla/mux/bench_test.go new file mode 100644 index 00000000000..c5f97b2b2a8 --- /dev/null +++ b/Godeps/_workspace/src/github.com/gorilla/mux/bench_test.go @@ -0,0 +1,21 @@ +// Copyright 2012 The Gorilla Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package mux + +import ( + "net/http" + "testing" +) + +func BenchmarkMux(b *testing.B) { + router := new(Router) + handler := func(w http.ResponseWriter, r *http.Request) {} + router.HandleFunc("/v1/{v1}", handler) + + request, _ := http.NewRequest("GET", "/v1/anything", nil) + for i := 0; i < b.N; i++ { + router.ServeHTTP(nil, request) + } +} diff --git a/Godeps/_workspace/src/github.com/gorilla/mux/doc.go b/Godeps/_workspace/src/github.com/gorilla/mux/doc.go new file mode 100644 index 00000000000..b2deed34c47 --- /dev/null +++ b/Godeps/_workspace/src/github.com/gorilla/mux/doc.go @@ -0,0 +1,199 @@ +// Copyright 2012 The Gorilla Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +/* +Package gorilla/mux implements a request router and dispatcher. + +The name mux stands for "HTTP request multiplexer". Like the standard +http.ServeMux, mux.Router matches incoming requests against a list of +registered routes and calls a handler for the route that matches the URL +or other conditions. The main features are: + + * Requests can be matched based on URL host, path, path prefix, schemes, + header and query values, HTTP methods or using custom matchers. + * URL hosts and paths can have variables with an optional regular + expression. + * Registered URLs can be built, or "reversed", which helps maintaining + references to resources. + * Routes can be used as subrouters: nested routes are only tested if the + parent route matches. This is useful to define groups of routes that + share common conditions like a host, a path prefix or other repeated + attributes. As a bonus, this optimizes request matching. + * It implements the http.Handler interface so it is compatible with the + standard http.ServeMux. + +Let's start registering a couple of URL paths and handlers: + + func main() { + r := mux.NewRouter() + r.HandleFunc("/", HomeHandler) + r.HandleFunc("/products", ProductsHandler) + r.HandleFunc("/articles", ArticlesHandler) + http.Handle("/", r) + } + +Here we register three routes mapping URL paths to handlers. This is +equivalent to how http.HandleFunc() works: if an incoming request URL matches +one of the paths, the corresponding handler is called passing +(http.ResponseWriter, *http.Request) as parameters. + +Paths can have variables. They are defined using the format {name} or +{name:pattern}. If a regular expression pattern is not defined, the matched +variable will be anything until the next slash. For example: + + r := mux.NewRouter() + r.HandleFunc("/products/{key}", ProductHandler) + r.HandleFunc("/articles/{category}/", ArticlesCategoryHandler) + r.HandleFunc("/articles/{category}/{id:[0-9]+}", ArticleHandler) + +The names are used to create a map of route variables which can be retrieved +calling mux.Vars(): + + vars := mux.Vars(request) + category := vars["category"] + +And this is all you need to know about the basic usage. More advanced options +are explained below. + +Routes can also be restricted to a domain or subdomain. Just define a host +pattern to be matched. They can also have variables: + + r := mux.NewRouter() + // Only matches if domain is "www.domain.com". + r.Host("www.domain.com") + // Matches a dynamic subdomain. + r.Host("{subdomain:[a-z]+}.domain.com") + +There are several other matchers that can be added. To match path prefixes: + + r.PathPrefix("/products/") + +...or HTTP methods: + + r.Methods("GET", "POST") + +...or URL schemes: + + r.Schemes("https") + +...or header values: + + r.Headers("X-Requested-With", "XMLHttpRequest") + +...or query values: + + r.Queries("key", "value") + +...or to use a custom matcher function: + + r.MatcherFunc(func(r *http.Request, rm *RouteMatch) bool { + return r.ProtoMajor == 0 + }) + +...and finally, it is possible to combine several matchers in a single route: + + r.HandleFunc("/products", ProductsHandler). + Host("www.domain.com"). + Methods("GET"). + Schemes("http") + +Setting the same matching conditions again and again can be boring, so we have +a way to group several routes that share the same requirements. +We call it "subrouting". + +For example, let's say we have several URLs that should only match when the +host is "www.domain.com". Create a route for that host and get a "subrouter" +from it: + + r := mux.NewRouter() + s := r.Host("www.domain.com").Subrouter() + +Then register routes in the subrouter: + + s.HandleFunc("/products/", ProductsHandler) + s.HandleFunc("/products/{key}", ProductHandler) + s.HandleFunc("/articles/{category}/{id:[0-9]+}"), ArticleHandler) + +The three URL paths we registered above will only be tested if the domain is +"www.domain.com", because the subrouter is tested first. This is not +only convenient, but also optimizes request matching. You can create +subrouters combining any attribute matchers accepted by a route. + +Subrouters can be used to create domain or path "namespaces": you define +subrouters in a central place and then parts of the app can register its +paths relatively to a given subrouter. + +There's one more thing about subroutes. When a subrouter has a path prefix, +the inner routes use it as base for their paths: + + r := mux.NewRouter() + s := r.PathPrefix("/products").Subrouter() + // "/products/" + s.HandleFunc("/", ProductsHandler) + // "/products/{key}/" + s.HandleFunc("/{key}/", ProductHandler) + // "/products/{key}/details" + s.HandleFunc("/{key}/details", ProductDetailsHandler) + +Now let's see how to build registered URLs. + +Routes can be named. All routes that define a name can have their URLs built, +or "reversed". We define a name calling Name() on a route. For example: + + r := mux.NewRouter() + r.HandleFunc("/articles/{category}/{id:[0-9]+}", ArticleHandler). + Name("article") + +To build a URL, get the route and call the URL() method, passing a sequence of +key/value pairs for the route variables. For the previous route, we would do: + + url, err := r.Get("article").URL("category", "technology", "id", "42") + +...and the result will be a url.URL with the following path: + + "/articles/technology/42" + +This also works for host variables: + + r := mux.NewRouter() + r.Host("{subdomain}.domain.com"). + Path("/articles/{category}/{id:[0-9]+}"). + HandlerFunc(ArticleHandler). + Name("article") + + // url.String() will be "http://news.domain.com/articles/technology/42" + url, err := r.Get("article").URL("subdomain", "news", + "category", "technology", + "id", "42") + +All variables defined in the route are required, and their values must +conform to the corresponding patterns. These requirements guarantee that a +generated URL will always match a registered route -- the only exception is +for explicitly defined "build-only" routes which never match. + +There's also a way to build only the URL host or path for a route: +use the methods URLHost() or URLPath() instead. For the previous route, +we would do: + + // "http://news.domain.com/" + host, err := r.Get("article").URLHost("subdomain", "news") + + // "/articles/technology/42" + path, err := r.Get("article").URLPath("category", "technology", "id", "42") + +And if you use subrouters, host and path defined separately can be built +as well: + + r := mux.NewRouter() + s := r.Host("{subdomain}.domain.com").Subrouter() + s.Path("/articles/{category}/{id:[0-9]+}"). + HandlerFunc(ArticleHandler). + Name("article") + + // "http://news.domain.com/articles/technology/42" + url, err := r.Get("article").URL("subdomain", "news", + "category", "technology", + "id", "42") +*/ +package mux diff --git a/Godeps/_workspace/src/github.com/gorilla/mux/mux.go b/Godeps/_workspace/src/github.com/gorilla/mux/mux.go new file mode 100644 index 00000000000..5b5f8e7db5d --- /dev/null +++ b/Godeps/_workspace/src/github.com/gorilla/mux/mux.go @@ -0,0 +1,353 @@ +// Copyright 2012 The Gorilla Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package mux + +import ( + "fmt" + "net/http" + "path" + + "github.com/gorilla/context" +) + +// NewRouter returns a new router instance. +func NewRouter() *Router { + return &Router{namedRoutes: make(map[string]*Route), KeepContext: false} +} + +// Router registers routes to be matched and dispatches a handler. +// +// It implements the http.Handler interface, so it can be registered to serve +// requests: +// +// var router = mux.NewRouter() +// +// func main() { +// http.Handle("/", router) +// } +// +// Or, for Google App Engine, register it in a init() function: +// +// func init() { +// http.Handle("/", router) +// } +// +// This will send all incoming requests to the router. +type Router struct { + // Configurable Handler to be used when no route matches. + NotFoundHandler http.Handler + // Parent route, if this is a subrouter. + parent parentRoute + // Routes to be matched, in order. + routes []*Route + // Routes by name for URL building. + namedRoutes map[string]*Route + // See Router.StrictSlash(). This defines the flag for new routes. + strictSlash bool + // If true, do not clear the request context after handling the request + KeepContext bool +} + +// Match matches registered routes against the request. +func (r *Router) Match(req *http.Request, match *RouteMatch) bool { + for _, route := range r.routes { + if route.Match(req, match) { + return true + } + } + return false +} + +// ServeHTTP dispatches the handler registered in the matched route. +// +// When there is a match, the route variables can be retrieved calling +// mux.Vars(request). +func (r *Router) ServeHTTP(w http.ResponseWriter, req *http.Request) { + // Clean path to canonical form and redirect. + if p := cleanPath(req.URL.Path); p != req.URL.Path { + + // Added 3 lines (Philip Schlump) - It was droping the query string and #whatever from query. + // This matches with fix in go 1.2 r.c. 4 for same problem. Go Issue: + // http://code.google.com/p/go/issues/detail?id=5252 + url := *req.URL + url.Path = p + p = url.String() + + w.Header().Set("Location", p) + w.WriteHeader(http.StatusMovedPermanently) + return + } + var match RouteMatch + var handler http.Handler + if r.Match(req, &match) { + handler = match.Handler + setVars(req, match.Vars) + setCurrentRoute(req, match.Route) + } + if handler == nil { + handler = r.NotFoundHandler + if handler == nil { + handler = http.NotFoundHandler() + } + } + if !r.KeepContext { + defer context.Clear(req) + } + handler.ServeHTTP(w, req) +} + +// Get returns a route registered with the given name. +func (r *Router) Get(name string) *Route { + return r.getNamedRoutes()[name] +} + +// GetRoute returns a route registered with the given name. This method +// was renamed to Get() and remains here for backwards compatibility. +func (r *Router) GetRoute(name string) *Route { + return r.getNamedRoutes()[name] +} + +// StrictSlash defines the trailing slash behavior for new routes. The initial +// value is false. +// +// When true, if the route path is "/path/", accessing "/path" will redirect +// to the former and vice versa. In other words, your application will always +// see the path as specified in the route. +// +// When false, if the route path is "/path", accessing "/path/" will not match +// this route and vice versa. +// +// Special case: when a route sets a path prefix using the PathPrefix() method, +// strict slash is ignored for that route because the redirect behavior can't +// be determined from a prefix alone. However, any subrouters created from that +// route inherit the original StrictSlash setting. +func (r *Router) StrictSlash(value bool) *Router { + r.strictSlash = value + return r +} + +// ---------------------------------------------------------------------------- +// parentRoute +// ---------------------------------------------------------------------------- + +// getNamedRoutes returns the map where named routes are registered. +func (r *Router) getNamedRoutes() map[string]*Route { + if r.namedRoutes == nil { + if r.parent != nil { + r.namedRoutes = r.parent.getNamedRoutes() + } else { + r.namedRoutes = make(map[string]*Route) + } + } + return r.namedRoutes +} + +// getRegexpGroup returns regexp definitions from the parent route, if any. +func (r *Router) getRegexpGroup() *routeRegexpGroup { + if r.parent != nil { + return r.parent.getRegexpGroup() + } + return nil +} + +// ---------------------------------------------------------------------------- +// Route factories +// ---------------------------------------------------------------------------- + +// NewRoute registers an empty route. +func (r *Router) NewRoute() *Route { + route := &Route{parent: r, strictSlash: r.strictSlash} + r.routes = append(r.routes, route) + return route +} + +// Handle registers a new route with a matcher for the URL path. +// See Route.Path() and Route.Handler(). +func (r *Router) Handle(path string, handler http.Handler) *Route { + return r.NewRoute().Path(path).Handler(handler) +} + +// HandleFunc registers a new route with a matcher for the URL path. +// See Route.Path() and Route.HandlerFunc(). +func (r *Router) HandleFunc(path string, f func(http.ResponseWriter, + *http.Request)) *Route { + return r.NewRoute().Path(path).HandlerFunc(f) +} + +// Headers registers a new route with a matcher for request header values. +// See Route.Headers(). +func (r *Router) Headers(pairs ...string) *Route { + return r.NewRoute().Headers(pairs...) +} + +// Host registers a new route with a matcher for the URL host. +// See Route.Host(). +func (r *Router) Host(tpl string) *Route { + return r.NewRoute().Host(tpl) +} + +// MatcherFunc registers a new route with a custom matcher function. +// See Route.MatcherFunc(). +func (r *Router) MatcherFunc(f MatcherFunc) *Route { + return r.NewRoute().MatcherFunc(f) +} + +// Methods registers a new route with a matcher for HTTP methods. +// See Route.Methods(). +func (r *Router) Methods(methods ...string) *Route { + return r.NewRoute().Methods(methods...) +} + +// Path registers a new route with a matcher for the URL path. +// See Route.Path(). +func (r *Router) Path(tpl string) *Route { + return r.NewRoute().Path(tpl) +} + +// PathPrefix registers a new route with a matcher for the URL path prefix. +// See Route.PathPrefix(). +func (r *Router) PathPrefix(tpl string) *Route { + return r.NewRoute().PathPrefix(tpl) +} + +// Queries registers a new route with a matcher for URL query values. +// See Route.Queries(). +func (r *Router) Queries(pairs ...string) *Route { + return r.NewRoute().Queries(pairs...) +} + +// Schemes registers a new route with a matcher for URL schemes. +// See Route.Schemes(). +func (r *Router) Schemes(schemes ...string) *Route { + return r.NewRoute().Schemes(schemes...) +} + +// ---------------------------------------------------------------------------- +// Context +// ---------------------------------------------------------------------------- + +// RouteMatch stores information about a matched route. +type RouteMatch struct { + Route *Route + Handler http.Handler + Vars map[string]string +} + +type contextKey int + +const ( + varsKey contextKey = iota + routeKey +) + +// Vars returns the route variables for the current request, if any. +func Vars(r *http.Request) map[string]string { + if rv := context.Get(r, varsKey); rv != nil { + return rv.(map[string]string) + } + return nil +} + +// CurrentRoute returns the matched route for the current request, if any. +func CurrentRoute(r *http.Request) *Route { + if rv := context.Get(r, routeKey); rv != nil { + return rv.(*Route) + } + return nil +} + +func setVars(r *http.Request, val interface{}) { + context.Set(r, varsKey, val) +} + +func setCurrentRoute(r *http.Request, val interface{}) { + context.Set(r, routeKey, val) +} + +// ---------------------------------------------------------------------------- +// Helpers +// ---------------------------------------------------------------------------- + +// cleanPath returns the canonical path for p, eliminating . and .. elements. +// Borrowed from the net/http package. +func cleanPath(p string) string { + if p == "" { + return "/" + } + if p[0] != '/' { + p = "/" + p + } + np := path.Clean(p) + // path.Clean removes trailing slash except for root; + // put the trailing slash back if necessary. + if p[len(p)-1] == '/' && np != "/" { + np += "/" + } + return np +} + +// uniqueVars returns an error if two slices contain duplicated strings. +func uniqueVars(s1, s2 []string) error { + for _, v1 := range s1 { + for _, v2 := range s2 { + if v1 == v2 { + return fmt.Errorf("mux: duplicated route variable %q", v2) + } + } + } + return nil +} + +// mapFromPairs converts variadic string parameters to a string map. +func mapFromPairs(pairs ...string) (map[string]string, error) { + length := len(pairs) + if length%2 != 0 { + return nil, fmt.Errorf( + "mux: number of parameters must be multiple of 2, got %v", pairs) + } + m := make(map[string]string, length/2) + for i := 0; i < length; i += 2 { + m[pairs[i]] = pairs[i+1] + } + return m, nil +} + +// matchInArray returns true if the given string value is in the array. +func matchInArray(arr []string, value string) bool { + for _, v := range arr { + if v == value { + return true + } + } + return false +} + +// matchMap returns true if the given key/value pairs exist in a given map. +func matchMap(toCheck map[string]string, toMatch map[string][]string, + canonicalKey bool) bool { + for k, v := range toCheck { + // Check if key exists. + if canonicalKey { + k = http.CanonicalHeaderKey(k) + } + if values := toMatch[k]; values == nil { + return false + } else if v != "" { + // If value was defined as an empty string we only check that the + // key exists. Otherwise we also check for equality. + valueExists := false + for _, value := range values { + if v == value { + valueExists = true + break + } + } + if !valueExists { + return false + } + } + } + return true +} diff --git a/Godeps/_workspace/src/github.com/gorilla/mux/mux_test.go b/Godeps/_workspace/src/github.com/gorilla/mux/mux_test.go new file mode 100644 index 00000000000..e455bce8fdf --- /dev/null +++ b/Godeps/_workspace/src/github.com/gorilla/mux/mux_test.go @@ -0,0 +1,943 @@ +// Copyright 2012 The Gorilla Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package mux + +import ( + "fmt" + "net/http" + "testing" + + "github.com/gorilla/context" +) + +type routeTest struct { + title string // title of the test + route *Route // the route being tested + request *http.Request // a request to test the route + vars map[string]string // the expected vars of the match + host string // the expected host of the match + path string // the expected path of the match + shouldMatch bool // whether the request is expected to match the route at all + shouldRedirect bool // whether the request should result in a redirect +} + +func TestHost(t *testing.T) { + // newRequestHost a new request with a method, url, and host header + newRequestHost := func(method, url, host string) *http.Request { + req, err := http.NewRequest(method, url, nil) + if err != nil { + panic(err) + } + req.Host = host + return req + } + + tests := []routeTest{ + { + title: "Host route match", + route: new(Route).Host("aaa.bbb.ccc"), + request: newRequest("GET", "http://aaa.bbb.ccc/111/222/333"), + vars: map[string]string{}, + host: "aaa.bbb.ccc", + path: "", + shouldMatch: true, + }, + { + title: "Host route, wrong host in request URL", + route: new(Route).Host("aaa.bbb.ccc"), + request: newRequest("GET", "http://aaa.222.ccc/111/222/333"), + vars: map[string]string{}, + host: "aaa.bbb.ccc", + path: "", + shouldMatch: false, + }, + { + title: "Host route with port, match", + route: new(Route).Host("aaa.bbb.ccc:1234"), + request: newRequest("GET", "http://aaa.bbb.ccc:1234/111/222/333"), + vars: map[string]string{}, + host: "aaa.bbb.ccc:1234", + path: "", + shouldMatch: true, + }, + { + title: "Host route with port, wrong port in request URL", + route: new(Route).Host("aaa.bbb.ccc:1234"), + request: newRequest("GET", "http://aaa.bbb.ccc:9999/111/222/333"), + vars: map[string]string{}, + host: "aaa.bbb.ccc:1234", + path: "", + shouldMatch: false, + }, + { + title: "Host route, match with host in request header", + route: new(Route).Host("aaa.bbb.ccc"), + request: newRequestHost("GET", "/111/222/333", "aaa.bbb.ccc"), + vars: map[string]string{}, + host: "aaa.bbb.ccc", + path: "", + shouldMatch: true, + }, + { + title: "Host route, wrong host in request header", + route: new(Route).Host("aaa.bbb.ccc"), + request: newRequestHost("GET", "/111/222/333", "aaa.222.ccc"), + vars: map[string]string{}, + host: "aaa.bbb.ccc", + path: "", + shouldMatch: false, + }, + // BUG {new(Route).Host("aaa.bbb.ccc:1234"), newRequestHost("GET", "/111/222/333", "aaa.bbb.ccc:1234"), map[string]string{}, "aaa.bbb.ccc:1234", "", true}, + { + title: "Host route with port, wrong host in request header", + route: new(Route).Host("aaa.bbb.ccc:1234"), + request: newRequestHost("GET", "/111/222/333", "aaa.bbb.ccc:9999"), + vars: map[string]string{}, + host: "aaa.bbb.ccc:1234", + path: "", + shouldMatch: false, + }, + { + title: "Host route with pattern, match", + route: new(Route).Host("aaa.{v1:[a-z]{3}}.ccc"), + request: newRequest("GET", "http://aaa.bbb.ccc/111/222/333"), + vars: map[string]string{"v1": "bbb"}, + host: "aaa.bbb.ccc", + path: "", + shouldMatch: true, + }, + { + title: "Host route with pattern, wrong host in request URL", + route: new(Route).Host("aaa.{v1:[a-z]{3}}.ccc"), + request: newRequest("GET", "http://aaa.222.ccc/111/222/333"), + vars: map[string]string{"v1": "bbb"}, + host: "aaa.bbb.ccc", + path: "", + shouldMatch: false, + }, + { + title: "Host route with multiple patterns, match", + route: new(Route).Host("{v1:[a-z]{3}}.{v2:[a-z]{3}}.{v3:[a-z]{3}}"), + request: newRequest("GET", "http://aaa.bbb.ccc/111/222/333"), + vars: map[string]string{"v1": "aaa", "v2": "bbb", "v3": "ccc"}, + host: "aaa.bbb.ccc", + path: "", + shouldMatch: true, + }, + { + title: "Host route with multiple patterns, wrong host in request URL", + route: new(Route).Host("{v1:[a-z]{3}}.{v2:[a-z]{3}}.{v3:[a-z]{3}}"), + request: newRequest("GET", "http://aaa.222.ccc/111/222/333"), + vars: map[string]string{"v1": "aaa", "v2": "bbb", "v3": "ccc"}, + host: "aaa.bbb.ccc", + path: "", + shouldMatch: false, + }, + } + for _, test := range tests { + testRoute(t, test) + } +} + +func TestPath(t *testing.T) { + tests := []routeTest{ + { + title: "Path route, match", + route: new(Route).Path("/111/222/333"), + request: newRequest("GET", "http://localhost/111/222/333"), + vars: map[string]string{}, + host: "", + path: "/111/222/333", + shouldMatch: true, + }, + { + title: "Path route, match with trailing slash in request and path", + route: new(Route).Path("/111/"), + request: newRequest("GET", "http://localhost/111/"), + vars: map[string]string{}, + host: "", + path: "/111/", + shouldMatch: true, + }, + { + title: "Path route, do not match with trailing slash in path", + route: new(Route).Path("/111/"), + request: newRequest("GET", "http://localhost/111"), + vars: map[string]string{}, + host: "", + path: "/111", + shouldMatch: false, + }, + { + title: "Path route, do not match with trailing slash in request", + route: new(Route).Path("/111"), + request: newRequest("GET", "http://localhost/111/"), + vars: map[string]string{}, + host: "", + path: "/111/", + shouldMatch: false, + }, + { + title: "Path route, wrong path in request in request URL", + route: new(Route).Path("/111/222/333"), + request: newRequest("GET", "http://localhost/1/2/3"), + vars: map[string]string{}, + host: "", + path: "/111/222/333", + shouldMatch: false, + }, + { + title: "Path route with pattern, match", + route: new(Route).Path("/111/{v1:[0-9]{3}}/333"), + request: newRequest("GET", "http://localhost/111/222/333"), + vars: map[string]string{"v1": "222"}, + host: "", + path: "/111/222/333", + shouldMatch: true, + }, + { + title: "Path route with pattern, URL in request does not match", + route: new(Route).Path("/111/{v1:[0-9]{3}}/333"), + request: newRequest("GET", "http://localhost/111/aaa/333"), + vars: map[string]string{"v1": "222"}, + host: "", + path: "/111/222/333", + shouldMatch: false, + }, + { + title: "Path route with multiple patterns, match", + route: new(Route).Path("/{v1:[0-9]{3}}/{v2:[0-9]{3}}/{v3:[0-9]{3}}"), + request: newRequest("GET", "http://localhost/111/222/333"), + vars: map[string]string{"v1": "111", "v2": "222", "v3": "333"}, + host: "", + path: "/111/222/333", + shouldMatch: true, + }, + { + title: "Path route with multiple patterns, URL in request does not match", + route: new(Route).Path("/{v1:[0-9]{3}}/{v2:[0-9]{3}}/{v3:[0-9]{3}}"), + request: newRequest("GET", "http://localhost/111/aaa/333"), + vars: map[string]string{"v1": "111", "v2": "222", "v3": "333"}, + host: "", + path: "/111/222/333", + shouldMatch: false, + }, + } + + for _, test := range tests { + testRoute(t, test) + } +} + +func TestPathPrefix(t *testing.T) { + tests := []routeTest{ + { + title: "PathPrefix route, match", + route: new(Route).PathPrefix("/111"), + request: newRequest("GET", "http://localhost/111/222/333"), + vars: map[string]string{}, + host: "", + path: "/111", + shouldMatch: true, + }, + { + title: "PathPrefix route, match substring", + route: new(Route).PathPrefix("/1"), + request: newRequest("GET", "http://localhost/111/222/333"), + vars: map[string]string{}, + host: "", + path: "/1", + shouldMatch: true, + }, + { + title: "PathPrefix route, URL prefix in request does not match", + route: new(Route).PathPrefix("/111"), + request: newRequest("GET", "http://localhost/1/2/3"), + vars: map[string]string{}, + host: "", + path: "/111", + shouldMatch: false, + }, + { + title: "PathPrefix route with pattern, match", + route: new(Route).PathPrefix("/111/{v1:[0-9]{3}}"), + request: newRequest("GET", "http://localhost/111/222/333"), + vars: map[string]string{"v1": "222"}, + host: "", + path: "/111/222", + shouldMatch: true, + }, + { + title: "PathPrefix route with pattern, URL prefix in request does not match", + route: new(Route).PathPrefix("/111/{v1:[0-9]{3}}"), + request: newRequest("GET", "http://localhost/111/aaa/333"), + vars: map[string]string{"v1": "222"}, + host: "", + path: "/111/222", + shouldMatch: false, + }, + { + title: "PathPrefix route with multiple patterns, match", + route: new(Route).PathPrefix("/{v1:[0-9]{3}}/{v2:[0-9]{3}}"), + request: newRequest("GET", "http://localhost/111/222/333"), + vars: map[string]string{"v1": "111", "v2": "222"}, + host: "", + path: "/111/222", + shouldMatch: true, + }, + { + title: "PathPrefix route with multiple patterns, URL prefix in request does not match", + route: new(Route).PathPrefix("/{v1:[0-9]{3}}/{v2:[0-9]{3}}"), + request: newRequest("GET", "http://localhost/111/aaa/333"), + vars: map[string]string{"v1": "111", "v2": "222"}, + host: "", + path: "/111/222", + shouldMatch: false, + }, + } + + for _, test := range tests { + testRoute(t, test) + } +} + +func TestHostPath(t *testing.T) { + tests := []routeTest{ + { + title: "Host and Path route, match", + route: new(Route).Host("aaa.bbb.ccc").Path("/111/222/333"), + request: newRequest("GET", "http://aaa.bbb.ccc/111/222/333"), + vars: map[string]string{}, + host: "", + path: "", + shouldMatch: true, + }, + { + title: "Host and Path route, wrong host in request URL", + route: new(Route).Host("aaa.bbb.ccc").Path("/111/222/333"), + request: newRequest("GET", "http://aaa.222.ccc/111/222/333"), + vars: map[string]string{}, + host: "", + path: "", + shouldMatch: false, + }, + { + title: "Host and Path route with pattern, match", + route: new(Route).Host("aaa.{v1:[a-z]{3}}.ccc").Path("/111/{v2:[0-9]{3}}/333"), + request: newRequest("GET", "http://aaa.bbb.ccc/111/222/333"), + vars: map[string]string{"v1": "bbb", "v2": "222"}, + host: "aaa.bbb.ccc", + path: "/111/222/333", + shouldMatch: true, + }, + { + title: "Host and Path route with pattern, URL in request does not match", + route: new(Route).Host("aaa.{v1:[a-z]{3}}.ccc").Path("/111/{v2:[0-9]{3}}/333"), + request: newRequest("GET", "http://aaa.222.ccc/111/222/333"), + vars: map[string]string{"v1": "bbb", "v2": "222"}, + host: "aaa.bbb.ccc", + path: "/111/222/333", + shouldMatch: false, + }, + { + title: "Host and Path route with multiple patterns, match", + route: new(Route).Host("{v1:[a-z]{3}}.{v2:[a-z]{3}}.{v3:[a-z]{3}}").Path("/{v4:[0-9]{3}}/{v5:[0-9]{3}}/{v6:[0-9]{3}}"), + request: newRequest("GET", "http://aaa.bbb.ccc/111/222/333"), + vars: map[string]string{"v1": "aaa", "v2": "bbb", "v3": "ccc", "v4": "111", "v5": "222", "v6": "333"}, + host: "aaa.bbb.ccc", + path: "/111/222/333", + shouldMatch: true, + }, + { + title: "Host and Path route with multiple patterns, URL in request does not match", + route: new(Route).Host("{v1:[a-z]{3}}.{v2:[a-z]{3}}.{v3:[a-z]{3}}").Path("/{v4:[0-9]{3}}/{v5:[0-9]{3}}/{v6:[0-9]{3}}"), + request: newRequest("GET", "http://aaa.222.ccc/111/222/333"), + vars: map[string]string{"v1": "aaa", "v2": "bbb", "v3": "ccc", "v4": "111", "v5": "222", "v6": "333"}, + host: "aaa.bbb.ccc", + path: "/111/222/333", + shouldMatch: false, + }, + } + + for _, test := range tests { + testRoute(t, test) + } +} + +func TestHeaders(t *testing.T) { + // newRequestHeaders creates a new request with a method, url, and headers + newRequestHeaders := func(method, url string, headers map[string]string) *http.Request { + req, err := http.NewRequest(method, url, nil) + if err != nil { + panic(err) + } + for k, v := range headers { + req.Header.Add(k, v) + } + return req + } + + tests := []routeTest{ + { + title: "Headers route, match", + route: new(Route).Headers("foo", "bar", "baz", "ding"), + request: newRequestHeaders("GET", "http://localhost", map[string]string{"foo": "bar", "baz": "ding"}), + vars: map[string]string{}, + host: "", + path: "", + shouldMatch: true, + }, + { + title: "Headers route, bad header values", + route: new(Route).Headers("foo", "bar", "baz", "ding"), + request: newRequestHeaders("GET", "http://localhost", map[string]string{"foo": "bar", "baz": "dong"}), + vars: map[string]string{}, + host: "", + path: "", + shouldMatch: false, + }, + } + + for _, test := range tests { + testRoute(t, test) + } + +} + +func TestMethods(t *testing.T) { + tests := []routeTest{ + { + title: "Methods route, match GET", + route: new(Route).Methods("GET", "POST"), + request: newRequest("GET", "http://localhost"), + vars: map[string]string{}, + host: "", + path: "", + shouldMatch: true, + }, + { + title: "Methods route, match POST", + route: new(Route).Methods("GET", "POST"), + request: newRequest("POST", "http://localhost"), + vars: map[string]string{}, + host: "", + path: "", + shouldMatch: true, + }, + { + title: "Methods route, bad method", + route: new(Route).Methods("GET", "POST"), + request: newRequest("PUT", "http://localhost"), + vars: map[string]string{}, + host: "", + path: "", + shouldMatch: false, + }, + } + + for _, test := range tests { + testRoute(t, test) + } +} + +func TestQueries(t *testing.T) { + tests := []routeTest{ + { + title: "Queries route, match", + route: new(Route).Queries("foo", "bar", "baz", "ding"), + request: newRequest("GET", "http://localhost?foo=bar&baz=ding"), + vars: map[string]string{}, + host: "", + path: "", + shouldMatch: true, + }, + { + title: "Queries route, match with a query string", + route: new(Route).Host("www.example.com").Path("/api").Queries("foo", "bar", "baz", "ding"), + request: newRequest("GET", "http://www.example.com/api?foo=bar&baz=ding"), + vars: map[string]string{}, + host: "", + path: "", + shouldMatch: true, + }, + { + title: "Queries route, match with a query string out of order", + route: new(Route).Host("www.example.com").Path("/api").Queries("foo", "bar", "baz", "ding"), + request: newRequest("GET", "http://www.example.com/api?baz=ding&foo=bar"), + vars: map[string]string{}, + host: "", + path: "", + shouldMatch: true, + }, + { + title: "Queries route, bad query", + route: new(Route).Queries("foo", "bar", "baz", "ding"), + request: newRequest("GET", "http://localhost?foo=bar&baz=dong"), + vars: map[string]string{}, + host: "", + path: "", + shouldMatch: false, + }, + { + title: "Queries route with pattern, match", + route: new(Route).Queries("foo", "{v1}"), + request: newRequest("GET", "http://localhost?foo=bar"), + vars: map[string]string{"v1": "bar"}, + host: "", + path: "", + shouldMatch: true, + }, + { + title: "Queries route with multiple patterns, match", + route: new(Route).Queries("foo", "{v1}", "baz", "{v2}"), + request: newRequest("GET", "http://localhost?foo=bar&baz=ding"), + vars: map[string]string{"v1": "bar", "v2": "ding"}, + host: "", + path: "", + shouldMatch: true, + }, + { + title: "Queries route with regexp pattern, match", + route: new(Route).Queries("foo", "{v1:[0-9]+}"), + request: newRequest("GET", "http://localhost?foo=10"), + vars: map[string]string{"v1": "10"}, + host: "", + path: "", + shouldMatch: true, + }, + { + title: "Queries route with regexp pattern, regexp does not match", + route: new(Route).Queries("foo", "{v1:[0-9]+}"), + request: newRequest("GET", "http://localhost?foo=a"), + vars: map[string]string{}, + host: "", + path: "", + shouldMatch: false, + }, + } + + for _, test := range tests { + testRoute(t, test) + } +} + +func TestSchemes(t *testing.T) { + tests := []routeTest{ + // Schemes + { + title: "Schemes route, match https", + route: new(Route).Schemes("https", "ftp"), + request: newRequest("GET", "https://localhost"), + vars: map[string]string{}, + host: "", + path: "", + shouldMatch: true, + }, + { + title: "Schemes route, match ftp", + route: new(Route).Schemes("https", "ftp"), + request: newRequest("GET", "ftp://localhost"), + vars: map[string]string{}, + host: "", + path: "", + shouldMatch: true, + }, + { + title: "Schemes route, bad scheme", + route: new(Route).Schemes("https", "ftp"), + request: newRequest("GET", "http://localhost"), + vars: map[string]string{}, + host: "", + path: "", + shouldMatch: false, + }, + } + for _, test := range tests { + testRoute(t, test) + } +} + +func TestMatcherFunc(t *testing.T) { + m := func(r *http.Request, m *RouteMatch) bool { + if r.URL.Host == "aaa.bbb.ccc" { + return true + } + return false + } + + tests := []routeTest{ + { + title: "MatchFunc route, match", + route: new(Route).MatcherFunc(m), + request: newRequest("GET", "http://aaa.bbb.ccc"), + vars: map[string]string{}, + host: "", + path: "", + shouldMatch: true, + }, + { + title: "MatchFunc route, non-match", + route: new(Route).MatcherFunc(m), + request: newRequest("GET", "http://aaa.222.ccc"), + vars: map[string]string{}, + host: "", + path: "", + shouldMatch: false, + }, + } + + for _, test := range tests { + testRoute(t, test) + } +} + +func TestSubRouter(t *testing.T) { + subrouter1 := new(Route).Host("{v1:[a-z]+}.google.com").Subrouter() + subrouter2 := new(Route).PathPrefix("/foo/{v1}").Subrouter() + + tests := []routeTest{ + { + route: subrouter1.Path("/{v2:[a-z]+}"), + request: newRequest("GET", "http://aaa.google.com/bbb"), + vars: map[string]string{"v1": "aaa", "v2": "bbb"}, + host: "aaa.google.com", + path: "/bbb", + shouldMatch: true, + }, + { + route: subrouter1.Path("/{v2:[a-z]+}"), + request: newRequest("GET", "http://111.google.com/111"), + vars: map[string]string{"v1": "aaa", "v2": "bbb"}, + host: "aaa.google.com", + path: "/bbb", + shouldMatch: false, + }, + { + route: subrouter2.Path("/baz/{v2}"), + request: newRequest("GET", "http://localhost/foo/bar/baz/ding"), + vars: map[string]string{"v1": "bar", "v2": "ding"}, + host: "", + path: "/foo/bar/baz/ding", + shouldMatch: true, + }, + { + route: subrouter2.Path("/baz/{v2}"), + request: newRequest("GET", "http://localhost/foo/bar"), + vars: map[string]string{"v1": "bar", "v2": "ding"}, + host: "", + path: "/foo/bar/baz/ding", + shouldMatch: false, + }, + } + + for _, test := range tests { + testRoute(t, test) + } +} + +func TestNamedRoutes(t *testing.T) { + r1 := NewRouter() + r1.NewRoute().Name("a") + r1.NewRoute().Name("b") + r1.NewRoute().Name("c") + + r2 := r1.NewRoute().Subrouter() + r2.NewRoute().Name("d") + r2.NewRoute().Name("e") + r2.NewRoute().Name("f") + + r3 := r2.NewRoute().Subrouter() + r3.NewRoute().Name("g") + r3.NewRoute().Name("h") + r3.NewRoute().Name("i") + + if r1.namedRoutes == nil || len(r1.namedRoutes) != 9 { + t.Errorf("Expected 9 named routes, got %v", r1.namedRoutes) + } else if r1.Get("i") == nil { + t.Errorf("Subroute name not registered") + } +} + +func TestStrictSlash(t *testing.T) { + r := NewRouter() + r.StrictSlash(true) + + tests := []routeTest{ + { + title: "Redirect path without slash", + route: r.NewRoute().Path("/111/"), + request: newRequest("GET", "http://localhost/111"), + vars: map[string]string{}, + host: "", + path: "/111/", + shouldMatch: true, + shouldRedirect: true, + }, + { + title: "Do not redirect path with slash", + route: r.NewRoute().Path("/111/"), + request: newRequest("GET", "http://localhost/111/"), + vars: map[string]string{}, + host: "", + path: "/111/", + shouldMatch: true, + shouldRedirect: false, + }, + { + title: "Redirect path with slash", + route: r.NewRoute().Path("/111"), + request: newRequest("GET", "http://localhost/111/"), + vars: map[string]string{}, + host: "", + path: "/111", + shouldMatch: true, + shouldRedirect: true, + }, + { + title: "Do not redirect path without slash", + route: r.NewRoute().Path("/111"), + request: newRequest("GET", "http://localhost/111"), + vars: map[string]string{}, + host: "", + path: "/111", + shouldMatch: true, + shouldRedirect: false, + }, + { + title: "Propagate StrictSlash to subrouters", + route: r.NewRoute().PathPrefix("/static/").Subrouter().Path("/images/"), + request: newRequest("GET", "http://localhost/static/images"), + vars: map[string]string{}, + host: "", + path: "/static/images/", + shouldMatch: true, + shouldRedirect: true, + }, + { + title: "Ignore StrictSlash for path prefix", + route: r.NewRoute().PathPrefix("/static/"), + request: newRequest("GET", "http://localhost/static/logo.png"), + vars: map[string]string{}, + host: "", + path: "/static/", + shouldMatch: true, + shouldRedirect: false, + }, + } + + for _, test := range tests { + testRoute(t, test) + } +} + +// ---------------------------------------------------------------------------- +// Helpers +// ---------------------------------------------------------------------------- + +func getRouteTemplate(route *Route) string { + host, path := "none", "none" + if route.regexp != nil { + if route.regexp.host != nil { + host = route.regexp.host.template + } + if route.regexp.path != nil { + path = route.regexp.path.template + } + } + return fmt.Sprintf("Host: %v, Path: %v", host, path) +} + +func testRoute(t *testing.T, test routeTest) { + request := test.request + route := test.route + vars := test.vars + shouldMatch := test.shouldMatch + host := test.host + path := test.path + url := test.host + test.path + shouldRedirect := test.shouldRedirect + + var match RouteMatch + ok := route.Match(request, &match) + if ok != shouldMatch { + msg := "Should match" + if !shouldMatch { + msg = "Should not match" + } + t.Errorf("(%v) %v:\nRoute: %#v\nRequest: %#v\nVars: %v\n", test.title, msg, route, request, vars) + return + } + if shouldMatch { + if test.vars != nil && !stringMapEqual(test.vars, match.Vars) { + t.Errorf("(%v) Vars not equal: expected %v, got %v", test.title, vars, match.Vars) + return + } + if host != "" { + u, _ := test.route.URLHost(mapToPairs(match.Vars)...) + if host != u.Host { + t.Errorf("(%v) URLHost not equal: expected %v, got %v -- %v", test.title, host, u.Host, getRouteTemplate(route)) + return + } + } + if path != "" { + u, _ := route.URLPath(mapToPairs(match.Vars)...) + if path != u.Path { + t.Errorf("(%v) URLPath not equal: expected %v, got %v -- %v", test.title, path, u.Path, getRouteTemplate(route)) + return + } + } + if url != "" { + u, _ := route.URL(mapToPairs(match.Vars)...) + if url != u.Host+u.Path { + t.Errorf("(%v) URL not equal: expected %v, got %v -- %v", test.title, url, u.Host+u.Path, getRouteTemplate(route)) + return + } + } + if shouldRedirect && match.Handler == nil { + t.Errorf("(%v) Did not redirect", test.title) + return + } + if !shouldRedirect && match.Handler != nil { + t.Errorf("(%v) Unexpected redirect", test.title) + return + } + } +} + +// Tests that the context is cleared or not cleared properly depending on +// the configuration of the router +func TestKeepContext(t *testing.T) { + func1 := func(w http.ResponseWriter, r *http.Request) {} + + r := NewRouter() + r.HandleFunc("/", func1).Name("func1") + + req, _ := http.NewRequest("GET", "http://localhost/", nil) + context.Set(req, "t", 1) + + res := new(http.ResponseWriter) + r.ServeHTTP(*res, req) + + if _, ok := context.GetOk(req, "t"); ok { + t.Error("Context should have been cleared at end of request") + } + + r.KeepContext = true + + req, _ = http.NewRequest("GET", "http://localhost/", nil) + context.Set(req, "t", 1) + + r.ServeHTTP(*res, req) + if _, ok := context.GetOk(req, "t"); !ok { + t.Error("Context should NOT have been cleared at end of request") + } + +} + +type TestA301ResponseWriter struct { + hh http.Header + status int +} + +func (ho TestA301ResponseWriter) Header() http.Header { + return http.Header(ho.hh) +} + +func (ho TestA301ResponseWriter) Write(b []byte) (int, error) { + return 0, nil +} + +func (ho TestA301ResponseWriter) WriteHeader(code int) { + ho.status = code +} + +func Test301Redirect(t *testing.T) { + m := make(http.Header) + + func1 := func(w http.ResponseWriter, r *http.Request) {} + func2 := func(w http.ResponseWriter, r *http.Request) {} + + r := NewRouter() + r.HandleFunc("/api/", func2).Name("func2") + r.HandleFunc("/", func1).Name("func1") + + req, _ := http.NewRequest("GET", "http://localhost//api/?abc=def", nil) + + res := TestA301ResponseWriter{ + hh: m, + status: 0, + } + r.ServeHTTP(&res, req) + + if "http://localhost/api/?abc=def" != res.hh["Location"][0] { + t.Errorf("Should have complete URL with query string") + } +} + +// https://plus.google.com/101022900381697718949/posts/eWy6DjFJ6uW +func TestSubrouterHeader(t *testing.T) { + expected := "func1 response" + func1 := func(w http.ResponseWriter, r *http.Request) { + fmt.Fprint(w, expected) + } + func2 := func(http.ResponseWriter, *http.Request) {} + + r := NewRouter() + s := r.Headers("SomeSpecialHeader", "").Subrouter() + s.HandleFunc("/", func1).Name("func1") + r.HandleFunc("/", func2).Name("func2") + + req, _ := http.NewRequest("GET", "http://localhost/", nil) + req.Header.Add("SomeSpecialHeader", "foo") + match := new(RouteMatch) + matched := r.Match(req, match) + if !matched { + t.Errorf("Should match request") + } + if match.Route.GetName() != "func1" { + t.Errorf("Expecting func1 handler, got %s", match.Route.GetName()) + } + resp := NewRecorder() + match.Handler.ServeHTTP(resp, req) + if resp.Body.String() != expected { + t.Errorf("Expecting %q", expected) + } +} + +// mapToPairs converts a string map to a slice of string pairs +func mapToPairs(m map[string]string) []string { + var i int + p := make([]string, len(m)*2) + for k, v := range m { + p[i] = k + p[i+1] = v + i += 2 + } + return p +} + +// stringMapEqual checks the equality of two string maps +func stringMapEqual(m1, m2 map[string]string) bool { + nil1 := m1 == nil + nil2 := m2 == nil + if nil1 != nil2 || len(m1) != len(m2) { + return false + } + for k, v := range m1 { + if v != m2[k] { + return false + } + } + return true +} + +// newRequest is a helper function to create a new request with a method and url +func newRequest(method, url string) *http.Request { + req, err := http.NewRequest(method, url, nil) + if err != nil { + panic(err) + } + return req +} diff --git a/Godeps/_workspace/src/github.com/gorilla/mux/old_test.go b/Godeps/_workspace/src/github.com/gorilla/mux/old_test.go new file mode 100644 index 00000000000..1f7c190c0f9 --- /dev/null +++ b/Godeps/_workspace/src/github.com/gorilla/mux/old_test.go @@ -0,0 +1,714 @@ +// Old tests ported to Go1. This is a mess. Want to drop it one day. + +// Copyright 2011 Gorilla Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package mux + +import ( + "bytes" + "net/http" + "testing" +) + +// ---------------------------------------------------------------------------- +// ResponseRecorder +// ---------------------------------------------------------------------------- +// Copyright 2009 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// ResponseRecorder is an implementation of http.ResponseWriter that +// records its mutations for later inspection in tests. +type ResponseRecorder struct { + Code int // the HTTP response code from WriteHeader + HeaderMap http.Header // the HTTP response headers + Body *bytes.Buffer // if non-nil, the bytes.Buffer to append written data to + Flushed bool +} + +// NewRecorder returns an initialized ResponseRecorder. +func NewRecorder() *ResponseRecorder { + return &ResponseRecorder{ + HeaderMap: make(http.Header), + Body: new(bytes.Buffer), + } +} + +// DefaultRemoteAddr is the default remote address to return in RemoteAddr if +// an explicit DefaultRemoteAddr isn't set on ResponseRecorder. +const DefaultRemoteAddr = "1.2.3.4" + +// Header returns the response headers. +func (rw *ResponseRecorder) Header() http.Header { + return rw.HeaderMap +} + +// Write always succeeds and writes to rw.Body, if not nil. +func (rw *ResponseRecorder) Write(buf []byte) (int, error) { + if rw.Body != nil { + rw.Body.Write(buf) + } + if rw.Code == 0 { + rw.Code = http.StatusOK + } + return len(buf), nil +} + +// WriteHeader sets rw.Code. +func (rw *ResponseRecorder) WriteHeader(code int) { + rw.Code = code +} + +// Flush sets rw.Flushed to true. +func (rw *ResponseRecorder) Flush() { + rw.Flushed = true +} + +// ---------------------------------------------------------------------------- + +func TestRouteMatchers(t *testing.T) { + var scheme, host, path, query, method string + var headers map[string]string + var resultVars map[bool]map[string]string + + router := NewRouter() + router.NewRoute().Host("{var1}.google.com"). + Path("/{var2:[a-z]+}/{var3:[0-9]+}"). + Queries("foo", "bar"). + Methods("GET"). + Schemes("https"). + Headers("x-requested-with", "XMLHttpRequest") + router.NewRoute().Host("www.{var4}.com"). + PathPrefix("/foo/{var5:[a-z]+}/{var6:[0-9]+}"). + Queries("baz", "ding"). + Methods("POST"). + Schemes("http"). + Headers("Content-Type", "application/json") + + reset := func() { + // Everything match. + scheme = "https" + host = "www.google.com" + path = "/product/42" + query = "?foo=bar" + method = "GET" + headers = map[string]string{"X-Requested-With": "XMLHttpRequest"} + resultVars = map[bool]map[string]string{ + true: {"var1": "www", "var2": "product", "var3": "42"}, + false: {}, + } + } + + reset2 := func() { + // Everything match. + scheme = "http" + host = "www.google.com" + path = "/foo/product/42/path/that/is/ignored" + query = "?baz=ding" + method = "POST" + headers = map[string]string{"Content-Type": "application/json"} + resultVars = map[bool]map[string]string{ + true: {"var4": "google", "var5": "product", "var6": "42"}, + false: {}, + } + } + + match := func(shouldMatch bool) { + url := scheme + "://" + host + path + query + request, _ := http.NewRequest(method, url, nil) + for key, value := range headers { + request.Header.Add(key, value) + } + + var routeMatch RouteMatch + matched := router.Match(request, &routeMatch) + if matched != shouldMatch { + // Need better messages. :) + if matched { + t.Errorf("Should match.") + } else { + t.Errorf("Should not match.") + } + } + + if matched { + currentRoute := routeMatch.Route + if currentRoute == nil { + t.Errorf("Expected a current route.") + } + vars := routeMatch.Vars + expectedVars := resultVars[shouldMatch] + if len(vars) != len(expectedVars) { + t.Errorf("Expected vars: %v Got: %v.", expectedVars, vars) + } + for name, value := range vars { + if expectedVars[name] != value { + t.Errorf("Expected vars: %v Got: %v.", expectedVars, vars) + } + } + } + } + + // 1st route -------------------------------------------------------------- + + // Everything match. + reset() + match(true) + + // Scheme doesn't match. + reset() + scheme = "http" + match(false) + + // Host doesn't match. + reset() + host = "www.mygoogle.com" + match(false) + + // Path doesn't match. + reset() + path = "/product/notdigits" + match(false) + + // Query doesn't match. + reset() + query = "?foo=baz" + match(false) + + // Method doesn't match. + reset() + method = "POST" + match(false) + + // Header doesn't match. + reset() + headers = map[string]string{} + match(false) + + // Everything match, again. + reset() + match(true) + + // 2nd route -------------------------------------------------------------- + + // Everything match. + reset2() + match(true) + + // Scheme doesn't match. + reset2() + scheme = "https" + match(false) + + // Host doesn't match. + reset2() + host = "sub.google.com" + match(false) + + // Path doesn't match. + reset2() + path = "/bar/product/42" + match(false) + + // Query doesn't match. + reset2() + query = "?foo=baz" + match(false) + + // Method doesn't match. + reset2() + method = "GET" + match(false) + + // Header doesn't match. + reset2() + headers = map[string]string{} + match(false) + + // Everything match, again. + reset2() + match(true) +} + +type headerMatcherTest struct { + matcher headerMatcher + headers map[string]string + result bool +} + +var headerMatcherTests = []headerMatcherTest{ + { + matcher: headerMatcher(map[string]string{"x-requested-with": "XMLHttpRequest"}), + headers: map[string]string{"X-Requested-With": "XMLHttpRequest"}, + result: true, + }, + { + matcher: headerMatcher(map[string]string{"x-requested-with": ""}), + headers: map[string]string{"X-Requested-With": "anything"}, + result: true, + }, + { + matcher: headerMatcher(map[string]string{"x-requested-with": "XMLHttpRequest"}), + headers: map[string]string{}, + result: false, + }, +} + +type hostMatcherTest struct { + matcher *Route + url string + vars map[string]string + result bool +} + +var hostMatcherTests = []hostMatcherTest{ + { + matcher: NewRouter().NewRoute().Host("{foo:[a-z][a-z][a-z]}.{bar:[a-z][a-z][a-z]}.{baz:[a-z][a-z][a-z]}"), + url: "http://abc.def.ghi/", + vars: map[string]string{"foo": "abc", "bar": "def", "baz": "ghi"}, + result: true, + }, + { + matcher: NewRouter().NewRoute().Host("{foo:[a-z][a-z][a-z]}.{bar:[a-z][a-z][a-z]}.{baz:[a-z][a-z][a-z]}"), + url: "http://a.b.c/", + vars: map[string]string{"foo": "abc", "bar": "def", "baz": "ghi"}, + result: false, + }, +} + +type methodMatcherTest struct { + matcher methodMatcher + method string + result bool +} + +var methodMatcherTests = []methodMatcherTest{ + { + matcher: methodMatcher([]string{"GET", "POST", "PUT"}), + method: "GET", + result: true, + }, + { + matcher: methodMatcher([]string{"GET", "POST", "PUT"}), + method: "POST", + result: true, + }, + { + matcher: methodMatcher([]string{"GET", "POST", "PUT"}), + method: "PUT", + result: true, + }, + { + matcher: methodMatcher([]string{"GET", "POST", "PUT"}), + method: "DELETE", + result: false, + }, +} + +type pathMatcherTest struct { + matcher *Route + url string + vars map[string]string + result bool +} + +var pathMatcherTests = []pathMatcherTest{ + { + matcher: NewRouter().NewRoute().Path("/{foo:[0-9][0-9][0-9]}/{bar:[0-9][0-9][0-9]}/{baz:[0-9][0-9][0-9]}"), + url: "http://localhost:8080/123/456/789", + vars: map[string]string{"foo": "123", "bar": "456", "baz": "789"}, + result: true, + }, + { + matcher: NewRouter().NewRoute().Path("/{foo:[0-9][0-9][0-9]}/{bar:[0-9][0-9][0-9]}/{baz:[0-9][0-9][0-9]}"), + url: "http://localhost:8080/1/2/3", + vars: map[string]string{"foo": "123", "bar": "456", "baz": "789"}, + result: false, + }, +} + +type schemeMatcherTest struct { + matcher schemeMatcher + url string + result bool +} + +var schemeMatcherTests = []schemeMatcherTest{ + { + matcher: schemeMatcher([]string{"http", "https"}), + url: "http://localhost:8080/", + result: true, + }, + { + matcher: schemeMatcher([]string{"http", "https"}), + url: "https://localhost:8080/", + result: true, + }, + { + matcher: schemeMatcher([]string{"https"}), + url: "http://localhost:8080/", + result: false, + }, + { + matcher: schemeMatcher([]string{"http"}), + url: "https://localhost:8080/", + result: false, + }, +} + +type urlBuildingTest struct { + route *Route + vars []string + url string +} + +var urlBuildingTests = []urlBuildingTest{ + { + route: new(Route).Host("foo.domain.com"), + vars: []string{}, + url: "http://foo.domain.com", + }, + { + route: new(Route).Host("{subdomain}.domain.com"), + vars: []string{"subdomain", "bar"}, + url: "http://bar.domain.com", + }, + { + route: new(Route).Host("foo.domain.com").Path("/articles"), + vars: []string{}, + url: "http://foo.domain.com/articles", + }, + { + route: new(Route).Path("/articles"), + vars: []string{}, + url: "/articles", + }, + { + route: new(Route).Path("/articles/{category}/{id:[0-9]+}"), + vars: []string{"category", "technology", "id", "42"}, + url: "/articles/technology/42", + }, + { + route: new(Route).Host("{subdomain}.domain.com").Path("/articles/{category}/{id:[0-9]+}"), + vars: []string{"subdomain", "foo", "category", "technology", "id", "42"}, + url: "http://foo.domain.com/articles/technology/42", + }, +} + +func TestHeaderMatcher(t *testing.T) { + for _, v := range headerMatcherTests { + request, _ := http.NewRequest("GET", "http://localhost:8080/", nil) + for key, value := range v.headers { + request.Header.Add(key, value) + } + var routeMatch RouteMatch + result := v.matcher.Match(request, &routeMatch) + if result != v.result { + if v.result { + t.Errorf("%#v: should match %v.", v.matcher, request.Header) + } else { + t.Errorf("%#v: should not match %v.", v.matcher, request.Header) + } + } + } +} + +func TestHostMatcher(t *testing.T) { + for _, v := range hostMatcherTests { + request, _ := http.NewRequest("GET", v.url, nil) + var routeMatch RouteMatch + result := v.matcher.Match(request, &routeMatch) + vars := routeMatch.Vars + if result != v.result { + if v.result { + t.Errorf("%#v: should match %v.", v.matcher, v.url) + } else { + t.Errorf("%#v: should not match %v.", v.matcher, v.url) + } + } + if result { + if len(vars) != len(v.vars) { + t.Errorf("%#v: vars length should be %v, got %v.", v.matcher, len(v.vars), len(vars)) + } + for name, value := range vars { + if v.vars[name] != value { + t.Errorf("%#v: expected value %v for key %v, got %v.", v.matcher, v.vars[name], name, value) + } + } + } else { + if len(vars) != 0 { + t.Errorf("%#v: vars length should be 0, got %v.", v.matcher, len(vars)) + } + } + } +} + +func TestMethodMatcher(t *testing.T) { + for _, v := range methodMatcherTests { + request, _ := http.NewRequest(v.method, "http://localhost:8080/", nil) + var routeMatch RouteMatch + result := v.matcher.Match(request, &routeMatch) + if result != v.result { + if v.result { + t.Errorf("%#v: should match %v.", v.matcher, v.method) + } else { + t.Errorf("%#v: should not match %v.", v.matcher, v.method) + } + } + } +} + +func TestPathMatcher(t *testing.T) { + for _, v := range pathMatcherTests { + request, _ := http.NewRequest("GET", v.url, nil) + var routeMatch RouteMatch + result := v.matcher.Match(request, &routeMatch) + vars := routeMatch.Vars + if result != v.result { + if v.result { + t.Errorf("%#v: should match %v.", v.matcher, v.url) + } else { + t.Errorf("%#v: should not match %v.", v.matcher, v.url) + } + } + if result { + if len(vars) != len(v.vars) { + t.Errorf("%#v: vars length should be %v, got %v.", v.matcher, len(v.vars), len(vars)) + } + for name, value := range vars { + if v.vars[name] != value { + t.Errorf("%#v: expected value %v for key %v, got %v.", v.matcher, v.vars[name], name, value) + } + } + } else { + if len(vars) != 0 { + t.Errorf("%#v: vars length should be 0, got %v.", v.matcher, len(vars)) + } + } + } +} + +func TestSchemeMatcher(t *testing.T) { + for _, v := range schemeMatcherTests { + request, _ := http.NewRequest("GET", v.url, nil) + var routeMatch RouteMatch + result := v.matcher.Match(request, &routeMatch) + if result != v.result { + if v.result { + t.Errorf("%#v: should match %v.", v.matcher, v.url) + } else { + t.Errorf("%#v: should not match %v.", v.matcher, v.url) + } + } + } +} + +func TestUrlBuilding(t *testing.T) { + + for _, v := range urlBuildingTests { + u, _ := v.route.URL(v.vars...) + url := u.String() + if url != v.url { + t.Errorf("expected %v, got %v", v.url, url) + /* + reversePath := "" + reverseHost := "" + if v.route.pathTemplate != nil { + reversePath = v.route.pathTemplate.Reverse + } + if v.route.hostTemplate != nil { + reverseHost = v.route.hostTemplate.Reverse + } + + t.Errorf("%#v:\nexpected: %q\ngot: %q\nreverse path: %q\nreverse host: %q", v.route, v.url, url, reversePath, reverseHost) + */ + } + } + + ArticleHandler := func(w http.ResponseWriter, r *http.Request) { + } + + router := NewRouter() + router.HandleFunc("/articles/{category}/{id:[0-9]+}", ArticleHandler).Name("article") + + url, _ := router.Get("article").URL("category", "technology", "id", "42") + expected := "/articles/technology/42" + if url.String() != expected { + t.Errorf("Expected %v, got %v", expected, url.String()) + } +} + +func TestMatchedRouteName(t *testing.T) { + routeName := "stock" + router := NewRouter() + route := router.NewRoute().Path("/products/").Name(routeName) + + url := "http://www.domain.com/products/" + request, _ := http.NewRequest("GET", url, nil) + var rv RouteMatch + ok := router.Match(request, &rv) + + if !ok || rv.Route != route { + t.Errorf("Expected same route, got %+v.", rv.Route) + } + + retName := rv.Route.GetName() + if retName != routeName { + t.Errorf("Expected %q, got %q.", routeName, retName) + } +} + +func TestSubRouting(t *testing.T) { + // Example from docs. + router := NewRouter() + subrouter := router.NewRoute().Host("www.domain.com").Subrouter() + route := subrouter.NewRoute().Path("/products/").Name("products") + + url := "http://www.domain.com/products/" + request, _ := http.NewRequest("GET", url, nil) + var rv RouteMatch + ok := router.Match(request, &rv) + + if !ok || rv.Route != route { + t.Errorf("Expected same route, got %+v.", rv.Route) + } + + u, _ := router.Get("products").URL() + builtUrl := u.String() + // Yay, subroute aware of the domain when building! + if builtUrl != url { + t.Errorf("Expected %q, got %q.", url, builtUrl) + } +} + +func TestVariableNames(t *testing.T) { + route := new(Route).Host("{arg1}.domain.com").Path("/{arg1}/{arg2:[0-9]+}") + if route.err == nil { + t.Errorf("Expected error for duplicated variable names") + } +} + +func TestRedirectSlash(t *testing.T) { + var route *Route + var routeMatch RouteMatch + r := NewRouter() + + r.StrictSlash(false) + route = r.NewRoute() + if route.strictSlash != false { + t.Errorf("Expected false redirectSlash.") + } + + r.StrictSlash(true) + route = r.NewRoute() + if route.strictSlash != true { + t.Errorf("Expected true redirectSlash.") + } + + route = new(Route) + route.strictSlash = true + route.Path("/{arg1}/{arg2:[0-9]+}/") + request, _ := http.NewRequest("GET", "http://localhost/foo/123", nil) + routeMatch = RouteMatch{} + _ = route.Match(request, &routeMatch) + vars := routeMatch.Vars + if vars["arg1"] != "foo" { + t.Errorf("Expected foo.") + } + if vars["arg2"] != "123" { + t.Errorf("Expected 123.") + } + rsp := NewRecorder() + routeMatch.Handler.ServeHTTP(rsp, request) + if rsp.HeaderMap.Get("Location") != "http://localhost/foo/123/" { + t.Errorf("Expected redirect header.") + } + + route = new(Route) + route.strictSlash = true + route.Path("/{arg1}/{arg2:[0-9]+}") + request, _ = http.NewRequest("GET", "http://localhost/foo/123/", nil) + routeMatch = RouteMatch{} + _ = route.Match(request, &routeMatch) + vars = routeMatch.Vars + if vars["arg1"] != "foo" { + t.Errorf("Expected foo.") + } + if vars["arg2"] != "123" { + t.Errorf("Expected 123.") + } + rsp = NewRecorder() + routeMatch.Handler.ServeHTTP(rsp, request) + if rsp.HeaderMap.Get("Location") != "http://localhost/foo/123" { + t.Errorf("Expected redirect header.") + } +} + +// Test for the new regexp library, still not available in stable Go. +func TestNewRegexp(t *testing.T) { + var p *routeRegexp + var matches []string + + tests := map[string]map[string][]string{ + "/{foo:a{2}}": { + "/a": nil, + "/aa": {"aa"}, + "/aaa": nil, + "/aaaa": nil, + }, + "/{foo:a{2,}}": { + "/a": nil, + "/aa": {"aa"}, + "/aaa": {"aaa"}, + "/aaaa": {"aaaa"}, + }, + "/{foo:a{2,3}}": { + "/a": nil, + "/aa": {"aa"}, + "/aaa": {"aaa"}, + "/aaaa": nil, + }, + "/{foo:[a-z]{3}}/{bar:[a-z]{2}}": { + "/a": nil, + "/ab": nil, + "/abc": nil, + "/abcd": nil, + "/abc/ab": {"abc", "ab"}, + "/abc/abc": nil, + "/abcd/ab": nil, + }, + `/{foo:\w{3,}}/{bar:\d{2,}}`: { + "/a": nil, + "/ab": nil, + "/abc": nil, + "/abc/1": nil, + "/abc/12": {"abc", "12"}, + "/abcd/12": {"abcd", "12"}, + "/abcd/123": {"abcd", "123"}, + }, + } + + for pattern, paths := range tests { + p, _ = newRouteRegexp(pattern, false, false, false, false) + for path, result := range paths { + matches = p.regexp.FindStringSubmatch(path) + if result == nil { + if matches != nil { + t.Errorf("%v should not match %v.", pattern, path) + } + } else { + if len(matches) != len(result)+1 { + t.Errorf("Expected %v matches, got %v.", len(result)+1, len(matches)) + } else { + for k, v := range result { + if matches[k+1] != v { + t.Errorf("Expected %v, got %v.", v, matches[k+1]) + } + } + } + } + } + } +} diff --git a/Godeps/_workspace/src/github.com/gorilla/mux/regexp.go b/Godeps/_workspace/src/github.com/gorilla/mux/regexp.go new file mode 100644 index 00000000000..ef1db8ff98a --- /dev/null +++ b/Godeps/_workspace/src/github.com/gorilla/mux/regexp.go @@ -0,0 +1,274 @@ +// Copyright 2012 The Gorilla Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package mux + +import ( + "bytes" + "fmt" + "net/http" + "net/url" + "regexp" + "strings" +) + +// newRouteRegexp parses a route template and returns a routeRegexp, +// used to match a host, a path or a query string. +// +// It will extract named variables, assemble a regexp to be matched, create +// a "reverse" template to build URLs and compile regexps to validate variable +// values used in URL building. +// +// Previously we accepted only Python-like identifiers for variable +// names ([a-zA-Z_][a-zA-Z0-9_]*), but currently the only restriction is that +// name and pattern can't be empty, and names can't contain a colon. +func newRouteRegexp(tpl string, matchHost, matchPrefix, matchQuery, strictSlash bool) (*routeRegexp, error) { + // Check if it is well-formed. + idxs, errBraces := braceIndices(tpl) + if errBraces != nil { + return nil, errBraces + } + // Backup the original. + template := tpl + // Now let's parse it. + defaultPattern := "[^/]+" + if matchQuery { + defaultPattern = "[^?&]+" + matchPrefix, strictSlash = true, false + } else if matchHost { + defaultPattern = "[^.]+" + matchPrefix, strictSlash = false, false + } + if matchPrefix { + strictSlash = false + } + // Set a flag for strictSlash. + endSlash := false + if strictSlash && strings.HasSuffix(tpl, "/") { + tpl = tpl[:len(tpl)-1] + endSlash = true + } + varsN := make([]string, len(idxs)/2) + varsR := make([]*regexp.Regexp, len(idxs)/2) + pattern := bytes.NewBufferString("") + if !matchQuery { + pattern.WriteByte('^') + } + reverse := bytes.NewBufferString("") + var end int + var err error + for i := 0; i < len(idxs); i += 2 { + // Set all values we are interested in. + raw := tpl[end:idxs[i]] + end = idxs[i+1] + parts := strings.SplitN(tpl[idxs[i]+1:end-1], ":", 2) + name := parts[0] + patt := defaultPattern + if len(parts) == 2 { + patt = parts[1] + } + // Name or pattern can't be empty. + if name == "" || patt == "" { + return nil, fmt.Errorf("mux: missing name or pattern in %q", + tpl[idxs[i]:end]) + } + // Build the regexp pattern. + fmt.Fprintf(pattern, "%s(%s)", regexp.QuoteMeta(raw), patt) + // Build the reverse template. + fmt.Fprintf(reverse, "%s%%s", raw) + // Append variable name and compiled pattern. + varsN[i/2] = name + varsR[i/2], err = regexp.Compile(fmt.Sprintf("^%s$", patt)) + if err != nil { + return nil, err + } + } + // Add the remaining. + raw := tpl[end:] + pattern.WriteString(regexp.QuoteMeta(raw)) + if strictSlash { + pattern.WriteString("[/]?") + } + if !matchPrefix { + pattern.WriteByte('$') + } + reverse.WriteString(raw) + if endSlash { + reverse.WriteByte('/') + } + // Compile full regexp. + reg, errCompile := regexp.Compile(pattern.String()) + if errCompile != nil { + return nil, errCompile + } + // Done! + return &routeRegexp{ + template: template, + matchHost: matchHost, + matchQuery: matchQuery, + strictSlash: strictSlash, + regexp: reg, + reverse: reverse.String(), + varsN: varsN, + varsR: varsR, + }, nil +} + +// routeRegexp stores a regexp to match a host or path and information to +// collect and validate route variables. +type routeRegexp struct { + // The unmodified template. + template string + // True for host match, false for path or query string match. + matchHost bool + // True for query string match, false for path and host match. + matchQuery bool + // The strictSlash value defined on the route, but disabled if PathPrefix was used. + strictSlash bool + // Expanded regexp. + regexp *regexp.Regexp + // Reverse template. + reverse string + // Variable names. + varsN []string + // Variable regexps (validators). + varsR []*regexp.Regexp +} + +// Match matches the regexp against the URL host or path. +func (r *routeRegexp) Match(req *http.Request, match *RouteMatch) bool { + if !r.matchHost { + if r.matchQuery { + return r.regexp.MatchString(req.URL.RawQuery) + } else { + return r.regexp.MatchString(req.URL.Path) + } + } + return r.regexp.MatchString(getHost(req)) +} + +// url builds a URL part using the given values. +func (r *routeRegexp) url(pairs ...string) (string, error) { + values, err := mapFromPairs(pairs...) + if err != nil { + return "", err + } + urlValues := make([]interface{}, len(r.varsN)) + for k, v := range r.varsN { + value, ok := values[v] + if !ok { + return "", fmt.Errorf("mux: missing route variable %q", v) + } + urlValues[k] = value + } + rv := fmt.Sprintf(r.reverse, urlValues...) + if !r.regexp.MatchString(rv) { + // The URL is checked against the full regexp, instead of checking + // individual variables. This is faster but to provide a good error + // message, we check individual regexps if the URL doesn't match. + for k, v := range r.varsN { + if !r.varsR[k].MatchString(values[v]) { + return "", fmt.Errorf( + "mux: variable %q doesn't match, expected %q", values[v], + r.varsR[k].String()) + } + } + } + return rv, nil +} + +// braceIndices returns the first level curly brace indices from a string. +// It returns an error in case of unbalanced braces. +func braceIndices(s string) ([]int, error) { + var level, idx int + idxs := make([]int, 0) + for i := 0; i < len(s); i++ { + switch s[i] { + case '{': + if level++; level == 1 { + idx = i + } + case '}': + if level--; level == 0 { + idxs = append(idxs, idx, i+1) + } else if level < 0 { + return nil, fmt.Errorf("mux: unbalanced braces in %q", s) + } + } + } + if level != 0 { + return nil, fmt.Errorf("mux: unbalanced braces in %q", s) + } + return idxs, nil +} + +// ---------------------------------------------------------------------------- +// routeRegexpGroup +// ---------------------------------------------------------------------------- + +// routeRegexpGroup groups the route matchers that carry variables. +type routeRegexpGroup struct { + host *routeRegexp + path *routeRegexp + queries []*routeRegexp +} + +// setMatch extracts the variables from the URL once a route matches. +func (v *routeRegexpGroup) setMatch(req *http.Request, m *RouteMatch, r *Route) { + // Store host variables. + if v.host != nil { + hostVars := v.host.regexp.FindStringSubmatch(getHost(req)) + if hostVars != nil { + for k, v := range v.host.varsN { + m.Vars[v] = hostVars[k+1] + } + } + } + // Store path variables. + if v.path != nil { + pathVars := v.path.regexp.FindStringSubmatch(req.URL.Path) + if pathVars != nil { + for k, v := range v.path.varsN { + m.Vars[v] = pathVars[k+1] + } + // Check if we should redirect. + if v.path.strictSlash { + p1 := strings.HasSuffix(req.URL.Path, "/") + p2 := strings.HasSuffix(v.path.template, "/") + if p1 != p2 { + u, _ := url.Parse(req.URL.String()) + if p1 { + u.Path = u.Path[:len(u.Path)-1] + } else { + u.Path += "/" + } + m.Handler = http.RedirectHandler(u.String(), 301) + } + } + } + } + // Store query string variables. + rawQuery := req.URL.RawQuery + for _, q := range v.queries { + queryVars := q.regexp.FindStringSubmatch(rawQuery) + if queryVars != nil { + for k, v := range q.varsN { + m.Vars[v] = queryVars[k+1] + } + } + } +} + +// getHost tries its best to return the request host. +func getHost(r *http.Request) string { + if !r.URL.IsAbs() { + host := r.Host + // Slice off any port information. + if i := strings.Index(host, ":"); i != -1 { + host = host[:i] + } + return host + } + return r.URL.Host +} diff --git a/Godeps/_workspace/src/github.com/gorilla/mux/route.go b/Godeps/_workspace/src/github.com/gorilla/mux/route.go new file mode 100644 index 00000000000..c310e66bc7c --- /dev/null +++ b/Godeps/_workspace/src/github.com/gorilla/mux/route.go @@ -0,0 +1,524 @@ +// Copyright 2012 The Gorilla Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package mux + +import ( + "errors" + "fmt" + "net/http" + "net/url" + "strings" +) + +// Route stores information to match a request and build URLs. +type Route struct { + // Parent where the route was registered (a Router). + parent parentRoute + // Request handler for the route. + handler http.Handler + // List of matchers. + matchers []matcher + // Manager for the variables from host and path. + regexp *routeRegexpGroup + // If true, when the path pattern is "/path/", accessing "/path" will + // redirect to the former and vice versa. + strictSlash bool + // If true, this route never matches: it is only used to build URLs. + buildOnly bool + // The name used to build URLs. + name string + // Error resulted from building a route. + err error +} + +// Match matches the route against the request. +func (r *Route) Match(req *http.Request, match *RouteMatch) bool { + if r.buildOnly || r.err != nil { + return false + } + // Match everything. + for _, m := range r.matchers { + if matched := m.Match(req, match); !matched { + return false + } + } + // Yay, we have a match. Let's collect some info about it. + if match.Route == nil { + match.Route = r + } + if match.Handler == nil { + match.Handler = r.handler + } + if match.Vars == nil { + match.Vars = make(map[string]string) + } + // Set variables. + if r.regexp != nil { + r.regexp.setMatch(req, match, r) + } + return true +} + +// ---------------------------------------------------------------------------- +// Route attributes +// ---------------------------------------------------------------------------- + +// GetError returns an error resulted from building the route, if any. +func (r *Route) GetError() error { + return r.err +} + +// BuildOnly sets the route to never match: it is only used to build URLs. +func (r *Route) BuildOnly() *Route { + r.buildOnly = true + return r +} + +// Handler -------------------------------------------------------------------- + +// Handler sets a handler for the route. +func (r *Route) Handler(handler http.Handler) *Route { + if r.err == nil { + r.handler = handler + } + return r +} + +// HandlerFunc sets a handler function for the route. +func (r *Route) HandlerFunc(f func(http.ResponseWriter, *http.Request)) *Route { + return r.Handler(http.HandlerFunc(f)) +} + +// GetHandler returns the handler for the route, if any. +func (r *Route) GetHandler() http.Handler { + return r.handler +} + +// Name ----------------------------------------------------------------------- + +// Name sets the name for the route, used to build URLs. +// If the name was registered already it will be overwritten. +func (r *Route) Name(name string) *Route { + if r.name != "" { + r.err = fmt.Errorf("mux: route already has name %q, can't set %q", + r.name, name) + } + if r.err == nil { + r.name = name + r.getNamedRoutes()[name] = r + } + return r +} + +// GetName returns the name for the route, if any. +func (r *Route) GetName() string { + return r.name +} + +// ---------------------------------------------------------------------------- +// Matchers +// ---------------------------------------------------------------------------- + +// matcher types try to match a request. +type matcher interface { + Match(*http.Request, *RouteMatch) bool +} + +// addMatcher adds a matcher to the route. +func (r *Route) addMatcher(m matcher) *Route { + if r.err == nil { + r.matchers = append(r.matchers, m) + } + return r +} + +// addRegexpMatcher adds a host or path matcher and builder to a route. +func (r *Route) addRegexpMatcher(tpl string, matchHost, matchPrefix, matchQuery bool) error { + if r.err != nil { + return r.err + } + r.regexp = r.getRegexpGroup() + if !matchHost && !matchQuery { + if len(tpl) == 0 || tpl[0] != '/' { + return fmt.Errorf("mux: path must start with a slash, got %q", tpl) + } + if r.regexp.path != nil { + tpl = strings.TrimRight(r.regexp.path.template, "/") + tpl + } + } + rr, err := newRouteRegexp(tpl, matchHost, matchPrefix, matchQuery, r.strictSlash) + if err != nil { + return err + } + for _, q := range r.regexp.queries { + if err = uniqueVars(rr.varsN, q.varsN); err != nil { + return err + } + } + if matchHost { + if r.regexp.path != nil { + if err = uniqueVars(rr.varsN, r.regexp.path.varsN); err != nil { + return err + } + } + r.regexp.host = rr + } else { + if r.regexp.host != nil { + if err = uniqueVars(rr.varsN, r.regexp.host.varsN); err != nil { + return err + } + } + if matchQuery { + r.regexp.queries = append(r.regexp.queries, rr) + } else { + r.regexp.path = rr + } + } + r.addMatcher(rr) + return nil +} + +// Headers -------------------------------------------------------------------- + +// headerMatcher matches the request against header values. +type headerMatcher map[string]string + +func (m headerMatcher) Match(r *http.Request, match *RouteMatch) bool { + return matchMap(m, r.Header, true) +} + +// Headers adds a matcher for request header values. +// It accepts a sequence of key/value pairs to be matched. For example: +// +// r := mux.NewRouter() +// r.Headers("Content-Type", "application/json", +// "X-Requested-With", "XMLHttpRequest") +// +// The above route will only match if both request header values match. +// +// It the value is an empty string, it will match any value if the key is set. +func (r *Route) Headers(pairs ...string) *Route { + if r.err == nil { + var headers map[string]string + headers, r.err = mapFromPairs(pairs...) + return r.addMatcher(headerMatcher(headers)) + } + return r +} + +// Host ----------------------------------------------------------------------- + +// Host adds a matcher for the URL host. +// It accepts a template with zero or more URL variables enclosed by {}. +// Variables can define an optional regexp pattern to me matched: +// +// - {name} matches anything until the next dot. +// +// - {name:pattern} matches the given regexp pattern. +// +// For example: +// +// r := mux.NewRouter() +// r.Host("www.domain.com") +// r.Host("{subdomain}.domain.com") +// r.Host("{subdomain:[a-z]+}.domain.com") +// +// Variable names must be unique in a given route. They can be retrieved +// calling mux.Vars(request). +func (r *Route) Host(tpl string) *Route { + r.err = r.addRegexpMatcher(tpl, true, false, false) + return r +} + +// MatcherFunc ---------------------------------------------------------------- + +// MatcherFunc is the function signature used by custom matchers. +type MatcherFunc func(*http.Request, *RouteMatch) bool + +func (m MatcherFunc) Match(r *http.Request, match *RouteMatch) bool { + return m(r, match) +} + +// MatcherFunc adds a custom function to be used as request matcher. +func (r *Route) MatcherFunc(f MatcherFunc) *Route { + return r.addMatcher(f) +} + +// Methods -------------------------------------------------------------------- + +// methodMatcher matches the request against HTTP methods. +type methodMatcher []string + +func (m methodMatcher) Match(r *http.Request, match *RouteMatch) bool { + return matchInArray(m, r.Method) +} + +// Methods adds a matcher for HTTP methods. +// It accepts a sequence of one or more methods to be matched, e.g.: +// "GET", "POST", "PUT". +func (r *Route) Methods(methods ...string) *Route { + for k, v := range methods { + methods[k] = strings.ToUpper(v) + } + return r.addMatcher(methodMatcher(methods)) +} + +// Path ----------------------------------------------------------------------- + +// Path adds a matcher for the URL path. +// It accepts a template with zero or more URL variables enclosed by {}. The +// template must start with a "/". +// Variables can define an optional regexp pattern to me matched: +// +// - {name} matches anything until the next slash. +// +// - {name:pattern} matches the given regexp pattern. +// +// For example: +// +// r := mux.NewRouter() +// r.Path("/products/").Handler(ProductsHandler) +// r.Path("/products/{key}").Handler(ProductsHandler) +// r.Path("/articles/{category}/{id:[0-9]+}"). +// Handler(ArticleHandler) +// +// Variable names must be unique in a given route. They can be retrieved +// calling mux.Vars(request). +func (r *Route) Path(tpl string) *Route { + r.err = r.addRegexpMatcher(tpl, false, false, false) + return r +} + +// PathPrefix ----------------------------------------------------------------- + +// PathPrefix adds a matcher for the URL path prefix. This matches if the given +// template is a prefix of the full URL path. See Route.Path() for details on +// the tpl argument. +// +// Note that it does not treat slashes specially ("/foobar/" will be matched by +// the prefix "/foo") so you may want to use a trailing slash here. +// +// Also note that the setting of Router.StrictSlash() has no effect on routes +// with a PathPrefix matcher. +func (r *Route) PathPrefix(tpl string) *Route { + r.err = r.addRegexpMatcher(tpl, false, true, false) + return r +} + +// Query ---------------------------------------------------------------------- + +// Queries adds a matcher for URL query values. +// It accepts a sequence of key/value pairs. Values may define variables. +// For example: +// +// r := mux.NewRouter() +// r.Queries("foo", "bar", "id", "{id:[0-9]+}") +// +// The above route will only match if the URL contains the defined queries +// values, e.g.: ?foo=bar&id=42. +// +// It the value is an empty string, it will match any value if the key is set. +// +// Variables can define an optional regexp pattern to me matched: +// +// - {name} matches anything until the next slash. +// +// - {name:pattern} matches the given regexp pattern. +func (r *Route) Queries(pairs ...string) *Route { + length := len(pairs) + if length%2 != 0 { + r.err = fmt.Errorf( + "mux: number of parameters must be multiple of 2, got %v", pairs) + return nil + } + for i := 0; i < length; i += 2 { + if r.err = r.addRegexpMatcher(pairs[i]+"="+pairs[i+1], false, true, true); r.err != nil { + return r + } + } + + return r +} + +// Schemes -------------------------------------------------------------------- + +// schemeMatcher matches the request against URL schemes. +type schemeMatcher []string + +func (m schemeMatcher) Match(r *http.Request, match *RouteMatch) bool { + return matchInArray(m, r.URL.Scheme) +} + +// Schemes adds a matcher for URL schemes. +// It accepts a sequence of schemes to be matched, e.g.: "http", "https". +func (r *Route) Schemes(schemes ...string) *Route { + for k, v := range schemes { + schemes[k] = strings.ToLower(v) + } + return r.addMatcher(schemeMatcher(schemes)) +} + +// Subrouter ------------------------------------------------------------------ + +// Subrouter creates a subrouter for the route. +// +// It will test the inner routes only if the parent route matched. For example: +// +// r := mux.NewRouter() +// s := r.Host("www.domain.com").Subrouter() +// s.HandleFunc("/products/", ProductsHandler) +// s.HandleFunc("/products/{key}", ProductHandler) +// s.HandleFunc("/articles/{category}/{id:[0-9]+}"), ArticleHandler) +// +// Here, the routes registered in the subrouter won't be tested if the host +// doesn't match. +func (r *Route) Subrouter() *Router { + router := &Router{parent: r, strictSlash: r.strictSlash} + r.addMatcher(router) + return router +} + +// ---------------------------------------------------------------------------- +// URL building +// ---------------------------------------------------------------------------- + +// URL builds a URL for the route. +// +// It accepts a sequence of key/value pairs for the route variables. For +// example, given this route: +// +// r := mux.NewRouter() +// r.HandleFunc("/articles/{category}/{id:[0-9]+}", ArticleHandler). +// Name("article") +// +// ...a URL for it can be built using: +// +// url, err := r.Get("article").URL("category", "technology", "id", "42") +// +// ...which will return an url.URL with the following path: +// +// "/articles/technology/42" +// +// This also works for host variables: +// +// r := mux.NewRouter() +// r.Host("{subdomain}.domain.com"). +// HandleFunc("/articles/{category}/{id:[0-9]+}", ArticleHandler). +// Name("article") +// +// // url.String() will be "http://news.domain.com/articles/technology/42" +// url, err := r.Get("article").URL("subdomain", "news", +// "category", "technology", +// "id", "42") +// +// All variables defined in the route are required, and their values must +// conform to the corresponding patterns. +func (r *Route) URL(pairs ...string) (*url.URL, error) { + if r.err != nil { + return nil, r.err + } + if r.regexp == nil { + return nil, errors.New("mux: route doesn't have a host or path") + } + var scheme, host, path string + var err error + if r.regexp.host != nil { + // Set a default scheme. + scheme = "http" + if host, err = r.regexp.host.url(pairs...); err != nil { + return nil, err + } + } + if r.regexp.path != nil { + if path, err = r.regexp.path.url(pairs...); err != nil { + return nil, err + } + } + return &url.URL{ + Scheme: scheme, + Host: host, + Path: path, + }, nil +} + +// URLHost builds the host part of the URL for a route. See Route.URL(). +// +// The route must have a host defined. +func (r *Route) URLHost(pairs ...string) (*url.URL, error) { + if r.err != nil { + return nil, r.err + } + if r.regexp == nil || r.regexp.host == nil { + return nil, errors.New("mux: route doesn't have a host") + } + host, err := r.regexp.host.url(pairs...) + if err != nil { + return nil, err + } + return &url.URL{ + Scheme: "http", + Host: host, + }, nil +} + +// URLPath builds the path part of the URL for a route. See Route.URL(). +// +// The route must have a path defined. +func (r *Route) URLPath(pairs ...string) (*url.URL, error) { + if r.err != nil { + return nil, r.err + } + if r.regexp == nil || r.regexp.path == nil { + return nil, errors.New("mux: route doesn't have a path") + } + path, err := r.regexp.path.url(pairs...) + if err != nil { + return nil, err + } + return &url.URL{ + Path: path, + }, nil +} + +// ---------------------------------------------------------------------------- +// parentRoute +// ---------------------------------------------------------------------------- + +// parentRoute allows routes to know about parent host and path definitions. +type parentRoute interface { + getNamedRoutes() map[string]*Route + getRegexpGroup() *routeRegexpGroup +} + +// getNamedRoutes returns the map where named routes are registered. +func (r *Route) getNamedRoutes() map[string]*Route { + if r.parent == nil { + // During tests router is not always set. + r.parent = NewRouter() + } + return r.parent.getNamedRoutes() +} + +// getRegexpGroup returns regexp definitions from this route. +func (r *Route) getRegexpGroup() *routeRegexpGroup { + if r.regexp == nil { + if r.parent == nil { + // During tests router is not always set. + r.parent = NewRouter() + } + regexp := r.parent.getRegexpGroup() + if regexp == nil { + r.regexp = new(routeRegexpGroup) + } else { + // Copy. + r.regexp = &routeRegexpGroup{ + host: regexp.host, + path: regexp.path, + queries: regexp.queries, + } + } + } + return r.regexp +} diff --git a/server/http/http.go b/server/http/http.go index aa0c5a4d673..a6056bcaa25 100644 --- a/server/http/http.go +++ b/server/http/http.go @@ -4,7 +4,7 @@ import ( "fmt" "net/http" - "github.com/gorilla/mux" + "github.com/jbenet/go-ipfs/Godeps/_workspace/src/github.com/gorilla/mux" mh "github.com/jbenet/go-ipfs/Godeps/_workspace/src/github.com/jbenet/go-multihash" core "github.com/jbenet/go-ipfs/core" ) From 4510977a4d4761484d585988deca3229d418d80d Mon Sep 17 00:00:00 2001 From: verokarhu Date: Thu, 18 Sep 2014 00:14:18 +0300 Subject: [PATCH 17/24] fix imports on mux --- Godeps/_workspace/src/github.com/gorilla/mux/mux.go | 2 +- Godeps/_workspace/src/github.com/gorilla/mux/mux_test.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Godeps/_workspace/src/github.com/gorilla/mux/mux.go b/Godeps/_workspace/src/github.com/gorilla/mux/mux.go index 5b5f8e7db5d..26966422b71 100644 --- a/Godeps/_workspace/src/github.com/gorilla/mux/mux.go +++ b/Godeps/_workspace/src/github.com/gorilla/mux/mux.go @@ -9,7 +9,7 @@ import ( "net/http" "path" - "github.com/gorilla/context" + "github.com/jbenet/go-ipfs/Godeps/_workspace/src/github.com/gorilla/context" ) // NewRouter returns a new router instance. diff --git a/Godeps/_workspace/src/github.com/gorilla/mux/mux_test.go b/Godeps/_workspace/src/github.com/gorilla/mux/mux_test.go index e455bce8fdf..4c2706b3ce7 100644 --- a/Godeps/_workspace/src/github.com/gorilla/mux/mux_test.go +++ b/Godeps/_workspace/src/github.com/gorilla/mux/mux_test.go @@ -9,7 +9,7 @@ import ( "net/http" "testing" - "github.com/gorilla/context" + "github.com/jbenet/go-ipfs/Godeps/_workspace/src/github.com/gorilla/context" ) type routeTest struct { From 600829b4e70aa584774371a40e98eb861c84e4e2 Mon Sep 17 00:00:00 2001 From: verokarhu Date: Thu, 18 Sep 2014 00:28:06 +0300 Subject: [PATCH 18/24] fix localNode call --- cmd/ipfs/serve.go | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/cmd/ipfs/serve.go b/cmd/ipfs/serve.go index 0d1fb1d3bbd..7fae022d90d 100644 --- a/cmd/ipfs/serve.go +++ b/cmd/ipfs/serve.go @@ -38,7 +38,12 @@ func serveHttpCmd(c *commander.Command, _ []string) error { return errors.New("invalid port number") } - n, err := localNode(true) + conf, err := getConfigDir(c.Parent.Parent) + if err != nil { + return err + } + + n, err := localNode(conf, true) if err != nil { return err } From 7b22bdc68b09dc92b54dad77f0adc03b662ddf8a Mon Sep 17 00:00:00 2001 From: verokarhu Date: Sun, 28 Sep 2014 11:02:57 +0300 Subject: [PATCH 19/24] fix compile error after sync --- cmd/ipfs/serve.go | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/cmd/ipfs/serve.go b/cmd/ipfs/serve.go index 7fae022d90d..0e2dc9a63b6 100644 --- a/cmd/ipfs/serve.go +++ b/cmd/ipfs/serve.go @@ -6,6 +6,8 @@ import ( "github.com/jbenet/go-ipfs/Godeps/_workspace/src/github.com/gonuts/flag" "github.com/jbenet/go-ipfs/Godeps/_workspace/src/github.com/jbenet/commander" + ma "github.com/jbenet/go-ipfs/Godeps/_workspace/src/github.com/jbenet/go-multiaddr" + "github.com/jbenet/go-ipfs/daemon" h "github.com/jbenet/go-ipfs/server/http" ) @@ -48,9 +50,19 @@ func serveHttpCmd(c *commander.Command, _ []string) error { return err } - fmt.Println("starting new daemon listener...") - dl, err := daemon.NewDaemonListener(n, "localhost:12345") + // launch the API RPC endpoint. + if n.Config.Addresses.API == "" { + return errors.New("no config.RPCAddress endpoint supplied") + } + + maddr, err := ma.NewMultiaddr(n.Config.Addresses.API) + if err != nil { + return err + } + + dl, err := daemon.NewDaemonListener(n, maddr) if err != nil { + fmt.Println("Failed to create daemon listener.") return err } go dl.Listen() From 8382c797fcc415802be875a72a929357de49f972 Mon Sep 17 00:00:00 2001 From: verokarhu Date: Sun, 28 Sep 2014 12:26:59 +0300 Subject: [PATCH 20/24] use dagreader in servehttp --- server/http/http.go | 13 +++++++++++-- server/http/http_test.go | 14 ++++++++++++++ server/http/ipfs.go | 5 +++++ 3 files changed, 30 insertions(+), 2 deletions(-) diff --git a/server/http/http.go b/server/http/http.go index a6056bcaa25..591d1660a2d 100644 --- a/server/http/http.go +++ b/server/http/http.go @@ -2,6 +2,7 @@ package http import ( "fmt" + "io" "net/http" "github.com/jbenet/go-ipfs/Godeps/_workspace/src/github.com/gorilla/mux" @@ -30,11 +31,19 @@ func (i *handler) ServeHTTP(w http.ResponseWriter, r *http.Request) { nd, err := i.ResolvePath(path) if err != nil { w.WriteHeader(http.StatusInternalServerError) + fmt.Println(err) + return + } + + dr, err := i.NewDagReader(nd) + if err != nil { + // TODO: return json object containing the tree data if it's a directory (err == ErrIsDir) + w.WriteHeader(http.StatusInternalServerError) + fmt.Println(err) return } - // TODO: return json object containing the tree data if it's a folder - w.Write(nd.Data) + io.Copy(w, dr) } func (i *handler) postHandler(w http.ResponseWriter, r *http.Request) { diff --git a/server/http/http_test.go b/server/http/http_test.go index f62ba6c774a..71512978b0f 100644 --- a/server/http/http_test.go +++ b/server/http/http_test.go @@ -1,6 +1,7 @@ package http import ( + "bytes" "errors" "io" "io/ioutil" @@ -25,6 +26,7 @@ func TestServeHTTP(t *testing.T) { tests := []test{ {"/", http.StatusInternalServerError, "", ""}, {"/hash", http.StatusOK, "", "some fine data"}, + {"/hash2", http.StatusInternalServerError, "", ""}, } for _, test := range tests { @@ -74,6 +76,10 @@ func (i *testIpfsHandler) ResolvePath(path string) (*dag.Node, error) { return &dag.Node{Data: []byte("some fine data")}, nil } + if path == "/hash2" { + return &dag.Node{Data: []byte("data that breaks dagreader")}, nil + } + return nil, errors.New("") } @@ -92,3 +98,11 @@ func (i *testIpfsHandler) AddNodeToDAG(nd *dag.Node) (u.Key, error) { return "", errors.New("") } + +func (i *testIpfsHandler) NewDagReader(nd *dag.Node) (io.Reader, error) { + if string(nd.Data) != "data that breaks dagreader" { + return bytes.NewReader(nd.Data), nil + } + + return nil, errors.New("") +} diff --git a/server/http/ipfs.go b/server/http/ipfs.go index e9c24399391..6a177023f8d 100644 --- a/server/http/ipfs.go +++ b/server/http/ipfs.go @@ -13,6 +13,7 @@ type ipfs interface { ResolvePath(string) (*dag.Node, error) NewDagFromReader(io.Reader) (*dag.Node, error) AddNodeToDAG(nd *dag.Node) (u.Key, error) + NewDagReader(nd *dag.Node) (io.Reader, error) } type ipfsHandler struct { @@ -30,3 +31,7 @@ func (i *ipfsHandler) NewDagFromReader(r io.Reader) (*dag.Node, error) { func (i *ipfsHandler) AddNodeToDAG(nd *dag.Node) (u.Key, error) { return i.node.DAG.Add(nd) } + +func (i *ipfsHandler) NewDagReader(nd *dag.Node) (io.Reader, error) { + return dag.NewDagReader(nd, i.node.DAG) +} From 0beff4bb4ad8ab2f9a9c9911f36cb236bd847b36 Mon Sep 17 00:00:00 2001 From: verokarhu Date: Sun, 28 Sep 2014 13:45:53 +0300 Subject: [PATCH 21/24] return the read bytes when EOF is reached --- importer/splitting.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/importer/splitting.go b/importer/splitting.go index 30ecc1e3e51..05eaec15ae5 100644 --- a/importer/splitting.go +++ b/importer/splitting.go @@ -23,6 +23,9 @@ func (ss *SizeSplitter) Split(r io.Reader) chan []byte { nread, err := r.Read(chunk) if err != nil { if err == io.EOF { + if nread > 0 { + out <- chunk[:nread] + } return } u.PErr("block split error: %v\n", err) From 0b0bee6fd5b40e6e04967f270437fc943cc1dbfb Mon Sep 17 00:00:00 2001 From: verokarhu Date: Tue, 30 Sep 2014 20:54:13 +0300 Subject: [PATCH 22/24] switch to multiaddr and use /ipfs/ path in http api --- cmd/ipfs/serve.go | 19 ++++++++----------- server/http/http.go | 17 ++++++++++++----- 2 files changed, 20 insertions(+), 16 deletions(-) diff --git a/cmd/ipfs/serve.go b/cmd/ipfs/serve.go index 0e2dc9a63b6..97dae0531c4 100644 --- a/cmd/ipfs/serve.go +++ b/cmd/ipfs/serve.go @@ -30,16 +30,10 @@ var cmdIpfsServeHttp = &commander.Command{ } func init() { - cmdIpfsServeHttp.Flag.Uint("port", 8080, "Port number") - cmdIpfsServeHttp.Flag.String("hostname", "localhost", "Hostname") + cmdIpfsServeHttp.Flag.String("address", "/ip4/127.0.0.1/tcp/8080", "Listen Address") } func serveHttpCmd(c *commander.Command, _ []string) error { - port := c.Flag.Lookup("port").Value.Get().(uint) - if port < 1 || port > 65535 { - return errors.New("invalid port number") - } - conf, err := getConfigDir(c.Parent.Parent) if err != nil { return err @@ -68,9 +62,12 @@ func serveHttpCmd(c *commander.Command, _ []string) error { go dl.Listen() defer dl.Close() - hostname := c.Flag.Lookup("hostname").Value.Get().(string) - address := fmt.Sprintf("%s:%d", hostname, port) - fmt.Printf("Serving on %s\n", address) + address := c.Flag.Lookup("address").Value.Get().(string) + maddr, err = ma.NewMultiaddr(address) + if err != nil { + return err + } - return h.Serve(address, n) + fmt.Printf("Serving on %s\n", address) + return h.Serve(maddr, n) } diff --git a/server/http/http.go b/server/http/http.go index 591d1660a2d..75623d5f2ab 100644 --- a/server/http/http.go +++ b/server/http/http.go @@ -6,7 +6,9 @@ import ( "net/http" "github.com/jbenet/go-ipfs/Godeps/_workspace/src/github.com/gorilla/mux" + ma "github.com/jbenet/go-ipfs/Godeps/_workspace/src/github.com/jbenet/go-multiaddr" mh "github.com/jbenet/go-ipfs/Godeps/_workspace/src/github.com/jbenet/go-multihash" + core "github.com/jbenet/go-ipfs/core" ) @@ -15,18 +17,23 @@ type handler struct { } // Serve starts the http server -func Serve(address string, node *core.IpfsNode) error { +func Serve(address *ma.Multiaddr, node *core.IpfsNode) error { r := mux.NewRouter() handler := &handler{&ipfsHandler{node}} - r.HandleFunc("/", handler.postHandler).Methods("POST") - r.PathPrefix("/").Handler(handler).Methods("GET") + r.HandleFunc("/ipfs/", handler.postHandler).Methods("POST") + r.PathPrefix("/ipfs/").Handler(handler).Methods("GET") http.Handle("/", r) - return http.ListenAndServe(address, nil) + _, host, err := address.DialArgs() + if err != nil { + return err + } + + return http.ListenAndServe(host, nil) } func (i *handler) ServeHTTP(w http.ResponseWriter, r *http.Request) { - path := r.URL.Path + path := r.URL.Path[5:] nd, err := i.ResolvePath(path) if err != nil { From c6c758d93fafd0bea5d62768a9e1120d1a616fab Mon Sep 17 00:00:00 2001 From: verokarhu Date: Tue, 30 Sep 2014 23:02:22 +0300 Subject: [PATCH 23/24] fix compile error after rebase --- cmd/ipfs/serve.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmd/ipfs/serve.go b/cmd/ipfs/serve.go index 97dae0531c4..b4fb48c1db8 100644 --- a/cmd/ipfs/serve.go +++ b/cmd/ipfs/serve.go @@ -54,7 +54,7 @@ func serveHttpCmd(c *commander.Command, _ []string) error { return err } - dl, err := daemon.NewDaemonListener(n, maddr) + dl, err := daemon.NewDaemonListener(n, maddr, conf) if err != nil { fmt.Println("Failed to create daemon listener.") return err From f40fbb24622694734233d3439a10d40ebb4f2921 Mon Sep 17 00:00:00 2001 From: verokarhu Date: Tue, 30 Sep 2014 23:16:55 +0300 Subject: [PATCH 24/24] fix http test --- server/http/http_test.go | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/server/http/http_test.go b/server/http/http_test.go index 71512978b0f..eaf56fd3621 100644 --- a/server/http/http_test.go +++ b/server/http/http_test.go @@ -24,9 +24,9 @@ type test struct { func TestServeHTTP(t *testing.T) { testhandler := &handler{&testIpfsHandler{}} tests := []test{ - {"/", http.StatusInternalServerError, "", ""}, - {"/hash", http.StatusOK, "", "some fine data"}, - {"/hash2", http.StatusInternalServerError, "", ""}, + {"/ipfs/", http.StatusInternalServerError, "", ""}, + {"/ipfs/hash", http.StatusOK, "", "some fine data"}, + {"/ipfs/hash2", http.StatusInternalServerError, "", ""}, } for _, test := range tests { @@ -48,9 +48,9 @@ func TestServeHTTP(t *testing.T) { func TestPostHandler(t *testing.T) { testhandler := &handler{&testIpfsHandler{}} tests := []test{ - {"/", http.StatusInternalServerError, "", ""}, - {"/", http.StatusInternalServerError, "something that causes an error in adding to DAG", ""}, - {"/", http.StatusCreated, "some fine data", "jSQBpNSebeYbPBjs1vp"}, + {"/ifps/", http.StatusInternalServerError, "", ""}, + {"/ipfs/", http.StatusInternalServerError, "something that causes an error in adding to DAG", ""}, + {"/ipfs/", http.StatusCreated, "some fine data", "jSQBpNSebeYbPBjs1vp"}, } for _, test := range tests {