diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..83a88f6 --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +# goreleaser +dist/ + +# OS +*.swp diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..6f3880f --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +The MIT License + +Copyright (c) 2010-2017 Google, Inc. http://angularjs.org + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/README.md b/README.md index 22644a0..4fc0fae 100644 --- a/README.md +++ b/README.md @@ -1,53 +1,76 @@ # Zap (WIP) +[![Powered By: GoReleaser](https://img.shields.io/badge/powered%20by-goreleaser-green.svg?style=flat-square)](https://github.com/goreleaser) + A simple recursive URL expander. -Note: As of right now, this repo is still being set up. This message will be removed when everything is ready. +**Note: As of right now, this repo is still being set up. This message will be removed when everything is ready.** ## Overview ZAP is a simple go app that sends 302 redirects. That's it. It was written in just a few hours in between random household tasks. It helps people be more efficient by providing simple shortcuts for common pages. +It was fun to build because it's super lightweight and insanely fast. Some sample benchmarks: -## Configuration - -### gotchas - -- `n` and `y` are reserved for canonical yaml booleans. Use quotes in config to avoid issues. +``` +# Trial 1: localhost +$ ab -n 10000 -c 100 http://localhost:8927/ +Requests per second: 39888.31 [#/sec] (mean) +Time per request: 2.507 [ms] (mean) + +# Trial 2: Hitting server on LAN over gigabit, zap behind nginx proxy +$ ab -n 10000 -c 100 http://server/z +Requests per second: 12671.57 [#/sec] (mean) +Time per request: 7.892 [ms] (mean) + +# Go benchmarks +$ go test -bench=. +BenchmarkIndexHandler-8 1000000 1679 ns/op +``` -## Benchmarks +As you can see, even behind an nginx proxy, running on a server that I +mercilessly abuse we still get a respectable ~13k QPS. -TODO: add `ab` results, as well as `go test -bench .` ---- ## Installation -### Quick Install +### Step 0: Quick Install If you just want to hack around: 1. `go install github.com/issmirnov/zap` 2. `tmux new-session -t zap` 3. `$GOPATH/bin/zap` -4. `curl -I -L -H 'Host: g' localhost:8927/i` +4. `curl -I -L -H 'Host: g' localhost:8927/z` - should return 302 to github.com/issmirnov.zap If you want to actually install this properly, read on. + ### Step 1: Set up Zap to run as a service +Zap takes several command line flags: + +- `-config` - path to config file. Default is `./c.yml` +- `-port` - port to bind to. Use either 80 or 8927 + +#### OSX (brew) + +1. `brew install issmirnov/apps/zap` +2. `sudo brew services start zap` +If you already have port 80 in use, you can run zap behind a reverse proxy. -Nginx +1. Change the port in the zap plist config: `sed -i '' 's/8927/80/'  /usr/local/Cellar/zap/*/homebrew.mxcl.zap.plist` +2. Start zap as user service: `brew services start zap` +3. Configure your web server to act as a reverse proxy. For nginx, this will suffice: ``` +# File: /usr/local/etc/nginx/servers/zap.conf server { - listen 80; - server_name s sp sg sf sd; - error_log /var/log/nginx/error.log; - access_log /var/log/nginx/access.log; - - location / { + listen 80; # Keep as 80, make sure nginx listens on 80 too + server_name e g; # Put your shortcuts here +location / { proxy_http_version 1.1; proxy_pass http://localhost:8927; proxy_set_header X-Forwarded-Host $host; @@ -56,28 +79,40 @@ server { } ``` +4. Restart your webserver and test the result: `curl -I -L -H 'Host: g' localhost:8927/z` + +#### Ubuntu + +TODO: Provide systemd script, add hints about nginx. ### Step 2: Configure DNS -For a lone machine, `/etc/hosts` is easiest. Add one entry per top level shortcut. For the sample config that would be `127.0.0.1 e` and `127.0.0.1 g`. The alternative is `dnsmasqd`, in which case you want to set up dnsmasqd to point to local server. +If you are running `zap` locally, you need to edit `/etc/hosts` and add each top level entry. For the sample config, this would be `127.0.0.1 e` and `127.0.0.1 g`. Adjust accordingly. + +For the advanced users, I suggest running `dnsmasqd` and add DNS entries for all TLDs, so that all of your clients will automatically point to the server. ### Step 3: Tweak the config file and launch the service -- `brew services start zap` -- `systemctl start zap` +The config file is located at `/usr/local/etc/zap/` on OSX, and `/path/tbd` on ubuntu. + +Open up `c.yml` and update the mappings you would like. You can nest arbitrarily deep. Expansions work on strings and ints. ---- +Important gotcha: yaml has [reserved types](http://yaml.org/type/bool.html) and thus `n`, `y`, `no` and the like need to be quoted. See the sample config. +Once you're done editing the file (making sure to keep the DNS entries in sync) restart the service and test it out. + +- OSX: `sudo brew services restart zap` or `brew services restart zap` +- Ubuntu: `systemctl restart zap` ## Contributing -Standard GitHub workflow - fork and submit a PR. +Patches are welcome! Please use the standard GitHub workflow - fork this repo and submit a PR. I'll usually get to it within a few days. -### Handy Commands +Handy commands for local dev: - `go run main.go config.go text.go web.go` to run locally -- `curl -I -L -H 'Host: g' localhost:8927/i/dotfiles` - to test locally e2e +- `curl -I -L -H 'Host: g' localhost:8927/z` - to test locally e2e - `goconvey` - launches web UI for go tests. - `go test` runs CLI tests. @@ -86,8 +121,8 @@ Standard GitHub workflow - fork and submit a PR. A short list of upcoming features and fixes, sorted by deadline. -- GoReleaser for release automation -- Homebrew install role +- GoReleaser for release automation - DONE +- Homebrew install script - DONE - Systemd service script - configurable index page. so 'start' or 'index.html', set in top level domain config. - queries: so `s/dns` -> `smirnov.wiki/start?do=search&id=dns` @@ -97,11 +132,6 @@ A short list of upcoming features and fixes, sorted by deadline. - add check for dual "expand" keys in config - -## License - -`ZAP` is licensed with MIT - ## Contributors - [Ivan Smirnov](http://ivansmirnov.name) diff --git a/c.yml b/c.yml index a18ccc7..b0b06fc 100644 --- a/c.yml +++ b/c.yml @@ -1,43 +1,12 @@ -sp: - expand: smirnov.wiki/project - h: - expand: hydra - m: - expand: monitoring +e: + expand: example.com a: - expand: ansible - z: - expand: zap -sg: - expand: smirnov.wiki/goals - 'n': - expand: 2017 - '18': - expand: 2018 -sd: - expand: smirnov.wiki/device - p: - expand: puma - s: - expand: services -s: - expand: smirnov.wiki -sc: - expand: smirnov.wiki/cycles - w: - expand: weekly - m: - expand: monthly -gh: + expand: apples + b: + expand: bananas +g: expand: github.com d: expand: issmirnov/dotfiles -g: - expand: git.smirnov.work - f: - expand: smivan/fleet - s: - expand: smivan/srm z: - expand: smivan/zap - + expand: issmirnov/zap diff --git a/dist/zap_Darwin_x86_64/zap b/dist/zap_Darwin_x86_64/zap new file mode 100755 index 0000000..6c80f99 Binary files /dev/null and b/dist/zap_Darwin_x86_64/zap differ diff --git a/dist/zap_Linux_x86_64/zap b/dist/zap_Linux_x86_64/zap new file mode 100755 index 0000000..65360b5 Binary files /dev/null and b/dist/zap_Linux_x86_64/zap differ diff --git a/goreleaser.yml b/goreleaser.yml new file mode 100644 index 0000000..be6da2f --- /dev/null +++ b/goreleaser.yml @@ -0,0 +1,16 @@ +build: + main: . + binary_name: zap + goos: + - darwin + - linux +archive: + replacements: + amd64: 64-bit + 386: 32-bit + darwin: MacOS + linux: Linux + files: + - c.yml + - README.md + - LICENSE diff --git a/main.go b/main.go index 7bd4523..3dd2dbc 100644 --- a/main.go +++ b/main.go @@ -4,10 +4,13 @@ import ( "flag" "fmt" "net/http" + "os" ) const appName = "zap" +var version = "develop" + func main() { var ( @@ -15,9 +18,15 @@ func main() { port = flag.Int("port", 8927, "port to bind to") host = flag.String("host", "127.0.0.1", "host interface") i = flag.String("index", "start", "string to append if path has trailing slash") + v = flag.Bool("v", false, "print version info") ) flag.Parse() + if *v { + fmt.Println(version) + os.Exit(0) + } + c, err := parseYaml(*configName) if err != nil { fmt.Printf("error: %s\n", err) diff --git a/main_test.go b/main_test.go index f314952..bd1de34 100644 --- a/main_test.go +++ b/main_test.go @@ -8,18 +8,19 @@ import ( ) const cYaml = ` -sp: - expand: smirnov.wiki/project - h: - expand: hydra -sg: - expand: smirnov.wiki/goals - 'n': - expand: 2017 -sd: - expand: smirnov.wiki/device - p: - expand: puma +e: + expand: example.com + a: + expand: apples + b: + expand: bananas +g: + expand: github.com + d: + expand: issmirnov/dotfiles + z: + expand: issmirnov/zap + ` func parseDummyYaml() (*gabs.Container, error) { diff --git a/text_test.go b/text_test.go index 9852fd4..30dc9b2 100644 --- a/text_test.go +++ b/text_test.go @@ -1,113 +1,104 @@ package main import ( - "bytes" - "testing" + "bytes" + "testing" - . "github.com/smartystreets/goconvey/convey" + . "github.com/smartystreets/goconvey/convey" ) func TestTokenizer(t *testing.T) { - Convey("Given a string 'sp/h/monitoring'", t, func() { - l := tokenize("sp/h/monitoring") - Convey("The resulting list should have length 3", func() { - So(l.Len(), ShouldEqual, 3) - }) - }) - - Convey("Given a string 'sp/h'", t, func() { - l := tokenize("sp/h") - Convey("The resulting list should", func() { - - Convey("Have length 2", func() { - So(l.Len(), ShouldEqual, 2) - }) - Convey("The first element should be equal to 'sp'", func() { - So(l.Front().Value, ShouldEqual, "sp") - }) - Convey("The last element should be equal to 'h'", func() { - So(l.Back().Value, ShouldEqual, "h") - }) - }) - }) - - Convey("Given a string 'sg/n'", t, func() { - l := tokenize("sg/n") - Convey("The resulting list should", func() { - - Convey("Have length 2", func() { - So(l.Len(), ShouldEqual, 2) - }) - Convey("The first element should be equal to 'sg'", func() { - So(l.Front().Value, ShouldEqual, "sg") - }) - Convey("The last element should be equal to 'g'", func() { - So(l.Back().Value, ShouldEqual, "n") - }) - }) - }) + Convey("Given a string 'g/z'", t, func() { + l := tokenize("g/z") + Convey("The resulting list should", func() { + + Convey("Have length 2", func() { + So(l.Len(), ShouldEqual, 2) + }) + Convey("The first element should be equal to 'g'", func() { + So(l.Front().Value, ShouldEqual, "g") + }) + Convey("The last element should be equal to 'z'", func() { + So(l.Back().Value, ShouldEqual, "z") + }) + }) + }) + + Convey("Given a string 'e/a/extratext'", t, func() { + l := tokenize("e/a/extratext") + Convey("The resulting list should have length 3", func() { + So(l.Len(), ShouldEqual, 3) + }) + }) + + Convey("Given a string 'e/a/extratext/'", t, func() { + l := tokenize("e/a/extratext/") + Convey("The resulting list should have length 4", func() { + So(l.Len(), ShouldEqual, 4) // Since we have nil terminator. + }) + }) } func TestExpander(t *testing.T) { - Convey("Given 'sp/h'", t, func() { - c, _ := parseDummyYaml() - l := tokenize("sp/h") - var res bytes.Buffer - res.WriteString("https:/") - - expand(c, l.Front(), &res) - - Convey("result should equal 'https://smirnov.wiki/project/hydra'", func() { - So(res.String(), ShouldEqual, "https://smirnov.wiki/project/hydra") - }) - }) - // Convey("Given 'sg/n'", t, func() { - // c, _ := parseDummyYaml() - // l := tokenize("sg/n ") - // var res bytes.Buffer - // res.WriteString("https:/") - // - // expand(c, l.Front(), &res) - // - // Convey("result should equal 'https://smirnov.wiki/goals/2017'", func() { - // So(res.String(), ShouldEqual, "https://smirnov.wiki/goals/2017") - // }) - // }) - Convey("Given 'sp/h/monitoring'", t, func() { - c, _ := parseDummyYaml() - l := tokenize("sp/h/monitoring") - var res bytes.Buffer - res.WriteString("https:/") - - expand(c, l.Front(), &res) - - Convey("result should equal 'https://smirnov.wiki/project/hydra/monitoring'", func() { - So(res.String(), ShouldEqual, "https://smirnov.wiki/project/hydra/monitoring") - }) - }) - Convey("Given 'sp/random'", t, func() { - c, _ := parseDummyYaml() - l := tokenize("sp/random") - var res bytes.Buffer - res.WriteString("https:/") - - expand(c, l.Front(), &res) - - Convey("result should equal 'https://smirnov.wiki/project/random'", func() { - So(res.String(), ShouldEqual, "https://smirnov.wiki/project/random") - }) - }) - Convey("Given 'sp/h/very/deep/path'", t, func() { - c, _ := parseDummyYaml() - l := tokenize("sp/h/very/deep/path") - var res bytes.Buffer - res.WriteString("https:/") - - expand(c, l.Front(), &res) - - Convey("result should equal 'https://smirnov.wiki/project/hydra/very/deep/path'", func() { - So(res.String(), ShouldEqual, "https://smirnov.wiki/project/hydra/very/deep/path") - }) - }) + Convey("Given 'g/z'", t, func() { + c, _ := parseDummyYaml() + l := tokenize("g/z") + var res bytes.Buffer + res.WriteString("https:/") + + expand(c, l.Front(), &res) + + Convey("result should equal 'https://github.com/issmirnov/zap'", func() { + So(res.String(), ShouldEqual, "https://github.com/issmirnov/zap") + }) + }) + // Convey("Given 'e/n'", t, func() { + // c, _ := parseDummyYaml() + // l := tokenize("e/n ") + // var res bytes.Buffer + // res.WriteString("https:/") + // + // expand(c, l.Front(), &res) + // + // Convey("result should equal 'https://example.com/999'", func() { + // So(res.String(), ShouldEqual, "https://example.com/999") + // }) + // }) + Convey("Given 'g/z/extratext'", t, func() { + c, _ := parseDummyYaml() + l := tokenize("g/z/extratext") + var res bytes.Buffer + res.WriteString("https:/") + + expand(c, l.Front(), &res) + + Convey("result should equal 'https://github.com/issmirnov/zap/extratext'", func() { + So(res.String(), ShouldEqual, "https://github.com/issmirnov/zap/extratext") + }) + }) + Convey("Given 'g/'", t, func() { + c, _ := parseDummyYaml() + l := tokenize("g/") + var res bytes.Buffer + res.WriteString("https:/") + + expand(c, l.Front(), &res) + + Convey("result should equal 'https://github.com/'", func() { + So(res.String(), ShouldEqual, "https://github.com/") + }) + }) + Convey("Given 'g/z/very/deep/path'", t, func() { + c, _ := parseDummyYaml() + l := tokenize("g/z/very/deep/path") + var res bytes.Buffer + res.WriteString("https:/") + + expand(c, l.Front(), &res) + + Convey("result should equal 'https://github.com/issmirnov/zap/very/deep/path'", func() { + So(res.String(), ShouldEqual, "https://github.com/issmirnov/zap/very/deep/path") + }) + }) } diff --git a/web_test.go b/web_test.go index 47843aa..d04ca81 100644 --- a/web_test.go +++ b/web_test.go @@ -1,219 +1,193 @@ package main import ( - "net/http" - "net/http/httptest" - "testing" + "net/http" + "net/http/httptest" + "testing" - "github.com/ghodss/yaml" - . "github.com/smartystreets/goconvey/convey" + "github.com/ghodss/yaml" + . "github.com/smartystreets/goconvey/convey" ) // See https://elithrar.github.io/article/testing-http-handlers-go/ for comments. func TestIndexHandler(t *testing.T) { - Convey("Given app is set up with default config", t, func() { - c, err := parseDummyYaml() - So(err, ShouldBeNil) - context := &context{config: c, index: "start"} - appHandler := &ctxWrapper{context, IndexHandler} - handler := http.Handler(appHandler) - Convey("When we GET http://sd/p", func() { - req, err := http.NewRequest("GET", "/p", nil) - So(err, ShouldBeNil) - req.Host = "sd" - - rr := httptest.NewRecorder() - handler.ServeHTTP(rr, req) - - Convey("The result should be a 302 to https://smirnov.wiki/device/puma", func() { - So(rr.Code, ShouldEqual, http.StatusFound) - So(rr.Header().Get("Location"), ShouldEqual, "https://smirnov.wiki/device/puma") - }) - }) - Convey("When we GET http://sd/p/", func() { - req, err := http.NewRequest("GET", "/p/", nil) - So(err, ShouldBeNil) - req.Host = "sd" - - rr := httptest.NewRecorder() - handler.ServeHTTP(rr, req) - - Convey("The result should be a 302 to https://smirnov.wiki/device/puma/start", func() { - So(rr.Code, ShouldEqual, http.StatusFound) - So(rr.Header().Get("Location"), ShouldEqual, "https://smirnov.wiki/device/puma/start") - }) - }) - Convey("When we GET http://sp/h", func() { - req, err := http.NewRequest("GET", "/h", nil) - So(err, ShouldBeNil) - req.Host = "sp" - - rr := httptest.NewRecorder() - handler.ServeHTTP(rr, req) - - Convey("The result should be a 302 to https://smirnov.wiki/project/hydra", func() { - So(rr.Code, ShouldEqual, http.StatusFound) - So(rr.Header().Get("Location"), ShouldEqual, "https://smirnov.wiki/project/hydra") - }) - }) - Convey("When we GET http://sp/h/", func() { - req, err := http.NewRequest("GET", "/h/", nil) - So(err, ShouldBeNil) - req.Host = "sp" - - rr := httptest.NewRecorder() - handler.ServeHTTP(rr, req) - - Convey("The result should be a 302 to https://smirnov.wiki/project/hydra/start", func() { - So(rr.Code, ShouldEqual, http.StatusFound) - So(rr.Header().Get("Location"), ShouldEqual, "https://smirnov.wiki/project/hydra/start") - }) - }) - Convey("When we GET http://sp/h/very/deep/path", func() { - req, err := http.NewRequest("GET", "/h/very/deep/path", nil) - So(err, ShouldBeNil) - req.Host = "sp" - - rr := httptest.NewRecorder() - handler.ServeHTTP(rr, req) - - Convey("The result should be a 302 to https://smirnov.wiki/project/hydra/very/deep/path", func() { - So(rr.Code, ShouldEqual, http.StatusFound) - So(rr.Header().Get("Location"), ShouldEqual, "https://smirnov.wiki/project/hydra/very/deep/path") - }) - }) - Convey("When we GET http://sp/h/very/deep/path/", func() { - req, err := http.NewRequest("GET", "/h/very/deep/path/", nil) - So(err, ShouldBeNil) - req.Host = "sp" - - rr := httptest.NewRecorder() - handler.ServeHTTP(rr, req) - - Convey("The result should be a 302 to https://smirnov.wiki/project/hydra/very/deep/path/start", func() { - So(rr.Code, ShouldEqual, http.StatusFound) - So(rr.Header().Get("Location"), ShouldEqual, "https://smirnov.wiki/project/hydra/very/deep/path/start") - }) - }) - Convey("When we GET http://sp/", func() { - req, err := http.NewRequest("GET", "/", nil) - So(err, ShouldBeNil) - req.Host = "sp" - - rr := httptest.NewRecorder() - handler.ServeHTTP(rr, req) - - Convey("The result should be a 302 to https://smirnov.wiki/project/start", func() { - So(rr.Code, ShouldEqual, http.StatusFound) - So(rr.Header().Get("Location"), ShouldEqual, "https://smirnov.wiki/project/start") - }) - }) - }) - Convey("Given app is set up with non default config", t, func() { - c, err := parseDummyYaml() - So(err, ShouldBeNil) - context := &context{config: c, index: "otherIndex"} - appHandler := &ctxWrapper{context, IndexHandler} - handler := http.Handler(appHandler) - Convey("When we GET http://sp/", func() { - req, err := http.NewRequest("GET", "/", nil) - So(err, ShouldBeNil) - req.Host = "sp" - - rr := httptest.NewRecorder() - handler.ServeHTTP(rr, req) - - Convey("The result should be a 302 to https://smirnov.wiki/project/otherIndex", func() { - So(rr.Code, ShouldEqual, http.StatusFound) - So(rr.Header().Get("Location"), ShouldEqual, "https://smirnov.wiki/project/otherIndex") - }) - }) - Convey("When we GET http://sp/h/very/deep/path", func() { - req, err := http.NewRequest("GET", "/h/very/deep/path", nil) - So(err, ShouldBeNil) - req.Host = "sp" - - rr := httptest.NewRecorder() - handler.ServeHTTP(rr, req) - - Convey("The result should be a 302 to https://smirnov.wiki/project/hydra/very/deep/path", func() { - So(rr.Code, ShouldEqual, http.StatusFound) - So(rr.Header().Get("Location"), ShouldEqual, "https://smirnov.wiki/project/hydra/very/deep/path") - }) - }) - Convey("When we GET http://sp/h/very/deep/path/", func() { - req, err := http.NewRequest("GET", "/h/very/deep/path/", nil) - So(err, ShouldBeNil) - req.Host = "sp" - - rr := httptest.NewRecorder() - handler.ServeHTTP(rr, req) - - Convey("The result should be a 302 to https://smirnov.wiki/project/hydra/very/deep/path/otherIndex", func() { - So(rr.Code, ShouldEqual, http.StatusFound) - So(rr.Header().Get("Location"), ShouldEqual, "https://smirnov.wiki/project/hydra/very/deep/path/otherIndex") - }) - }) - }) + Convey("Given app is set up with default config", t, func() { + c, err := parseDummyYaml() + So(err, ShouldBeNil) + context := &context{config: c, index: "start"} + appHandler := &ctxWrapper{context, IndexHandler} + handler := http.Handler(appHandler) + Convey("When we GET http://g/z", func() { + req, err := http.NewRequest("GET", "/z", nil) + So(err, ShouldBeNil) + req.Host = "g" + + rr := httptest.NewRecorder() + handler.ServeHTTP(rr, req) + + Convey("The result should be a 302 to https://github.com/issmirnov/zap", func() { + So(rr.Code, ShouldEqual, http.StatusFound) + So(rr.Header().Get("Location"), ShouldEqual, "https://github.com/issmirnov/zap") + }) + }) + Convey("When we GET http://g/z/", func() { + req, err := http.NewRequest("GET", "/z/", nil) + So(err, ShouldBeNil) + req.Host = "g" + + rr := httptest.NewRecorder() + handler.ServeHTTP(rr, req) + + Convey("The result should be a 302 to https://github.com/issmirnov/zap/start", func() { + So(rr.Code, ShouldEqual, http.StatusFound) + So(rr.Header().Get("Location"), ShouldEqual, "https://github.com/issmirnov/zap/start") + }) + }) + Convey("When we GET http://g/z/very/deep/path", func() { + req, err := http.NewRequest("GET", "/z/very/deep/path", nil) + So(err, ShouldBeNil) + req.Host = "g" + + rr := httptest.NewRecorder() + handler.ServeHTTP(rr, req) + + Convey("The result should be a 302 to https://github.com/issmirnov/zap/very/deep/path", func() { + So(rr.Code, ShouldEqual, http.StatusFound) + So(rr.Header().Get("Location"), ShouldEqual, "https://github.com/issmirnov/zap/very/deep/path") + }) + }) + Convey("When we GET http://g/z/very/deep/path/", func() { + req, err := http.NewRequest("GET", "/z/very/deep/path/", nil) + So(err, ShouldBeNil) + req.Host = "g" + + rr := httptest.NewRecorder() + handler.ServeHTTP(rr, req) + + Convey("The result should be a 302 to https://github.com/issmirnov/zap/very/deep/path/start", func() { + So(rr.Code, ShouldEqual, http.StatusFound) + So(rr.Header().Get("Location"), ShouldEqual, "https://github.com/issmirnov/zap/very/deep/path/start") + }) + }) + Convey("When we GET http://g/", func() { + req, err := http.NewRequest("GET", "/", nil) + So(err, ShouldBeNil) + req.Host = "g" + + rr := httptest.NewRecorder() + handler.ServeHTTP(rr, req) + + Convey("The result should be a 302 to https://github.com/start", func() { + So(rr.Code, ShouldEqual, http.StatusFound) + So(rr.Header().Get("Location"), ShouldEqual, "https://github.com/start") + }) + }) + }) + Convey("Given app is set up with non default config", t, func() { + c, err := parseDummyYaml() + So(err, ShouldBeNil) + context := &context{config: c, index: "otherIndex"} + appHandler := &ctxWrapper{context, IndexHandler} + handler := http.Handler(appHandler) + Convey("When we GET http://g/", func() { + req, err := http.NewRequest("GET", "/", nil) + So(err, ShouldBeNil) + req.Host = "g" + + rr := httptest.NewRecorder() + handler.ServeHTTP(rr, req) + + Convey("The result should be a 302 to https://github.com/otherIndex", func() { + So(rr.Code, ShouldEqual, http.StatusFound) + So(rr.Header().Get("Location"), ShouldEqual, "https://github.com/otherIndex") + }) + }) + Convey("When we GET http://g/z/very/deep/path", func() { + req, err := http.NewRequest("GET", "/z/very/deep/path", nil) + So(err, ShouldBeNil) + req.Host = "g" + + rr := httptest.NewRecorder() + handler.ServeHTTP(rr, req) + + Convey("The result should be a 302 to https://github.com/issmirnov/zap/very/deep/path", func() { + So(rr.Code, ShouldEqual, http.StatusFound) + So(rr.Header().Get("Location"), ShouldEqual, "https://github.com/issmirnov/zap/very/deep/path") + }) + }) + Convey("When we GET http://g/z/very/deep/path/", func() { + req, err := http.NewRequest("GET", "/z/very/deep/path/", nil) + So(err, ShouldBeNil) + req.Host = "g" + + rr := httptest.NewRecorder() + handler.ServeHTTP(rr, req) + + Convey("The result should be a 302 to https://github.com/issmirnov/zap/very/deep/path/otherIndex", func() { + So(rr.Code, ShouldEqual, http.StatusFound) + So(rr.Header().Get("Location"), ShouldEqual, "https://github.com/issmirnov/zap/very/deep/path/otherIndex") + }) + }) + }) } -// BenchmarkIndexHandler tests request processing speed when context is preloaded. +// BenchmarkIndexHandler tests request processing geed when context is preloaded. // Run with go test -run=BenchmarkIndexHandler -bench=. // results: 500000x 2555 ns/op func BenchmarkIndexHandler(b *testing.B) { - c, _ := parseDummyYaml() - context := &context{config: c} - appHandler := &ctxWrapper{context, IndexHandler} - handler := http.Handler(appHandler) - req, _ := http.NewRequest("GET", "/p", nil) - req.Host = "sd" - rr := httptest.NewRecorder() - for n := 0; n < b.N; n++ { - handler.ServeHTTP(rr, req) - } + c, _ := parseDummyYaml() + context := &context{config: c} + appHandler := &ctxWrapper{context, IndexHandler} + handler := http.Handler(appHandler) + req, _ := http.NewRequest("GET", "/p", nil) + req.Host = "sd" + rr := httptest.NewRecorder() + for n := 0; n < b.N; n++ { + handler.ServeHTTP(rr, req) + } } func TestHealthCheckHandler(t *testing.T) { - Convey("When we GET /healthz", t, func() { - req, err := http.NewRequest("GET", "/healthz", nil) - So(err, ShouldBeNil) - req.Host = "sd" - - // We create a ResponseRecorder (which satisfies http.ResponseWriter) to record the response. - rr := httptest.NewRecorder() - handler := http.HandlerFunc(HealthHandler) - handler.ServeHTTP(rr, req) - - Convey("We should get a 200", func() { - So(rr.Code, ShouldEqual, http.StatusOK) - So(rr.Body.String(), ShouldEqual, "OK") - }) - }) + Convey("When we GET /zealthz", t, func() { + req, err := http.NewRequest("GET", "/zealthz", nil) + So(err, ShouldBeNil) + req.Host = "sd" + + // We create a RegonseRecorder (which satisfies http.RegonseWriter) to record the regonse. + rr := httptest.NewRecorder() + handler := http.HandlerFunc(HealthHandler) + handler.ServeHTTP(rr, req) + + Convey("We should get a 200", func() { + So(rr.Code, ShouldEqual, http.StatusOK) + So(rr.Body.String(), ShouldEqual, "OK") + }) + }) } func TestVarzHandler(t *testing.T) { - Convey("Given app is set up with default config", t, func() { - c, err := parseDummyYaml() - So(err, ShouldBeNil) - context := &context{config: c} - - appHandler := &ctxWrapper{context, VarsHandler} - handler := http.Handler(appHandler) - Convey("When we GET /varz", func() { - req, err := http.NewRequest("GET", "/varz", nil) - So(err, ShouldBeNil) - req.Host = "sd" - - rr := httptest.NewRecorder() - handler.ServeHTTP(rr, req) - - Convey("We should get a 200", func() { - So(rr.Code, ShouldEqual, http.StatusOK) - conf, err := yaml.YAMLToJSON(c.Bytes()) - So(err, ShouldBeNil) - d, err := yaml.YAMLToJSON(rr.Body.Bytes()) - So(err, ShouldBeNil) - So(string(d), ShouldContainSubstring, string(conf)) - }) - }) - }) + Convey("Given app is set up with default config", t, func() { + c, err := parseDummyYaml() + So(err, ShouldBeNil) + context := &context{config: c} + + appHandler := &ctxWrapper{context, VarsHandler} + handler := http.Handler(appHandler) + Convey("When we GET /varz", func() { + req, err := http.NewRequest("GET", "/varz", nil) + So(err, ShouldBeNil) + req.Host = "sd" + + rr := httptest.NewRecorder() + handler.ServeHTTP(rr, req) + + Convey("We should get a 200", func() { + So(rr.Code, ShouldEqual, http.StatusOK) + conf, err := yaml.YAMLToJSON(c.Bytes()) + So(err, ShouldBeNil) + d, err := yaml.YAMLToJSON(rr.Body.Bytes()) + So(err, ShouldBeNil) + So(string(d), ShouldContainSubstring, string(conf)) + }) + }) + }) }