├── .dockerignore ├── .gitignore ├── CHANGELOG.md ├── Dockerfile ├── Godeps ├── Godeps.json ├── Readme └── _workspace │ ├── .gitignore │ └── src │ ├── github.com │ ├── codegangsta │ │ └── negroni │ │ │ ├── LICENSE │ │ │ ├── README.md │ │ │ ├── doc.go │ │ │ ├── logger.go │ │ │ ├── logger_test.go │ │ │ ├── negroni.go │ │ │ ├── negroni_test.go │ │ │ ├── recovery.go │ │ │ ├── recovery_test.go │ │ │ ├── response_writer.go │ │ │ ├── response_writer_test.go │ │ │ ├── static.go │ │ │ └── static_test.go │ ├── gorilla │ │ ├── context │ │ │ ├── .travis.yml │ │ │ ├── LICENSE │ │ │ ├── README.md │ │ │ ├── context.go │ │ │ ├── context_test.go │ │ │ └── doc.go │ │ ├── mux │ │ │ ├── .travis.yml │ │ │ ├── LICENSE │ │ │ ├── README.md │ │ │ ├── bench_test.go │ │ │ ├── doc.go │ │ │ ├── mux.go │ │ │ ├── mux_test.go │ │ │ ├── old_test.go │ │ │ ├── regexp.go │ │ │ └── route.go │ │ └── websocket │ │ │ ├── .gitignore │ │ │ ├── .travis.yml │ │ │ ├── AUTHORS │ │ │ ├── LICENSE │ │ │ ├── README.md │ │ │ ├── bench_test.go │ │ │ ├── client.go │ │ │ ├── client_server_test.go │ │ │ ├── client_test.go │ │ │ ├── conn.go │ │ │ ├── conn_test.go │ │ │ ├── doc.go │ │ │ ├── examples │ │ │ ├── autobahn │ │ │ │ ├── README.md │ │ │ │ ├── fuzzingclient.json │ │ │ │ └── server.go │ │ │ ├── chat │ │ │ │ ├── README.md │ │ │ │ ├── conn.go │ │ │ │ ├── home.html │ │ │ │ ├── hub.go │ │ │ │ └── main.go │ │ │ └── filewatch │ │ │ │ ├── README.md │ │ │ │ └── main.go │ │ │ ├── json.go │ │ │ ├── json_test.go │ │ │ ├── server.go │ │ │ ├── server_test.go │ │ │ ├── util.go │ │ │ └── util_test.go │ └── juju │ │ └── errgo │ │ ├── LICENSE │ │ ├── README.markdown │ │ ├── errors.go │ │ ├── errors │ │ ├── errors.go │ │ ├── errors_test.go │ │ └── export_test.go │ │ ├── errors_test.go │ │ └── export_test.go │ └── gopkg.in │ └── unrolled │ └── render.v1 │ ├── .gitignore │ ├── LICENSE │ ├── README.md │ ├── doc.go │ ├── engine.go │ ├── engine_integration_test.go │ ├── fixtures │ ├── amber │ │ ├── example.amber │ │ └── layouts │ │ │ └── base.amber │ ├── basic │ │ ├── admin │ │ │ └── index.tmpl │ │ ├── another_layout.tmpl │ │ ├── content.tmpl │ │ ├── current_layout.tmpl │ │ ├── delims.tmpl │ │ ├── hello.tmpl │ │ ├── hypertext.html │ │ └── layout.tmpl │ └── custom_funcs │ │ └── index.tmpl │ ├── render.go │ └── render_test.go ├── LICENSE ├── README.md ├── angular ├── .bowerrc ├── .editorconfig ├── .gitattributes ├── .gitignore ├── .jshintrc ├── .travis.yml ├── Gruntfile.js ├── app │ ├── .buildignore │ ├── .htaccess │ ├── 404.html │ ├── images │ │ └── yeoman.png │ ├── index.html │ ├── robots.txt │ ├── scripts │ │ ├── app.js │ │ ├── config.js │ │ ├── controllers │ │ │ ├── main.js │ │ │ ├── new-unit.js │ │ │ └── unit.js │ │ ├── filters │ │ │ └── service-name.js │ │ └── services │ │ │ ├── machine-service.js │ │ │ └── unit-service.js │ ├── styles │ │ ├── bootstrap-custom.scss │ │ ├── fa-custom.scss │ │ ├── layout.scss │ │ ├── machine.scss │ │ ├── new-unit.scss │ │ ├── popup.scss │ │ ├── unit.scss │ │ └── util.scss │ └── views │ │ ├── layout.html │ │ ├── main.html │ │ ├── new-unit.html │ │ └── unit.html ├── bower.json ├── package.json └── test │ ├── .jshintrc │ ├── karma.conf.js │ └── spec │ ├── controllers │ ├── main.js │ ├── new-unit.js │ └── unit.js │ ├── filters │ └── service-name.js │ └── services │ ├── machine-service.js │ └── unit-service.js ├── app.go ├── build.sh ├── fleet_client.go ├── fleet_client_cli.go ├── fleet_client_cli_status.go ├── handler.go ├── images ├── screenshot.png ├── screenshot2.png └── screenshot3.png ├── run.sh ├── tmp ├── app.1.service └── app@1.service └── util.go /.dockerignore: -------------------------------------------------------------------------------- 1 | Godeps 2 | angular/.tmp 3 | angular/.sass-cache 4 | angular/app 5 | angular/bower_components 6 | angular/node_modules 7 | angular/test 8 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | public 2 | tmp 3 | fleet-ui 4 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # fleet-ui 2 | 3 | ## 0.1.12 4 | * update ETCD_PEER option document 5 | * update fleetctl (v0.10.2) 6 | 7 | ## 0.1.11 8 | * support wss(websocket over http and https) 9 | * add bind address and port config 10 | * add failed style 11 | 12 | ## 0.1.10 13 | * improve websocket error handling 14 | * server source refactoring 15 | 16 | ## 0.1.9 17 | * improve error handling 18 | 19 | ## 0.1.8 20 | * update fleetctl version 21 | 22 | ## 0.1.6 23 | * fix tmp directory not found issue (#11) 24 | * update build.sh 25 | 26 | ## 0.1.5 27 | * change host ip 28 | 29 | ## 0.1.4 30 | * update fleetctl to v0.9.0 31 | 32 | ## 0.1.3 33 | * add ssh-util to docker image 34 | 35 | ## 0.1.2 36 | * optimize docker image size (only 25MB) 37 | 38 | ## 0.1.1 39 | * fix run.sh 40 | 41 | ## 0.1.0 42 | * support no private key (fix #2) 43 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | # docker build 2 | # docker build -t purpleworks/fleet-ui . 3 | # 4 | # docker run 5 | # docker run --rm -p [port]:3000 -e ETCD_PEER=[your_etcd_peer_ip] -v [your_ssh_private_key_file_path]:/root/id_rsa purpleworks/fleet-ui 6 | # docker run --rm -p 3000:3000 -e ETCD_PEER=10.0.0.1 -v ~/.ssh/id_rsa:/root/id_rsa purpleworks/fleet-ui 7 | 8 | FROM progrium/busybox 9 | MAINTAINER app@purpleworks.co.kr 10 | 11 | # install packages 12 | RUN opkg-install openssh-client-utils 13 | 14 | # add files 15 | ADD run.sh /root/fleet-ui/run.sh 16 | ADD tmp/fleet-ui /root/fleet-ui/fleet-ui 17 | ADD tmp/fleetctl /usr/local/bin/fleetctl 18 | ADD angular/dist /root/fleet-ui/public 19 | 20 | # set workdir 21 | WORKDIR /root/fleet-ui 22 | 23 | # export port 24 | EXPOSE 3000 25 | 26 | # run! 27 | CMD ./run.sh 28 | -------------------------------------------------------------------------------- /Godeps/Godeps.json: -------------------------------------------------------------------------------- 1 | { 2 | "ImportPath": "github.com/purpleworks/fleet-ui", 3 | "GoVersion": "go1.3.3", 4 | "Deps": [ 5 | { 6 | "ImportPath": "github.com/codegangsta/negroni", 7 | "Comment": "v0.1-51-g2b6e86b", 8 | "Rev": "2b6e86bff68451fcd38bd9e2da90e0b70e85983d" 9 | }, 10 | { 11 | "ImportPath": "github.com/gorilla/context", 12 | "Rev": "50c25fb3b2b3b3cc724e9b6ac75fb44b3bccd0da" 13 | }, 14 | { 15 | "ImportPath": "github.com/gorilla/mux", 16 | "Rev": "e444e69cbd2e2e3e0749a2f3c717cec491552bbf" 17 | }, 18 | { 19 | "ImportPath": "github.com/gorilla/websocket", 20 | "Rev": "9007e29a7c93fc3dcebbdbf742ce0efaf19e6510" 21 | }, 22 | { 23 | "ImportPath": "github.com/juju/errgo", 24 | "Rev": "08cceb5d0b5331634b9826762a8fd53b29b86ad8" 25 | }, 26 | { 27 | "ImportPath": "gopkg.in/unrolled/render.v1", 28 | "Rev": "4796b5bc747483bf9b20a50b2c80e00ed2c85fab" 29 | } 30 | ] 31 | } 32 | -------------------------------------------------------------------------------- /Godeps/Readme: -------------------------------------------------------------------------------- 1 | This directory tree is generated automatically by godep. 2 | 3 | Please do not edit. 4 | 5 | See https://github.com/tools/godep for more information. 6 | -------------------------------------------------------------------------------- /Godeps/_workspace/.gitignore: -------------------------------------------------------------------------------- 1 | /pkg 2 | /bin 3 | -------------------------------------------------------------------------------- /Godeps/_workspace/src/github.com/codegangsta/negroni/LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 Jeremy Saenz 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Godeps/_workspace/src/github.com/codegangsta/negroni/README.md: -------------------------------------------------------------------------------- 1 | # Negroni [![GoDoc](https://godoc.org/github.com/codegangsta/negroni?status.svg)](http://godoc.org/github.com/codegangsta/negroni) [![wercker status](https://app.wercker.com/status/13688a4a94b82d84a0b8d038c4965b61/s "wercker status")](https://app.wercker.com/project/bykey/13688a4a94b82d84a0b8d038c4965b61) 2 | 3 | Negroni is an idiomatic approach to web middleware in Go. It is tiny, non-intrusive, and encourages use of `net/http` Handlers. 4 | 5 | If you like the idea of [Martini](http://github.com/go-martini/martini), but you think it contains too much magic, then Negroni is a great fit. 6 | 7 | ## Getting Started 8 | 9 | After installing Go and setting up your [GOPATH](http://golang.org/doc/code.html#GOPATH), create your first `.go` file. We'll call it `server.go`. 10 | 11 | ~~~ go 12 | package main 13 | 14 | import ( 15 | "github.com/codegangsta/negroni" 16 | "net/http" 17 | "fmt" 18 | ) 19 | 20 | func main() { 21 | mux := http.NewServeMux() 22 | mux.HandleFunc("/", func(w http.ResponseWriter, req *http.Request) { 23 | fmt.Fprintf(w, "Welcome to the home page!") 24 | }) 25 | 26 | n := negroni.Classic() 27 | n.UseHandler(mux) 28 | n.Run(":3000") 29 | } 30 | ~~~ 31 | 32 | Then install the Negroni package (**go 1.1** and greater is required): 33 | ~~~ 34 | go get github.com/codegangsta/negroni 35 | ~~~ 36 | 37 | Then run your server: 38 | ~~~ 39 | go run server.go 40 | ~~~ 41 | 42 | You will now have a Go net/http webserver running on `localhost:3000`. 43 | 44 | ## Need Help? 45 | If you have a question or feature request, [go ask the mailing list](https://groups.google.com/forum/#!forum/negroni-users). The GitHub issues for Negroni will be used exclusively for bug reports and pull requests. 46 | 47 | ## Is Negroni a Framework? 48 | Negroni is **not** a framework. It is a library that is designed to work directly with net/http. 49 | 50 | ## Routing? 51 | Negroni is BYOR (Bring your own Router). The Go community already has a number of great http routers available, Negroni tries to play well with all of them by fully supporting `net/http`. For instance, integrating with [Gorilla Mux](http://github.com/gorilla/mux) looks like so: 52 | 53 | ~~~ go 54 | router := mux.NewRouter() 55 | router.HandleFunc("/", HomeHandler) 56 | 57 | n := negroni.New(Middleware1, Middleware2) 58 | // Or use a middleware with the Use() function 59 | n.Use(Middleware3) 60 | // router goes last 61 | n.UseHandler(router) 62 | 63 | n.Run(":3000") 64 | ~~~ 65 | 66 | ## `negroni.Classic()` 67 | `negroni.Classic()` provides some default middleware that is useful for most applications: 68 | 69 | * `negroni.Recovery` - Panic Recovery Middleware. 70 | * `negroni.Logging` - Request/Response Logging Middleware. 71 | * `negroni.Static` - Static File serving under the "public" directory. 72 | 73 | This makes it really easy to get started with some useful features from Negroni. 74 | 75 | ## Handlers 76 | Negroni provides a bidirectional middleware flow. This is done through the `negroni.Handler` interface: 77 | 78 | ~~~ go 79 | type Handler interface { 80 | ServeHTTP(rw http.ResponseWriter, r *http.Request, next http.HandlerFunc) 81 | } 82 | ~~~ 83 | 84 | If a middleware hasn't already written to the ResponseWriter, it should call the next `http.HandlerFunc` in the chain to yield to the next middleware handler. This can be used for great good: 85 | 86 | ~~~ go 87 | func MyMiddleware(rw http.ResponseWriter, r *http.Request, next http.HandlerFunc) { 88 | // do some stuff before 89 | next(rw, r) 90 | // do some stuff after 91 | } 92 | ~~~ 93 | 94 | And you can map it to the handler chain with the `Use` function: 95 | 96 | ~~~ go 97 | n := negroni.New() 98 | n.Use(negroni.HandlerFunc(MyMiddleware)) 99 | ~~~ 100 | 101 | You can also map plain old `http.Handler`s: 102 | 103 | ~~~ go 104 | n := negroni.New() 105 | 106 | mux := http.NewServeMux() 107 | // map your routes 108 | 109 | n.UseHandler(mux) 110 | 111 | n.Run(":3000") 112 | ~~~ 113 | 114 | ## `Run()` 115 | Negroni has a convenience function called `Run`. `Run` takes an addr string identical to [http.ListenAndServe](http://golang.org/pkg/net/http#ListenAndServe). 116 | 117 | ~~~ go 118 | n := negroni.Classic() 119 | // ... 120 | log.Fatal(http.ListenAndServe(":8080", n)) 121 | ~~~ 122 | 123 | ## Route Specific Middleware 124 | If you have a route group of routes that need specific middleware to be executed, you can simply create a new Negroni instance and use it as your route handler. 125 | 126 | ~~~ go 127 | router := mux.NewRouter() 128 | adminRoutes := mux.NewRouter() 129 | // add admin routes here 130 | 131 | // Create a new negroni for the admin middleware 132 | router.Handle("/admin", negroni.New( 133 | Middleware1, 134 | Middleware2, 135 | negroni.Wrap(adminRoutes), 136 | )) 137 | ~~~ 138 | 139 | ## Third Party Middleware 140 | 141 | Here is a current list of Negroni compatible middlware. Feel free to put up a PR linking your middleware if you have built one: 142 | 143 | 144 | | Middleware | Author | Description | 145 | | -----------|--------|-------------| 146 | | [RestGate](https://github.com/pjebs/restgate) | [Prasanga Siripala](https://github.com/pjebs) | Secure authentication for REST API endpoints | 147 | | [Graceful](https://github.com/stretchr/graceful) | [Tyler Bunnell](https://github.com/tylerb) | Graceful HTTP Shutdown | 148 | | [secure](https://github.com/unrolled/secure) | [Cory Jacobsen](https://github.com/unrolled) | Middleware that implements a few quick security wins | 149 | | [binding](https://github.com/mholt/binding) | [Matt Holt](https://github.com/mholt) | Data binding from HTTP requests into structs | 150 | | [logrus](https://github.com/meatballhat/negroni-logrus) | [Dan Buch](https://github.com/meatballhat) | Logrus-based logger | 151 | | [render](https://github.com/unrolled/render) | [Cory Jacobsen](https://github.com/unrolled) | Render JSON, XML and HTML templates | 152 | | [gorelic](https://github.com/jingweno/negroni-gorelic) | [Jingwen Owen Ou](https://github.com/jingweno) | New Relic agent for Go runtime | 153 | | [gzip](https://github.com/phyber/negroni-gzip) | [phyber](https://github.com/phyber) | GZIP response compression | 154 | | [oauth2](https://github.com/goincremental/negroni-oauth2) | [David Bochenski](https://github.com/bochenski) | oAuth2 middleware | 155 | | [sessions](https://github.com/goincremental/negroni-sessions) | [David Bochenski](https://github.com/bochenski) | Session Management | 156 | | [permissions2](https://github.com/xyproto/permissions2) | [Alexander Rødseth](https://github.com/xyproto) | Cookies, users and permissions | 157 | | [onthefly](https://github.com/xyproto/onthefly) | [Alexander Rødseth](https://github.com/xyproto) | Generate TinySVG, HTML and CSS on the fly | 158 | | [cors](https://github.com/rs/cors) | [Olivier Poitrey](https://github.com/rs) | [Cross Origin Resource Sharing](http://www.w3.org/TR/cors/) (CORS) support | 159 | | [xrequestid](https://github.com/pilu/xrequestid) | [Andrea Franz](https://github.com/pilu) | Middleware that assigns a random X-Request-Id header to each request | 160 | | [VanGoH](https://github.com/auroratechnologies/vangoh) | [Taylor Wrobel](https://github.com/twrobel3) | Configurable [AWS-Style](http://docs.aws.amazon.com/AmazonS3/latest/dev/RESTAuthentication.html) HMAC authentication middleware | 161 | 162 | ## Examples 163 | [Alexander Rødseth](https://github.com/xyproto) created [mooseware](https://github.com/xyproto/mooseware), a skeleton for writing a Negroni middleware handler. 164 | 165 | ## Live code reload? 166 | [gin](https://github.com/codegangsta/gin) and [fresh](https://github.com/pilu/fresh) both live reload negroni apps. 167 | 168 | ## Essential Reading for Beginners of Go & Negroni 169 | 170 | * [Using a Context to pass information from middleware to end handler](http://elithrar.github.io/article/map-string-interface/) 171 | * [Understanding middleware](http://mattstauffer.co/blog/laravel-5.0-middleware-replacing-filters) 172 | 173 | ## About 174 | 175 | Negroni is obsessively designed by none other than the [Code Gangsta](http://codegangsta.io/) 176 | -------------------------------------------------------------------------------- /Godeps/_workspace/src/github.com/codegangsta/negroni/doc.go: -------------------------------------------------------------------------------- 1 | // Package negroni is an idiomatic approach to web middleware in Go. It is tiny, non-intrusive, and encourages use of net/http Handlers. 2 | // 3 | // If you like the idea of Martini, but you think it contains too much magic, then Negroni is a great fit. 4 | // 5 | // For a full guide visit http://github.com/codegangsta/negroni 6 | // 7 | // package main 8 | // 9 | // import ( 10 | // "github.com/codegangsta/negroni" 11 | // "net/http" 12 | // "fmt" 13 | // ) 14 | // 15 | // func main() { 16 | // mux := http.NewServeMux() 17 | // mux.HandleFunc("/", func(w http.ResponseWriter, req *http.Request) { 18 | // fmt.Fprintf(w, "Welcome to the home page!") 19 | // }) 20 | // 21 | // n := negroni.Classic() 22 | // n.UseHandler(mux) 23 | // n.Run(":3000") 24 | // } 25 | package negroni 26 | -------------------------------------------------------------------------------- /Godeps/_workspace/src/github.com/codegangsta/negroni/logger.go: -------------------------------------------------------------------------------- 1 | package negroni 2 | 3 | import ( 4 | "log" 5 | "net/http" 6 | "os" 7 | "time" 8 | ) 9 | 10 | // Logger is a middleware handler that logs the request as it goes in and the response as it goes out. 11 | type Logger struct { 12 | // Logger inherits from log.Logger used to log messages with the Logger middleware 13 | *log.Logger 14 | } 15 | 16 | // NewLogger returns a new Logger instance 17 | func NewLogger() *Logger { 18 | return &Logger{log.New(os.Stdout, "[negroni] ", 0)} 19 | } 20 | 21 | func (l *Logger) ServeHTTP(rw http.ResponseWriter, r *http.Request, next http.HandlerFunc) { 22 | start := time.Now() 23 | l.Printf("Started %s %s", r.Method, r.URL.Path) 24 | 25 | next(rw, r) 26 | 27 | res := rw.(ResponseWriter) 28 | l.Printf("Completed %v %s in %v", res.Status(), http.StatusText(res.Status()), time.Since(start)) 29 | } 30 | -------------------------------------------------------------------------------- /Godeps/_workspace/src/github.com/codegangsta/negroni/logger_test.go: -------------------------------------------------------------------------------- 1 | package negroni 2 | 3 | import ( 4 | "bytes" 5 | "log" 6 | "net/http" 7 | "net/http/httptest" 8 | "testing" 9 | ) 10 | 11 | func Test_Logger(t *testing.T) { 12 | buff := bytes.NewBufferString("") 13 | recorder := httptest.NewRecorder() 14 | 15 | l := NewLogger() 16 | l.Logger = log.New(buff, "[negroni] ", 0) 17 | 18 | n := New() 19 | // replace log for testing 20 | n.Use(l) 21 | n.UseHandler(http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) { 22 | rw.WriteHeader(http.StatusNotFound) 23 | })) 24 | 25 | req, err := http.NewRequest("GET", "http://localhost:3000/foobar", nil) 26 | if err != nil { 27 | t.Error(err) 28 | } 29 | 30 | n.ServeHTTP(recorder, req) 31 | expect(t, recorder.Code, http.StatusNotFound) 32 | refute(t, len(buff.String()), 0) 33 | } 34 | -------------------------------------------------------------------------------- /Godeps/_workspace/src/github.com/codegangsta/negroni/negroni.go: -------------------------------------------------------------------------------- 1 | package negroni 2 | 3 | import ( 4 | "log" 5 | "net/http" 6 | "os" 7 | ) 8 | 9 | // Handler handler is an interface that objects can implement to be registered to serve as middleware 10 | // in the Negroni middleware stack. 11 | // ServeHTTP should yield to the next middleware in the chain by invoking the next http.HandlerFunc 12 | // passed in. 13 | // 14 | // If the Handler writes to the ResponseWriter, the next http.HandlerFunc should not be invoked. 15 | type Handler interface { 16 | ServeHTTP(rw http.ResponseWriter, r *http.Request, next http.HandlerFunc) 17 | } 18 | 19 | // HandlerFunc is an adapter to allow the use of ordinary functions as Negroni handlers. 20 | // If f is a function with the appropriate signature, HandlerFunc(f) is a Handler object that calls f. 21 | type HandlerFunc func(rw http.ResponseWriter, r *http.Request, next http.HandlerFunc) 22 | 23 | func (h HandlerFunc) ServeHTTP(rw http.ResponseWriter, r *http.Request, next http.HandlerFunc) { 24 | h(rw, r, next) 25 | } 26 | 27 | type middleware struct { 28 | handler Handler 29 | next *middleware 30 | } 31 | 32 | func (m middleware) ServeHTTP(rw http.ResponseWriter, r *http.Request) { 33 | m.handler.ServeHTTP(rw, r, m.next.ServeHTTP) 34 | } 35 | 36 | // Wrap converts a http.Handler into a negroni.Handler so it can be used as a Negroni 37 | // middleware. The next http.HandlerFunc is automatically called after the Handler 38 | // is executed. 39 | func Wrap(handler http.Handler) Handler { 40 | return HandlerFunc(func(rw http.ResponseWriter, r *http.Request, next http.HandlerFunc) { 41 | handler.ServeHTTP(rw, r) 42 | next(rw, r) 43 | }) 44 | } 45 | 46 | // Negroni is a stack of Middleware Handlers that can be invoked as an http.Handler. 47 | // Negroni middleware is evaluated in the order that they are added to the stack using 48 | // the Use and UseHandler methods. 49 | type Negroni struct { 50 | middleware middleware 51 | handlers []Handler 52 | } 53 | 54 | // New returns a new Negroni instance with no middleware preconfigured. 55 | func New(handlers ...Handler) *Negroni { 56 | return &Negroni{ 57 | handlers: handlers, 58 | middleware: build(handlers), 59 | } 60 | } 61 | 62 | // Classic returns a new Negroni instance with the default middleware already 63 | // in the stack. 64 | // 65 | // Recovery - Panic Recovery Middleware 66 | // Logger - Request/Response Logging 67 | // Static - Static File Serving 68 | func Classic() *Negroni { 69 | return New(NewRecovery(), NewLogger(), NewStatic(http.Dir("public"))) 70 | } 71 | 72 | func (n *Negroni) ServeHTTP(rw http.ResponseWriter, r *http.Request) { 73 | n.middleware.ServeHTTP(NewResponseWriter(rw), r) 74 | } 75 | 76 | // Use adds a Handler onto the middleware stack. Handlers are invoked in the order they are added to a Negroni. 77 | func (n *Negroni) Use(handler Handler) { 78 | n.handlers = append(n.handlers, handler) 79 | n.middleware = build(n.handlers) 80 | } 81 | 82 | // UseHandler adds a http.Handler onto the middleware stack. Handlers are invoked in the order they are added to a Negroni. 83 | func (n *Negroni) UseHandler(handler http.Handler) { 84 | n.Use(Wrap(handler)) 85 | } 86 | 87 | // Run is a convenience function that runs the negroni stack as an HTTP 88 | // server. The addr string takes the same format as http.ListenAndServe. 89 | func (n *Negroni) Run(addr string) { 90 | l := log.New(os.Stdout, "[negroni] ", 0) 91 | l.Printf("listening on %s", addr) 92 | l.Fatal(http.ListenAndServe(addr, n)) 93 | } 94 | 95 | // Returns a list of all the handlers in the current Negroni middleware chain. 96 | func (n *Negroni) Handlers() ([]Handler) { 97 | return n.handlers 98 | } 99 | 100 | func build(handlers []Handler) middleware { 101 | var next middleware 102 | 103 | if len(handlers) == 0 { 104 | return voidMiddleware() 105 | } else if len(handlers) > 1 { 106 | next = build(handlers[1:]) 107 | } else { 108 | next = voidMiddleware() 109 | } 110 | 111 | return middleware{handlers[0], &next} 112 | } 113 | 114 | func voidMiddleware() middleware { 115 | return middleware{ 116 | HandlerFunc(func(rw http.ResponseWriter, r *http.Request, next http.HandlerFunc) {}), 117 | &middleware{}, 118 | } 119 | } 120 | -------------------------------------------------------------------------------- /Godeps/_workspace/src/github.com/codegangsta/negroni/negroni_test.go: -------------------------------------------------------------------------------- 1 | package negroni 2 | 3 | import ( 4 | "net/http" 5 | "net/http/httptest" 6 | "reflect" 7 | "testing" 8 | ) 9 | 10 | /* Test Helpers */ 11 | func expect(t *testing.T, a interface{}, b interface{}) { 12 | if a != b { 13 | t.Errorf("Expected %v (type %v) - Got %v (type %v)", b, reflect.TypeOf(b), a, reflect.TypeOf(a)) 14 | } 15 | } 16 | 17 | func refute(t *testing.T, a interface{}, b interface{}) { 18 | if a == b { 19 | t.Errorf("Did not expect %v (type %v) - Got %v (type %v)", b, reflect.TypeOf(b), a, reflect.TypeOf(a)) 20 | } 21 | } 22 | 23 | func TestNegroniRun(t *testing.T) { 24 | // just test that Run doesn't bomb 25 | go New().Run(":3000") 26 | } 27 | 28 | func TestNegroniServeHTTP(t *testing.T) { 29 | result := "" 30 | response := httptest.NewRecorder() 31 | 32 | n := New() 33 | n.Use(HandlerFunc(func(rw http.ResponseWriter, r *http.Request, next http.HandlerFunc) { 34 | result += "foo" 35 | next(rw, r) 36 | result += "ban" 37 | })) 38 | n.Use(HandlerFunc(func(rw http.ResponseWriter, r *http.Request, next http.HandlerFunc) { 39 | result += "bar" 40 | next(rw, r) 41 | result += "baz" 42 | })) 43 | n.Use(HandlerFunc(func(rw http.ResponseWriter, r *http.Request, next http.HandlerFunc) { 44 | result += "bat" 45 | rw.WriteHeader(http.StatusBadRequest) 46 | })) 47 | 48 | n.ServeHTTP(response, (*http.Request)(nil)) 49 | 50 | expect(t, result, "foobarbatbazban") 51 | expect(t, response.Code, http.StatusBadRequest) 52 | } 53 | 54 | // Ensures that a Negroni middleware chain 55 | // can correctly return all of its handlers. 56 | func TestHandlers(t *testing.T) { 57 | response := httptest.NewRecorder() 58 | n := New() 59 | handlers := n.Handlers() 60 | expect(t, 0, len(handlers)) 61 | 62 | n.Use(HandlerFunc(func(rw http.ResponseWriter, r *http.Request, next http.HandlerFunc) { 63 | rw.WriteHeader(http.StatusOK) 64 | })) 65 | 66 | // Expects the length of handlers to be exactly 1 67 | // after adding exactly one handler to the middleware chain 68 | handlers = n.Handlers() 69 | expect(t, 1, len(handlers)) 70 | 71 | // Ensures that the first handler that is in sequence behaves 72 | // exactly the same as the one that was registered earlier 73 | handlers[0].ServeHTTP(response, (*http.Request)(nil), nil) 74 | expect(t, response.Code, http.StatusOK) 75 | } -------------------------------------------------------------------------------- /Godeps/_workspace/src/github.com/codegangsta/negroni/recovery.go: -------------------------------------------------------------------------------- 1 | package negroni 2 | 3 | import ( 4 | "fmt" 5 | "log" 6 | "net/http" 7 | "os" 8 | "runtime" 9 | ) 10 | 11 | // Recovery is a Negroni middleware that recovers from any panics and writes a 500 if there was one. 12 | type Recovery struct { 13 | Logger *log.Logger 14 | PrintStack bool 15 | StackAll bool 16 | StackSize int 17 | } 18 | 19 | // NewRecovery returns a new instance of Recovery 20 | func NewRecovery() *Recovery { 21 | return &Recovery{ 22 | Logger: log.New(os.Stdout, "[negroni] ", 0), 23 | PrintStack: true, 24 | StackAll: false, 25 | StackSize: 1024 * 8, 26 | } 27 | } 28 | 29 | func (rec *Recovery) ServeHTTP(rw http.ResponseWriter, r *http.Request, next http.HandlerFunc) { 30 | defer func() { 31 | if err := recover(); err != nil { 32 | rw.WriteHeader(http.StatusInternalServerError) 33 | stack := make([]byte, rec.StackSize) 34 | stack = stack[:runtime.Stack(stack, rec.StackAll)] 35 | 36 | f := "PANIC: %s\n%s" 37 | rec.Logger.Printf(f, err, stack) 38 | 39 | if rec.PrintStack { 40 | fmt.Fprintf(rw, f, err, stack) 41 | } 42 | } 43 | }() 44 | 45 | next(rw, r) 46 | } 47 | -------------------------------------------------------------------------------- /Godeps/_workspace/src/github.com/codegangsta/negroni/recovery_test.go: -------------------------------------------------------------------------------- 1 | package negroni 2 | 3 | import ( 4 | "bytes" 5 | "log" 6 | "net/http" 7 | "net/http/httptest" 8 | "testing" 9 | ) 10 | 11 | func TestRecovery(t *testing.T) { 12 | buff := bytes.NewBufferString("") 13 | recorder := httptest.NewRecorder() 14 | 15 | rec := NewRecovery() 16 | rec.Logger = log.New(buff, "[negroni] ", 0) 17 | 18 | n := New() 19 | // replace log for testing 20 | n.Use(rec) 21 | n.UseHandler(http.HandlerFunc(func(res http.ResponseWriter, req *http.Request) { 22 | panic("here is a panic!") 23 | })) 24 | n.ServeHTTP(recorder, (*http.Request)(nil)) 25 | expect(t, recorder.Code, http.StatusInternalServerError) 26 | refute(t, recorder.Body.Len(), 0) 27 | refute(t, len(buff.String()), 0) 28 | } 29 | -------------------------------------------------------------------------------- /Godeps/_workspace/src/github.com/codegangsta/negroni/response_writer.go: -------------------------------------------------------------------------------- 1 | package negroni 2 | 3 | import ( 4 | "bufio" 5 | "fmt" 6 | "net" 7 | "net/http" 8 | ) 9 | 10 | // ResponseWriter is a wrapper around http.ResponseWriter that provides extra information about 11 | // the response. It is recommended that middleware handlers use this construct to wrap a responsewriter 12 | // if the functionality calls for it. 13 | type ResponseWriter interface { 14 | http.ResponseWriter 15 | http.Flusher 16 | // Status returns the status code of the response or 0 if the response has not been written. 17 | Status() int 18 | // Written returns whether or not the ResponseWriter has been written. 19 | Written() bool 20 | // Size returns the size of the response body. 21 | Size() int 22 | // Before allows for a function to be called before the ResponseWriter has been written to. This is 23 | // useful for setting headers or any other operations that must happen before a response has been written. 24 | Before(func(ResponseWriter)) 25 | } 26 | 27 | type beforeFunc func(ResponseWriter) 28 | 29 | // NewResponseWriter creates a ResponseWriter that wraps an http.ResponseWriter 30 | func NewResponseWriter(rw http.ResponseWriter) ResponseWriter { 31 | return &responseWriter{rw, 0, 0, nil} 32 | } 33 | 34 | type responseWriter struct { 35 | http.ResponseWriter 36 | status int 37 | size int 38 | beforeFuncs []beforeFunc 39 | } 40 | 41 | func (rw *responseWriter) WriteHeader(s int) { 42 | rw.status = s 43 | rw.callBefore() 44 | rw.ResponseWriter.WriteHeader(s) 45 | } 46 | 47 | func (rw *responseWriter) Write(b []byte) (int, error) { 48 | if !rw.Written() { 49 | // The status will be StatusOK if WriteHeader has not been called yet 50 | rw.WriteHeader(http.StatusOK) 51 | } 52 | size, err := rw.ResponseWriter.Write(b) 53 | rw.size += size 54 | return size, err 55 | } 56 | 57 | func (rw *responseWriter) Status() int { 58 | return rw.status 59 | } 60 | 61 | func (rw *responseWriter) Size() int { 62 | return rw.size 63 | } 64 | 65 | func (rw *responseWriter) Written() bool { 66 | return rw.status != 0 67 | } 68 | 69 | func (rw *responseWriter) Before(before func(ResponseWriter)) { 70 | rw.beforeFuncs = append(rw.beforeFuncs, before) 71 | } 72 | 73 | func (rw *responseWriter) Hijack() (net.Conn, *bufio.ReadWriter, error) { 74 | hijacker, ok := rw.ResponseWriter.(http.Hijacker) 75 | if !ok { 76 | return nil, nil, fmt.Errorf("the ResponseWriter doesn't support the Hijacker interface") 77 | } 78 | return hijacker.Hijack() 79 | } 80 | 81 | func (rw *responseWriter) CloseNotify() <-chan bool { 82 | return rw.ResponseWriter.(http.CloseNotifier).CloseNotify() 83 | } 84 | 85 | func (rw *responseWriter) callBefore() { 86 | for i := len(rw.beforeFuncs) - 1; i >= 0; i-- { 87 | rw.beforeFuncs[i](rw) 88 | } 89 | } 90 | 91 | func (rw *responseWriter) Flush() { 92 | flusher, ok := rw.ResponseWriter.(http.Flusher) 93 | if ok { 94 | flusher.Flush() 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /Godeps/_workspace/src/github.com/codegangsta/negroni/response_writer_test.go: -------------------------------------------------------------------------------- 1 | package negroni 2 | 3 | import ( 4 | "bufio" 5 | "net" 6 | "net/http" 7 | "net/http/httptest" 8 | "testing" 9 | "time" 10 | ) 11 | 12 | type closeNotifyingRecorder struct { 13 | *httptest.ResponseRecorder 14 | closed chan bool 15 | } 16 | 17 | func newCloseNotifyingRecorder() *closeNotifyingRecorder { 18 | return &closeNotifyingRecorder{ 19 | httptest.NewRecorder(), 20 | make(chan bool, 1), 21 | } 22 | } 23 | 24 | func (c *closeNotifyingRecorder) close() { 25 | c.closed <- true 26 | } 27 | 28 | func (c *closeNotifyingRecorder) CloseNotify() <-chan bool { 29 | return c.closed 30 | } 31 | 32 | type hijackableResponse struct { 33 | Hijacked bool 34 | } 35 | 36 | func newHijackableResponse() *hijackableResponse { 37 | return &hijackableResponse{} 38 | } 39 | 40 | func (h *hijackableResponse) Header() http.Header { return nil } 41 | func (h *hijackableResponse) Write(buf []byte) (int, error) { return 0, nil } 42 | func (h *hijackableResponse) WriteHeader(code int) {} 43 | func (h *hijackableResponse) Flush() {} 44 | func (h *hijackableResponse) Hijack() (net.Conn, *bufio.ReadWriter, error) { 45 | h.Hijacked = true 46 | return nil, nil, nil 47 | } 48 | 49 | func TestResponseWriterWritingString(t *testing.T) { 50 | rec := httptest.NewRecorder() 51 | rw := NewResponseWriter(rec) 52 | 53 | rw.Write([]byte("Hello world")) 54 | 55 | expect(t, rec.Code, rw.Status()) 56 | expect(t, rec.Body.String(), "Hello world") 57 | expect(t, rw.Status(), http.StatusOK) 58 | expect(t, rw.Size(), 11) 59 | expect(t, rw.Written(), true) 60 | } 61 | 62 | func TestResponseWriterWritingStrings(t *testing.T) { 63 | rec := httptest.NewRecorder() 64 | rw := NewResponseWriter(rec) 65 | 66 | rw.Write([]byte("Hello world")) 67 | rw.Write([]byte("foo bar bat baz")) 68 | 69 | expect(t, rec.Code, rw.Status()) 70 | expect(t, rec.Body.String(), "Hello worldfoo bar bat baz") 71 | expect(t, rw.Status(), http.StatusOK) 72 | expect(t, rw.Size(), 26) 73 | } 74 | 75 | func TestResponseWriterWritingHeader(t *testing.T) { 76 | rec := httptest.NewRecorder() 77 | rw := NewResponseWriter(rec) 78 | 79 | rw.WriteHeader(http.StatusNotFound) 80 | 81 | expect(t, rec.Code, rw.Status()) 82 | expect(t, rec.Body.String(), "") 83 | expect(t, rw.Status(), http.StatusNotFound) 84 | expect(t, rw.Size(), 0) 85 | } 86 | 87 | func TestResponseWriterBefore(t *testing.T) { 88 | rec := httptest.NewRecorder() 89 | rw := NewResponseWriter(rec) 90 | result := "" 91 | 92 | rw.Before(func(ResponseWriter) { 93 | result += "foo" 94 | }) 95 | rw.Before(func(ResponseWriter) { 96 | result += "bar" 97 | }) 98 | 99 | rw.WriteHeader(http.StatusNotFound) 100 | 101 | expect(t, rec.Code, rw.Status()) 102 | expect(t, rec.Body.String(), "") 103 | expect(t, rw.Status(), http.StatusNotFound) 104 | expect(t, rw.Size(), 0) 105 | expect(t, result, "barfoo") 106 | } 107 | 108 | func TestResponseWriterHijack(t *testing.T) { 109 | hijackable := newHijackableResponse() 110 | rw := NewResponseWriter(hijackable) 111 | hijacker, ok := rw.(http.Hijacker) 112 | expect(t, ok, true) 113 | _, _, err := hijacker.Hijack() 114 | if err != nil { 115 | t.Error(err) 116 | } 117 | expect(t, hijackable.Hijacked, true) 118 | } 119 | 120 | func TestResponseWriteHijackNotOK(t *testing.T) { 121 | hijackable := new(http.ResponseWriter) 122 | rw := NewResponseWriter(*hijackable) 123 | hijacker, ok := rw.(http.Hijacker) 124 | expect(t, ok, true) 125 | _, _, err := hijacker.Hijack() 126 | 127 | refute(t, err, nil) 128 | } 129 | 130 | func TestResponseWriterCloseNotify(t *testing.T) { 131 | rec := newCloseNotifyingRecorder() 132 | rw := NewResponseWriter(rec) 133 | closed := false 134 | notifier := rw.(http.CloseNotifier).CloseNotify() 135 | rec.close() 136 | select { 137 | case <-notifier: 138 | closed = true 139 | case <-time.After(time.Second): 140 | } 141 | expect(t, closed, true) 142 | } 143 | 144 | func TestResponseWriterFlusher(t *testing.T) { 145 | rec := httptest.NewRecorder() 146 | rw := NewResponseWriter(rec) 147 | 148 | _, ok := rw.(http.Flusher) 149 | expect(t, ok, true) 150 | } 151 | -------------------------------------------------------------------------------- /Godeps/_workspace/src/github.com/codegangsta/negroni/static.go: -------------------------------------------------------------------------------- 1 | package negroni 2 | 3 | import ( 4 | "net/http" 5 | "path" 6 | "strings" 7 | ) 8 | 9 | // Static is a middleware handler that serves static files in the given directory/filesystem. 10 | type Static struct { 11 | // Dir is the directory to serve static files from 12 | Dir http.FileSystem 13 | // Prefix is the optional prefix used to serve the static directory content 14 | Prefix string 15 | // IndexFile defines which file to serve as index if it exists. 16 | IndexFile string 17 | } 18 | 19 | // NewStatic returns a new instance of Static 20 | func NewStatic(directory http.FileSystem) *Static { 21 | return &Static{ 22 | Dir: directory, 23 | Prefix: "", 24 | IndexFile: "index.html", 25 | } 26 | } 27 | 28 | func (s *Static) ServeHTTP(rw http.ResponseWriter, r *http.Request, next http.HandlerFunc) { 29 | if r.Method != "GET" && r.Method != "HEAD" { 30 | next(rw, r) 31 | return 32 | } 33 | file := r.URL.Path 34 | // if we have a prefix, filter requests by stripping the prefix 35 | if s.Prefix != "" { 36 | if !strings.HasPrefix(file, s.Prefix) { 37 | next(rw, r) 38 | return 39 | } 40 | file = file[len(s.Prefix):] 41 | if file != "" && file[0] != '/' { 42 | next(rw, r) 43 | return 44 | } 45 | } 46 | f, err := s.Dir.Open(file) 47 | if err != nil { 48 | // discard the error? 49 | next(rw, r) 50 | return 51 | } 52 | defer f.Close() 53 | 54 | fi, err := f.Stat() 55 | if err != nil { 56 | next(rw, r) 57 | return 58 | } 59 | 60 | // try to serve index file 61 | if fi.IsDir() { 62 | // redirect if missing trailing slash 63 | if !strings.HasSuffix(r.URL.Path, "/") { 64 | http.Redirect(rw, r, r.URL.Path+"/", http.StatusFound) 65 | return 66 | } 67 | 68 | file = path.Join(file, s.IndexFile) 69 | f, err = s.Dir.Open(file) 70 | if err != nil { 71 | next(rw, r) 72 | return 73 | } 74 | defer f.Close() 75 | 76 | fi, err = f.Stat() 77 | if err != nil || fi.IsDir() { 78 | next(rw, r) 79 | return 80 | } 81 | } 82 | 83 | http.ServeContent(rw, r, file, fi.ModTime(), f) 84 | } 85 | -------------------------------------------------------------------------------- /Godeps/_workspace/src/github.com/codegangsta/negroni/static_test.go: -------------------------------------------------------------------------------- 1 | package negroni 2 | 3 | import ( 4 | "bytes" 5 | "net/http" 6 | "net/http/httptest" 7 | "testing" 8 | ) 9 | 10 | func TestStatic(t *testing.T) { 11 | response := httptest.NewRecorder() 12 | response.Body = new(bytes.Buffer) 13 | 14 | n := New() 15 | n.Use(NewStatic(http.Dir("."))) 16 | 17 | req, err := http.NewRequest("GET", "http://localhost:3000/negroni.go", nil) 18 | if err != nil { 19 | t.Error(err) 20 | } 21 | n.ServeHTTP(response, req) 22 | expect(t, response.Code, http.StatusOK) 23 | expect(t, response.Header().Get("Expires"), "") 24 | if response.Body.Len() == 0 { 25 | t.Errorf("Got empty body for GET request") 26 | } 27 | } 28 | 29 | func TestStaticHead(t *testing.T) { 30 | response := httptest.NewRecorder() 31 | response.Body = new(bytes.Buffer) 32 | 33 | n := New() 34 | n.Use(NewStatic(http.Dir("."))) 35 | n.UseHandler(http.NotFoundHandler()) 36 | 37 | req, err := http.NewRequest("HEAD", "http://localhost:3000/negroni.go", nil) 38 | if err != nil { 39 | t.Error(err) 40 | } 41 | 42 | n.ServeHTTP(response, req) 43 | expect(t, response.Code, http.StatusOK) 44 | if response.Body.Len() != 0 { 45 | t.Errorf("Got non-empty body for HEAD request") 46 | } 47 | } 48 | 49 | func TestStaticAsPost(t *testing.T) { 50 | response := httptest.NewRecorder() 51 | 52 | n := New() 53 | n.Use(NewStatic(http.Dir("."))) 54 | n.UseHandler(http.NotFoundHandler()) 55 | 56 | req, err := http.NewRequest("POST", "http://localhost:3000/negroni.go", nil) 57 | if err != nil { 58 | t.Error(err) 59 | } 60 | 61 | n.ServeHTTP(response, req) 62 | expect(t, response.Code, http.StatusNotFound) 63 | } 64 | 65 | func TestStaticBadDir(t *testing.T) { 66 | response := httptest.NewRecorder() 67 | 68 | n := Classic() 69 | n.UseHandler(http.NotFoundHandler()) 70 | 71 | req, err := http.NewRequest("GET", "http://localhost:3000/negroni.go", nil) 72 | if err != nil { 73 | t.Error(err) 74 | } 75 | 76 | n.ServeHTTP(response, req) 77 | refute(t, response.Code, http.StatusOK) 78 | } 79 | 80 | func TestStaticOptionsServeIndex(t *testing.T) { 81 | response := httptest.NewRecorder() 82 | 83 | n := New() 84 | s := NewStatic(http.Dir(".")) 85 | s.IndexFile = "negroni.go" 86 | n.Use(s) 87 | 88 | req, err := http.NewRequest("GET", "http://localhost:3000/", nil) 89 | if err != nil { 90 | t.Error(err) 91 | } 92 | 93 | n.ServeHTTP(response, req) 94 | expect(t, response.Code, http.StatusOK) 95 | } 96 | 97 | func TestStaticOptionsPrefix(t *testing.T) { 98 | response := httptest.NewRecorder() 99 | 100 | n := New() 101 | s := NewStatic(http.Dir(".")) 102 | s.Prefix = "/public" 103 | n.Use(s) 104 | 105 | // Check file content behaviour 106 | req, err := http.NewRequest("GET", "http://localhost:3000/public/negroni.go", nil) 107 | if err != nil { 108 | t.Error(err) 109 | } 110 | 111 | n.ServeHTTP(response, req) 112 | expect(t, response.Code, http.StatusOK) 113 | } 114 | -------------------------------------------------------------------------------- /Godeps/_workspace/src/github.com/gorilla/context/.travis.yml: -------------------------------------------------------------------------------- 1 | language: go 2 | 3 | go: 4 | - 1.0 5 | - 1.1 6 | - 1.2 7 | - tip 8 | -------------------------------------------------------------------------------- /Godeps/_workspace/src/github.com/gorilla/context/LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2012 Rodrigo Moraes. All rights reserved. 2 | 3 | Redistribution and use in source and binary forms, with or without 4 | modification, are permitted provided that the following conditions are 5 | met: 6 | 7 | * Redistributions of source code must retain the above copyright 8 | notice, this list of conditions and the following disclaimer. 9 | * Redistributions in binary form must reproduce the above 10 | copyright notice, this list of conditions and the following disclaimer 11 | in the documentation and/or other materials provided with the 12 | distribution. 13 | * Neither the name of Google Inc. nor the names of its 14 | contributors may be used to endorse or promote products derived from 15 | this software without specific prior written permission. 16 | 17 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 18 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 19 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 20 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 21 | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 22 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 23 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 24 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 25 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 26 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 27 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | -------------------------------------------------------------------------------- /Godeps/_workspace/src/github.com/gorilla/context/README.md: -------------------------------------------------------------------------------- 1 | context 2 | ======= 3 | [![Build Status](https://travis-ci.org/gorilla/context.png?branch=master)](https://travis-ci.org/gorilla/context) 4 | 5 | gorilla/context is a general purpose registry for global request variables. 6 | 7 | Read the full documentation here: http://www.gorillatoolkit.org/pkg/context 8 | -------------------------------------------------------------------------------- /Godeps/_workspace/src/github.com/gorilla/context/context.go: -------------------------------------------------------------------------------- 1 | // Copyright 2012 The Gorilla Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package context 6 | 7 | import ( 8 | "net/http" 9 | "sync" 10 | "time" 11 | ) 12 | 13 | var ( 14 | mutex sync.RWMutex 15 | data = make(map[*http.Request]map[interface{}]interface{}) 16 | datat = make(map[*http.Request]int64) 17 | ) 18 | 19 | // Set stores a value for a given key in a given request. 20 | func Set(r *http.Request, key, val interface{}) { 21 | mutex.Lock() 22 | if data[r] == nil { 23 | data[r] = make(map[interface{}]interface{}) 24 | datat[r] = time.Now().Unix() 25 | } 26 | data[r][key] = val 27 | mutex.Unlock() 28 | } 29 | 30 | // Get returns a value stored for a given key in a given request. 31 | func Get(r *http.Request, key interface{}) interface{} { 32 | mutex.RLock() 33 | if ctx := data[r]; ctx != nil { 34 | value := ctx[key] 35 | mutex.RUnlock() 36 | return value 37 | } 38 | mutex.RUnlock() 39 | return nil 40 | } 41 | 42 | // GetOk returns stored value and presence state like multi-value return of map access. 43 | func GetOk(r *http.Request, key interface{}) (interface{}, bool) { 44 | mutex.RLock() 45 | if _, ok := data[r]; ok { 46 | value, ok := data[r][key] 47 | mutex.RUnlock() 48 | return value, ok 49 | } 50 | mutex.RUnlock() 51 | return nil, false 52 | } 53 | 54 | // GetAll returns all stored values for the request as a map. Nil is returned for invalid requests. 55 | func GetAll(r *http.Request) map[interface{}]interface{} { 56 | mutex.RLock() 57 | if context, ok := data[r]; ok { 58 | result := make(map[interface{}]interface{}, len(context)) 59 | for k, v := range context { 60 | result[k] = v 61 | } 62 | mutex.RUnlock() 63 | return result 64 | } 65 | mutex.RUnlock() 66 | return nil 67 | } 68 | 69 | // GetAllOk returns all stored values for the request as a map and a boolean value that indicates if 70 | // the request was registered. 71 | func GetAllOk(r *http.Request) (map[interface{}]interface{}, bool) { 72 | mutex.RLock() 73 | context, ok := data[r] 74 | result := make(map[interface{}]interface{}, len(context)) 75 | for k, v := range context { 76 | result[k] = v 77 | } 78 | mutex.RUnlock() 79 | return result, ok 80 | } 81 | 82 | // Delete removes a value stored for a given key in a given request. 83 | func Delete(r *http.Request, key interface{}) { 84 | mutex.Lock() 85 | if data[r] != nil { 86 | delete(data[r], key) 87 | } 88 | mutex.Unlock() 89 | } 90 | 91 | // Clear removes all values stored for a given request. 92 | // 93 | // This is usually called by a handler wrapper to clean up request 94 | // variables at the end of a request lifetime. See ClearHandler(). 95 | func Clear(r *http.Request) { 96 | mutex.Lock() 97 | clear(r) 98 | mutex.Unlock() 99 | } 100 | 101 | // clear is Clear without the lock. 102 | func clear(r *http.Request) { 103 | delete(data, r) 104 | delete(datat, r) 105 | } 106 | 107 | // Purge removes request data stored for longer than maxAge, in seconds. 108 | // It returns the amount of requests removed. 109 | // 110 | // If maxAge <= 0, all request data is removed. 111 | // 112 | // This is only used for sanity check: in case context cleaning was not 113 | // properly set some request data can be kept forever, consuming an increasing 114 | // amount of memory. In case this is detected, Purge() must be called 115 | // periodically until the problem is fixed. 116 | func Purge(maxAge int) int { 117 | mutex.Lock() 118 | count := 0 119 | if maxAge <= 0 { 120 | count = len(data) 121 | data = make(map[*http.Request]map[interface{}]interface{}) 122 | datat = make(map[*http.Request]int64) 123 | } else { 124 | min := time.Now().Unix() - int64(maxAge) 125 | for r := range data { 126 | if datat[r] < min { 127 | clear(r) 128 | count++ 129 | } 130 | } 131 | } 132 | mutex.Unlock() 133 | return count 134 | } 135 | 136 | // ClearHandler wraps an http.Handler and clears request values at the end 137 | // of a request lifetime. 138 | func ClearHandler(h http.Handler) http.Handler { 139 | return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 140 | defer Clear(r) 141 | h.ServeHTTP(w, r) 142 | }) 143 | } 144 | -------------------------------------------------------------------------------- /Godeps/_workspace/src/github.com/gorilla/context/context_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2012 The Gorilla Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package context 6 | 7 | import ( 8 | "net/http" 9 | "testing" 10 | ) 11 | 12 | type keyType int 13 | 14 | const ( 15 | key1 keyType = iota 16 | key2 17 | ) 18 | 19 | func TestContext(t *testing.T) { 20 | assertEqual := func(val interface{}, exp interface{}) { 21 | if val != exp { 22 | t.Errorf("Expected %v, got %v.", exp, val) 23 | } 24 | } 25 | 26 | r, _ := http.NewRequest("GET", "http://localhost:8080/", nil) 27 | emptyR, _ := http.NewRequest("GET", "http://localhost:8080/", nil) 28 | 29 | // Get() 30 | assertEqual(Get(r, key1), nil) 31 | 32 | // Set() 33 | Set(r, key1, "1") 34 | assertEqual(Get(r, key1), "1") 35 | assertEqual(len(data[r]), 1) 36 | 37 | Set(r, key2, "2") 38 | assertEqual(Get(r, key2), "2") 39 | assertEqual(len(data[r]), 2) 40 | 41 | //GetOk 42 | value, ok := GetOk(r, key1) 43 | assertEqual(value, "1") 44 | assertEqual(ok, true) 45 | 46 | value, ok = GetOk(r, "not exists") 47 | assertEqual(value, nil) 48 | assertEqual(ok, false) 49 | 50 | Set(r, "nil value", nil) 51 | value, ok = GetOk(r, "nil value") 52 | assertEqual(value, nil) 53 | assertEqual(ok, true) 54 | 55 | // GetAll() 56 | values := GetAll(r) 57 | assertEqual(len(values), 3) 58 | 59 | // GetAll() for empty request 60 | values = GetAll(emptyR) 61 | if values != nil { 62 | t.Error("GetAll didn't return nil value for invalid request") 63 | } 64 | 65 | // GetAllOk() 66 | values, ok = GetAllOk(r) 67 | assertEqual(len(values), 3) 68 | assertEqual(ok, true) 69 | 70 | // GetAllOk() for empty request 71 | values, ok = GetAllOk(emptyR) 72 | assertEqual(value, nil) 73 | assertEqual(ok, false) 74 | 75 | // Delete() 76 | Delete(r, key1) 77 | assertEqual(Get(r, key1), nil) 78 | assertEqual(len(data[r]), 2) 79 | 80 | Delete(r, key2) 81 | assertEqual(Get(r, key2), nil) 82 | assertEqual(len(data[r]), 1) 83 | 84 | // Clear() 85 | Clear(r) 86 | assertEqual(len(data), 0) 87 | } 88 | 89 | func parallelReader(r *http.Request, key string, iterations int, wait, done chan struct{}) { 90 | <-wait 91 | for i := 0; i < iterations; i++ { 92 | Get(r, key) 93 | } 94 | done <- struct{}{} 95 | 96 | } 97 | 98 | func parallelWriter(r *http.Request, key, value string, iterations int, wait, done chan struct{}) { 99 | <-wait 100 | for i := 0; i < iterations; i++ { 101 | Set(r, key, value) 102 | } 103 | done <- struct{}{} 104 | 105 | } 106 | 107 | func benchmarkMutex(b *testing.B, numReaders, numWriters, iterations int) { 108 | 109 | b.StopTimer() 110 | r, _ := http.NewRequest("GET", "http://localhost:8080/", nil) 111 | done := make(chan struct{}) 112 | b.StartTimer() 113 | 114 | for i := 0; i < b.N; i++ { 115 | wait := make(chan struct{}) 116 | 117 | for i := 0; i < numReaders; i++ { 118 | go parallelReader(r, "test", iterations, wait, done) 119 | } 120 | 121 | for i := 0; i < numWriters; i++ { 122 | go parallelWriter(r, "test", "123", iterations, wait, done) 123 | } 124 | 125 | close(wait) 126 | 127 | for i := 0; i < numReaders+numWriters; i++ { 128 | <-done 129 | } 130 | 131 | } 132 | 133 | } 134 | 135 | func BenchmarkMutexSameReadWrite1(b *testing.B) { 136 | benchmarkMutex(b, 1, 1, 32) 137 | } 138 | func BenchmarkMutexSameReadWrite2(b *testing.B) { 139 | benchmarkMutex(b, 2, 2, 32) 140 | } 141 | func BenchmarkMutexSameReadWrite4(b *testing.B) { 142 | benchmarkMutex(b, 4, 4, 32) 143 | } 144 | func BenchmarkMutex1(b *testing.B) { 145 | benchmarkMutex(b, 2, 8, 32) 146 | } 147 | func BenchmarkMutex2(b *testing.B) { 148 | benchmarkMutex(b, 16, 4, 64) 149 | } 150 | func BenchmarkMutex3(b *testing.B) { 151 | benchmarkMutex(b, 1, 2, 128) 152 | } 153 | func BenchmarkMutex4(b *testing.B) { 154 | benchmarkMutex(b, 128, 32, 256) 155 | } 156 | func BenchmarkMutex5(b *testing.B) { 157 | benchmarkMutex(b, 1024, 2048, 64) 158 | } 159 | func BenchmarkMutex6(b *testing.B) { 160 | benchmarkMutex(b, 2048, 1024, 512) 161 | } 162 | -------------------------------------------------------------------------------- /Godeps/_workspace/src/github.com/gorilla/context/doc.go: -------------------------------------------------------------------------------- 1 | // Copyright 2012 The Gorilla Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | /* 6 | Package context stores values shared during a request lifetime. 7 | 8 | For example, a router can set variables extracted from the URL and later 9 | application handlers can access those values, or it can be used to store 10 | sessions values to be saved at the end of a request. There are several 11 | others common uses. 12 | 13 | The idea was posted by Brad Fitzpatrick to the go-nuts mailing list: 14 | 15 | http://groups.google.com/group/golang-nuts/msg/e2d679d303aa5d53 16 | 17 | Here's the basic usage: first define the keys that you will need. The key 18 | type is interface{} so a key can be of any type that supports equality. 19 | Here we define a key using a custom int type to avoid name collisions: 20 | 21 | package foo 22 | 23 | import ( 24 | "github.com/gorilla/context" 25 | ) 26 | 27 | type key int 28 | 29 | const MyKey key = 0 30 | 31 | Then set a variable. Variables are bound to an http.Request object, so you 32 | need a request instance to set a value: 33 | 34 | context.Set(r, MyKey, "bar") 35 | 36 | The application can later access the variable using the same key you provided: 37 | 38 | func MyHandler(w http.ResponseWriter, r *http.Request) { 39 | // val is "bar". 40 | val := context.Get(r, foo.MyKey) 41 | 42 | // returns ("bar", true) 43 | val, ok := context.GetOk(r, foo.MyKey) 44 | // ... 45 | } 46 | 47 | And that's all about the basic usage. We discuss some other ideas below. 48 | 49 | Any type can be stored in the context. To enforce a given type, make the key 50 | private and wrap Get() and Set() to accept and return values of a specific 51 | type: 52 | 53 | type key int 54 | 55 | const mykey key = 0 56 | 57 | // GetMyKey returns a value for this package from the request values. 58 | func GetMyKey(r *http.Request) SomeType { 59 | if rv := context.Get(r, mykey); rv != nil { 60 | return rv.(SomeType) 61 | } 62 | return nil 63 | } 64 | 65 | // SetMyKey sets a value for this package in the request values. 66 | func SetMyKey(r *http.Request, val SomeType) { 67 | context.Set(r, mykey, val) 68 | } 69 | 70 | Variables must be cleared at the end of a request, to remove all values 71 | that were stored. This can be done in an http.Handler, after a request was 72 | served. Just call Clear() passing the request: 73 | 74 | context.Clear(r) 75 | 76 | ...or use ClearHandler(), which conveniently wraps an http.Handler to clear 77 | variables at the end of a request lifetime. 78 | 79 | The Routers from the packages gorilla/mux and gorilla/pat call Clear() 80 | so if you are using either of them you don't need to clear the context manually. 81 | */ 82 | package context 83 | -------------------------------------------------------------------------------- /Godeps/_workspace/src/github.com/gorilla/mux/.travis.yml: -------------------------------------------------------------------------------- 1 | language: go 2 | 3 | go: 4 | - 1.0 5 | - 1.1 6 | - 1.2 7 | - tip 8 | -------------------------------------------------------------------------------- /Godeps/_workspace/src/github.com/gorilla/mux/LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2012 Rodrigo Moraes. All rights reserved. 2 | 3 | Redistribution and use in source and binary forms, with or without 4 | modification, are permitted provided that the following conditions are 5 | met: 6 | 7 | * Redistributions of source code must retain the above copyright 8 | notice, this list of conditions and the following disclaimer. 9 | * Redistributions in binary form must reproduce the above 10 | copyright notice, this list of conditions and the following disclaimer 11 | in the documentation and/or other materials provided with the 12 | distribution. 13 | * Neither the name of Google Inc. nor the names of its 14 | contributors may be used to endorse or promote products derived from 15 | this software without specific prior written permission. 16 | 17 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 18 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 19 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 20 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 21 | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 22 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 23 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 24 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 25 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 26 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 27 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | -------------------------------------------------------------------------------- /Godeps/_workspace/src/github.com/gorilla/mux/README.md: -------------------------------------------------------------------------------- 1 | mux 2 | === 3 | [![Build Status](https://travis-ci.org/gorilla/mux.png?branch=master)](https://travis-ci.org/gorilla/mux) 4 | 5 | gorilla/mux is a powerful URL router and dispatcher. 6 | 7 | Read the full documentation here: http://www.gorillatoolkit.org/pkg/mux 8 | -------------------------------------------------------------------------------- /Godeps/_workspace/src/github.com/gorilla/mux/bench_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2012 The Gorilla Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package mux 6 | 7 | import ( 8 | "net/http" 9 | "testing" 10 | ) 11 | 12 | func BenchmarkMux(b *testing.B) { 13 | router := new(Router) 14 | handler := func(w http.ResponseWriter, r *http.Request) {} 15 | router.HandleFunc("/v1/{v1}", handler) 16 | 17 | request, _ := http.NewRequest("GET", "/v1/anything", nil) 18 | for i := 0; i < b.N; i++ { 19 | router.ServeHTTP(nil, request) 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /Godeps/_workspace/src/github.com/gorilla/mux/doc.go: -------------------------------------------------------------------------------- 1 | // Copyright 2012 The Gorilla Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | /* 6 | Package gorilla/mux implements a request router and dispatcher. 7 | 8 | The name mux stands for "HTTP request multiplexer". Like the standard 9 | http.ServeMux, mux.Router matches incoming requests against a list of 10 | registered routes and calls a handler for the route that matches the URL 11 | or other conditions. The main features are: 12 | 13 | * Requests can be matched based on URL host, path, path prefix, schemes, 14 | header and query values, HTTP methods or using custom matchers. 15 | * URL hosts and paths can have variables with an optional regular 16 | expression. 17 | * Registered URLs can be built, or "reversed", which helps maintaining 18 | references to resources. 19 | * Routes can be used as subrouters: nested routes are only tested if the 20 | parent route matches. This is useful to define groups of routes that 21 | share common conditions like a host, a path prefix or other repeated 22 | attributes. As a bonus, this optimizes request matching. 23 | * It implements the http.Handler interface so it is compatible with the 24 | standard http.ServeMux. 25 | 26 | Let's start registering a couple of URL paths and handlers: 27 | 28 | func main() { 29 | r := mux.NewRouter() 30 | r.HandleFunc("/", HomeHandler) 31 | r.HandleFunc("/products", ProductsHandler) 32 | r.HandleFunc("/articles", ArticlesHandler) 33 | http.Handle("/", r) 34 | } 35 | 36 | Here we register three routes mapping URL paths to handlers. This is 37 | equivalent to how http.HandleFunc() works: if an incoming request URL matches 38 | one of the paths, the corresponding handler is called passing 39 | (http.ResponseWriter, *http.Request) as parameters. 40 | 41 | Paths can have variables. They are defined using the format {name} or 42 | {name:pattern}. If a regular expression pattern is not defined, the matched 43 | variable will be anything until the next slash. For example: 44 | 45 | r := mux.NewRouter() 46 | r.HandleFunc("/products/{key}", ProductHandler) 47 | r.HandleFunc("/articles/{category}/", ArticlesCategoryHandler) 48 | r.HandleFunc("/articles/{category}/{id:[0-9]+}", ArticleHandler) 49 | 50 | The names are used to create a map of route variables which can be retrieved 51 | calling mux.Vars(): 52 | 53 | vars := mux.Vars(request) 54 | category := vars["category"] 55 | 56 | And this is all you need to know about the basic usage. More advanced options 57 | are explained below. 58 | 59 | Routes can also be restricted to a domain or subdomain. Just define a host 60 | pattern to be matched. They can also have variables: 61 | 62 | r := mux.NewRouter() 63 | // Only matches if domain is "www.domain.com". 64 | r.Host("www.domain.com") 65 | // Matches a dynamic subdomain. 66 | r.Host("{subdomain:[a-z]+}.domain.com") 67 | 68 | There are several other matchers that can be added. To match path prefixes: 69 | 70 | r.PathPrefix("/products/") 71 | 72 | ...or HTTP methods: 73 | 74 | r.Methods("GET", "POST") 75 | 76 | ...or URL schemes: 77 | 78 | r.Schemes("https") 79 | 80 | ...or header values: 81 | 82 | r.Headers("X-Requested-With", "XMLHttpRequest") 83 | 84 | ...or query values: 85 | 86 | r.Queries("key", "value") 87 | 88 | ...or to use a custom matcher function: 89 | 90 | r.MatcherFunc(func(r *http.Request, rm *RouteMatch) bool { 91 | return r.ProtoMajor == 0 92 | }) 93 | 94 | ...and finally, it is possible to combine several matchers in a single route: 95 | 96 | r.HandleFunc("/products", ProductsHandler). 97 | Host("www.domain.com"). 98 | Methods("GET"). 99 | Schemes("http") 100 | 101 | Setting the same matching conditions again and again can be boring, so we have 102 | a way to group several routes that share the same requirements. 103 | We call it "subrouting". 104 | 105 | For example, let's say we have several URLs that should only match when the 106 | host is "www.domain.com". Create a route for that host and get a "subrouter" 107 | from it: 108 | 109 | r := mux.NewRouter() 110 | s := r.Host("www.domain.com").Subrouter() 111 | 112 | Then register routes in the subrouter: 113 | 114 | s.HandleFunc("/products/", ProductsHandler) 115 | s.HandleFunc("/products/{key}", ProductHandler) 116 | s.HandleFunc("/articles/{category}/{id:[0-9]+}"), ArticleHandler) 117 | 118 | The three URL paths we registered above will only be tested if the domain is 119 | "www.domain.com", because the subrouter is tested first. This is not 120 | only convenient, but also optimizes request matching. You can create 121 | subrouters combining any attribute matchers accepted by a route. 122 | 123 | Subrouters can be used to create domain or path "namespaces": you define 124 | subrouters in a central place and then parts of the app can register its 125 | paths relatively to a given subrouter. 126 | 127 | There's one more thing about subroutes. When a subrouter has a path prefix, 128 | the inner routes use it as base for their paths: 129 | 130 | r := mux.NewRouter() 131 | s := r.PathPrefix("/products").Subrouter() 132 | // "/products/" 133 | s.HandleFunc("/", ProductsHandler) 134 | // "/products/{key}/" 135 | s.HandleFunc("/{key}/", ProductHandler) 136 | // "/products/{key}/details" 137 | s.HandleFunc("/{key}/details", ProductDetailsHandler) 138 | 139 | Now let's see how to build registered URLs. 140 | 141 | Routes can be named. All routes that define a name can have their URLs built, 142 | or "reversed". We define a name calling Name() on a route. For example: 143 | 144 | r := mux.NewRouter() 145 | r.HandleFunc("/articles/{category}/{id:[0-9]+}", ArticleHandler). 146 | Name("article") 147 | 148 | To build a URL, get the route and call the URL() method, passing a sequence of 149 | key/value pairs for the route variables. For the previous route, we would do: 150 | 151 | url, err := r.Get("article").URL("category", "technology", "id", "42") 152 | 153 | ...and the result will be a url.URL with the following path: 154 | 155 | "/articles/technology/42" 156 | 157 | This also works for host variables: 158 | 159 | r := mux.NewRouter() 160 | r.Host("{subdomain}.domain.com"). 161 | Path("/articles/{category}/{id:[0-9]+}"). 162 | HandlerFunc(ArticleHandler). 163 | Name("article") 164 | 165 | // url.String() will be "http://news.domain.com/articles/technology/42" 166 | url, err := r.Get("article").URL("subdomain", "news", 167 | "category", "technology", 168 | "id", "42") 169 | 170 | All variables defined in the route are required, and their values must 171 | conform to the corresponding patterns. These requirements guarantee that a 172 | generated URL will always match a registered route -- the only exception is 173 | for explicitly defined "build-only" routes which never match. 174 | 175 | There's also a way to build only the URL host or path for a route: 176 | use the methods URLHost() or URLPath() instead. For the previous route, 177 | we would do: 178 | 179 | // "http://news.domain.com/" 180 | host, err := r.Get("article").URLHost("subdomain", "news") 181 | 182 | // "/articles/technology/42" 183 | path, err := r.Get("article").URLPath("category", "technology", "id", "42") 184 | 185 | And if you use subrouters, host and path defined separately can be built 186 | as well: 187 | 188 | r := mux.NewRouter() 189 | s := r.Host("{subdomain}.domain.com").Subrouter() 190 | s.Path("/articles/{category}/{id:[0-9]+}"). 191 | HandlerFunc(ArticleHandler). 192 | Name("article") 193 | 194 | // "http://news.domain.com/articles/technology/42" 195 | url, err := r.Get("article").URL("subdomain", "news", 196 | "category", "technology", 197 | "id", "42") 198 | */ 199 | package mux 200 | -------------------------------------------------------------------------------- /Godeps/_workspace/src/github.com/gorilla/websocket/.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled Object files, Static and Dynamic libs (Shared Objects) 2 | *.o 3 | *.a 4 | *.so 5 | 6 | # Folders 7 | _obj 8 | _test 9 | 10 | # Architecture specific extensions/prefixes 11 | *.[568vq] 12 | [568vq].out 13 | 14 | *.cgo1.go 15 | *.cgo2.c 16 | _cgo_defun.c 17 | _cgo_gotypes.go 18 | _cgo_export.* 19 | 20 | _testmain.go 21 | 22 | *.exe 23 | -------------------------------------------------------------------------------- /Godeps/_workspace/src/github.com/gorilla/websocket/.travis.yml: -------------------------------------------------------------------------------- 1 | language: go 2 | 3 | go: 4 | - 1.1 5 | - 1.2 6 | - tip 7 | -------------------------------------------------------------------------------- /Godeps/_workspace/src/github.com/gorilla/websocket/AUTHORS: -------------------------------------------------------------------------------- 1 | # This is the official list of Gorilla WebSocket authors for copyright 2 | # purposes. 3 | # 4 | # Please keep the list sorted. 5 | 6 | Gary Burd 7 | Joachim Bauch 8 | 9 | -------------------------------------------------------------------------------- /Godeps/_workspace/src/github.com/gorilla/websocket/LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2013 The Gorilla WebSocket Authors. All rights reserved. 2 | 3 | Redistribution and use in source and binary forms, with or without 4 | modification, are permitted provided that the following conditions are met: 5 | 6 | Redistributions of source code must retain the above copyright notice, this 7 | list of conditions and the following disclaimer. 8 | 9 | Redistributions in binary form must reproduce the above copyright notice, 10 | this list of conditions and the following disclaimer in the documentation 11 | and/or other materials provided with the distribution. 12 | 13 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 14 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 15 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 16 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 17 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 18 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 19 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 20 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 21 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 22 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 23 | -------------------------------------------------------------------------------- /Godeps/_workspace/src/github.com/gorilla/websocket/README.md: -------------------------------------------------------------------------------- 1 | # Gorilla WebSocket 2 | 3 | Gorilla WebSocket is a [Go](http://golang.org/) implementation of the 4 | [WebSocket](http://www.rfc-editor.org/rfc/rfc6455.txt) protocol. 5 | 6 | ### Documentation 7 | 8 | * [API Reference](http://godoc.org/github.com/gorilla/websocket) 9 | * [Chat example](https://github.com/gorilla/websocket/tree/master/examples/chat) 10 | * [File watch example](https://github.com/gorilla/websocket/tree/master/examples/filewatch) 11 | 12 | ### Status 13 | 14 | The Gorilla WebSocket package provides a complete and tested implementation of 15 | the [WebSocket](http://www.rfc-editor.org/rfc/rfc6455.txt) protocol. The 16 | package API is stable. 17 | 18 | ### Installation 19 | 20 | go get github.com/gorilla/websocket 21 | 22 | ### Protocol Compliance 23 | 24 | The Gorilla WebSocket package passes the server tests in the [Autobahn Test 25 | Suite](http://autobahn.ws/testsuite) using the application in the [examples/autobahn 26 | subdirectory](https://github.com/gorilla/websocket/tree/master/examples/autobahn). 27 | 28 | ### Gorilla WebSocket compared with other packages 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 |
github.com/gorillagolang.org/x/net
RFC 6455 Features
Passes Autobahn Test SuiteYesNo
Receive fragmented messageYesNo, see note 1
Send close messageYesNo
Send pings and receive pongsYesNo
Get the type of a received data messageYesYes, see note 2
Other Features
Limit size of received messageYesNo
Read message using io.ReaderYesNo, see note 3
Write message using io.WriteCloserYesNo, see note 3
48 | 49 | Notes: 50 | 51 | 1. Large messages are fragmented in [Chrome's new WebSocket implementation](http://www.ietf.org/mail-archive/web/hybi/current/msg10503.html). 52 | 2. The application can get the type of a received data message by implementing 53 | a [Codec marshal](http://godoc.org/golang.org/x/net/websocket#Codec.Marshal) 54 | function. 55 | 3. The go.net io.Reader and io.Writer operate across WebSocket frame boundaries. 56 | Read returns when the input buffer is full or a frame boundary is 57 | encountered. Each call to Write sends a single frame message. The Gorilla 58 | io.Reader and io.WriteCloser operate on a single WebSocket message. 59 | 60 | -------------------------------------------------------------------------------- /Godeps/_workspace/src/github.com/gorilla/websocket/bench_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2014 The Gorilla WebSocket Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package websocket 6 | 7 | import ( 8 | "testing" 9 | ) 10 | 11 | func BenchmarkMaskBytes(b *testing.B) { 12 | var key [4]byte 13 | data := make([]byte, 1024) 14 | pos := 0 15 | for i := 0; i < b.N; i++ { 16 | pos = maskBytes(key, pos, data) 17 | } 18 | b.SetBytes(int64(len(data))) 19 | } 20 | -------------------------------------------------------------------------------- /Godeps/_workspace/src/github.com/gorilla/websocket/client.go: -------------------------------------------------------------------------------- 1 | // Copyright 2013 The Gorilla WebSocket Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package websocket 6 | 7 | import ( 8 | "crypto/tls" 9 | "errors" 10 | "net" 11 | "net/http" 12 | "net/url" 13 | "strings" 14 | "time" 15 | ) 16 | 17 | // ErrBadHandshake is returned when the server response to opening handshake is 18 | // invalid. 19 | var ErrBadHandshake = errors.New("websocket: bad handshake") 20 | 21 | // NewClient creates a new client connection using the given net connection. 22 | // The URL u specifies the host and request URI. Use requestHeader to specify 23 | // the origin (Origin), subprotocols (Sec-WebSocket-Protocol) and cookies 24 | // (Cookie). Use the response.Header to get the selected subprotocol 25 | // (Sec-WebSocket-Protocol) and cookies (Set-Cookie). 26 | // 27 | // If the WebSocket handshake fails, ErrBadHandshake is returned along with a 28 | // non-nil *http.Response so that callers can handle redirects, authentication, 29 | // etc. 30 | func NewClient(netConn net.Conn, u *url.URL, requestHeader http.Header, readBufSize, writeBufSize int) (c *Conn, response *http.Response, err error) { 31 | challengeKey, err := generateChallengeKey() 32 | if err != nil { 33 | return nil, nil, err 34 | } 35 | acceptKey := computeAcceptKey(challengeKey) 36 | 37 | c = newConn(netConn, false, readBufSize, writeBufSize) 38 | p := c.writeBuf[:0] 39 | p = append(p, "GET "...) 40 | p = append(p, u.RequestURI()...) 41 | p = append(p, " HTTP/1.1\r\nHost: "...) 42 | p = append(p, u.Host...) 43 | // "Upgrade" is capitalized for servers that do not use case insensitive 44 | // comparisons on header tokens. 45 | p = append(p, "\r\nUpgrade: websocket\r\nConnection: Upgrade\r\nSec-WebSocket-Version: 13\r\nSec-WebSocket-Key: "...) 46 | p = append(p, challengeKey...) 47 | p = append(p, "\r\n"...) 48 | for k, vs := range requestHeader { 49 | for _, v := range vs { 50 | p = append(p, k...) 51 | p = append(p, ": "...) 52 | p = append(p, v...) 53 | p = append(p, "\r\n"...) 54 | } 55 | } 56 | p = append(p, "\r\n"...) 57 | 58 | if _, err := netConn.Write(p); err != nil { 59 | return nil, nil, err 60 | } 61 | 62 | resp, err := http.ReadResponse(c.br, &http.Request{Method: "GET", URL: u}) 63 | if err != nil { 64 | return nil, nil, err 65 | } 66 | if resp.StatusCode != 101 || 67 | !strings.EqualFold(resp.Header.Get("Upgrade"), "websocket") || 68 | !strings.EqualFold(resp.Header.Get("Connection"), "upgrade") || 69 | resp.Header.Get("Sec-Websocket-Accept") != acceptKey { 70 | return nil, resp, ErrBadHandshake 71 | } 72 | c.subprotocol = resp.Header.Get("Sec-Websocket-Protocol") 73 | return c, resp, nil 74 | } 75 | 76 | // A Dialer contains options for connecting to WebSocket server. 77 | type Dialer struct { 78 | // NetDial specifies the dial function for creating TCP connections. If 79 | // NetDial is nil, net.Dial is used. 80 | NetDial func(network, addr string) (net.Conn, error) 81 | 82 | // TLSClientConfig specifies the TLS configuration to use with tls.Client. 83 | // If nil, the default configuration is used. 84 | TLSClientConfig *tls.Config 85 | 86 | // HandshakeTimeout specifies the duration for the handshake to complete. 87 | HandshakeTimeout time.Duration 88 | 89 | // Input and output buffer sizes. If the buffer size is zero, then a 90 | // default value of 4096 is used. 91 | ReadBufferSize, WriteBufferSize int 92 | 93 | // Subprotocols specifies the client's requested subprotocols. 94 | Subprotocols []string 95 | } 96 | 97 | var errMalformedURL = errors.New("malformed ws or wss URL") 98 | 99 | // parseURL parses the URL. The url.Parse function is not used here because 100 | // url.Parse mangles the path. 101 | func parseURL(s string) (*url.URL, error) { 102 | // From the RFC: 103 | // 104 | // ws-URI = "ws:" "//" host [ ":" port ] path [ "?" query ] 105 | // wss-URI = "wss:" "//" host [ ":" port ] path [ "?" query ] 106 | // 107 | // We don't use the net/url parser here because the dialer interface does 108 | // not provide a way for applications to work around percent deocding in 109 | // the net/url parser. 110 | 111 | var u url.URL 112 | switch { 113 | case strings.HasPrefix(s, "ws://"): 114 | u.Scheme = "ws" 115 | s = s[len("ws://"):] 116 | case strings.HasPrefix(s, "wss://"): 117 | u.Scheme = "wss" 118 | s = s[len("wss://"):] 119 | default: 120 | return nil, errMalformedURL 121 | } 122 | 123 | u.Host = s 124 | u.Opaque = "/" 125 | if i := strings.Index(s, "/"); i >= 0 { 126 | u.Host = s[:i] 127 | u.Opaque = s[i:] 128 | } 129 | 130 | return &u, nil 131 | } 132 | 133 | func hostPortNoPort(u *url.URL) (hostPort, hostNoPort string) { 134 | hostPort = u.Host 135 | hostNoPort = u.Host 136 | if i := strings.LastIndex(u.Host, ":"); i > strings.LastIndex(u.Host, "]") { 137 | hostNoPort = hostNoPort[:i] 138 | } else { 139 | if u.Scheme == "wss" { 140 | hostPort += ":443" 141 | } else { 142 | hostPort += ":80" 143 | } 144 | } 145 | return hostPort, hostNoPort 146 | } 147 | 148 | // DefaultDialer is a dialer with all fields set to the default zero values. 149 | var DefaultDialer *Dialer 150 | 151 | // Dial creates a new client connection. Use requestHeader to specify the 152 | // origin (Origin), subprotocols (Sec-WebSocket-Protocol) and cookies (Cookie). 153 | // Use the response.Header to get the selected subprotocol 154 | // (Sec-WebSocket-Protocol) and cookies (Set-Cookie). 155 | // 156 | // If the WebSocket handshake fails, ErrBadHandshake is returned along with a 157 | // non-nil *http.Response so that callers can handle redirects, authentication, 158 | // etc. 159 | func (d *Dialer) Dial(urlStr string, requestHeader http.Header) (*Conn, *http.Response, error) { 160 | u, err := parseURL(urlStr) 161 | if err != nil { 162 | return nil, nil, err 163 | } 164 | 165 | hostPort, hostNoPort := hostPortNoPort(u) 166 | 167 | if d == nil { 168 | d = &Dialer{} 169 | } 170 | 171 | var deadline time.Time 172 | if d.HandshakeTimeout != 0 { 173 | deadline = time.Now().Add(d.HandshakeTimeout) 174 | } 175 | 176 | netDial := d.NetDial 177 | if netDial == nil { 178 | netDialer := &net.Dialer{Deadline: deadline} 179 | netDial = netDialer.Dial 180 | } 181 | 182 | netConn, err := netDial("tcp", hostPort) 183 | if err != nil { 184 | return nil, nil, err 185 | } 186 | 187 | defer func() { 188 | if netConn != nil { 189 | netConn.Close() 190 | } 191 | }() 192 | 193 | if err := netConn.SetDeadline(deadline); err != nil { 194 | return nil, nil, err 195 | } 196 | 197 | if u.Scheme == "wss" { 198 | cfg := d.TLSClientConfig 199 | if cfg == nil { 200 | cfg = &tls.Config{ServerName: hostNoPort} 201 | } else if cfg.ServerName == "" { 202 | shallowCopy := *cfg 203 | cfg = &shallowCopy 204 | cfg.ServerName = hostNoPort 205 | } 206 | tlsConn := tls.Client(netConn, cfg) 207 | netConn = tlsConn 208 | if err := tlsConn.Handshake(); err != nil { 209 | return nil, nil, err 210 | } 211 | if !cfg.InsecureSkipVerify { 212 | if err := tlsConn.VerifyHostname(cfg.ServerName); err != nil { 213 | return nil, nil, err 214 | } 215 | } 216 | } 217 | 218 | if len(d.Subprotocols) > 0 { 219 | h := http.Header{} 220 | for k, v := range requestHeader { 221 | h[k] = v 222 | } 223 | h.Set("Sec-Websocket-Protocol", strings.Join(d.Subprotocols, ", ")) 224 | requestHeader = h 225 | } 226 | 227 | conn, resp, err := NewClient(netConn, u, requestHeader, d.ReadBufferSize, d.WriteBufferSize) 228 | if err != nil { 229 | return nil, resp, err 230 | } 231 | 232 | netConn.SetDeadline(time.Time{}) 233 | netConn = nil // to avoid close in defer. 234 | return conn, resp, nil 235 | } 236 | -------------------------------------------------------------------------------- /Godeps/_workspace/src/github.com/gorilla/websocket/client_server_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2013 The Gorilla WebSocket Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package websocket 6 | 7 | import ( 8 | "crypto/tls" 9 | "crypto/x509" 10 | "io" 11 | "net" 12 | "net/http" 13 | "net/http/httptest" 14 | "net/url" 15 | "reflect" 16 | "testing" 17 | "time" 18 | ) 19 | 20 | var cstUpgrader = Upgrader{ 21 | Subprotocols: []string{"p0", "p1"}, 22 | ReadBufferSize: 1024, 23 | WriteBufferSize: 1024, 24 | Error: func(w http.ResponseWriter, r *http.Request, status int, reason error) { 25 | http.Error(w, reason.Error(), status) 26 | }, 27 | } 28 | 29 | var cstDialer = Dialer{ 30 | Subprotocols: []string{"p1", "p2"}, 31 | ReadBufferSize: 1024, 32 | WriteBufferSize: 1024, 33 | } 34 | 35 | type cstHandler struct{ *testing.T } 36 | 37 | type Server struct { 38 | *httptest.Server 39 | URL string 40 | } 41 | 42 | func newServer(t *testing.T) *Server { 43 | var s Server 44 | s.Server = httptest.NewServer(cstHandler{t}) 45 | s.URL = "ws" + s.Server.URL[len("http"):] 46 | return &s 47 | } 48 | 49 | func newTLSServer(t *testing.T) *Server { 50 | var s Server 51 | s.Server = httptest.NewTLSServer(cstHandler{t}) 52 | s.URL = "ws" + s.Server.URL[len("http"):] 53 | return &s 54 | } 55 | 56 | func (t cstHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { 57 | if r.Method != "GET" { 58 | t.Logf("method %s not allowed", r.Method) 59 | http.Error(w, "method not allowed", 405) 60 | return 61 | } 62 | subprotos := Subprotocols(r) 63 | if !reflect.DeepEqual(subprotos, cstDialer.Subprotocols) { 64 | t.Logf("subprotols=%v, want %v", subprotos, cstDialer.Subprotocols) 65 | http.Error(w, "bad protocol", 400) 66 | return 67 | } 68 | ws, err := cstUpgrader.Upgrade(w, r, http.Header{"Set-Cookie": {"sessionID=1234"}}) 69 | if err != nil { 70 | t.Logf("Upgrade: %v", err) 71 | return 72 | } 73 | defer ws.Close() 74 | 75 | if ws.Subprotocol() != "p1" { 76 | t.Logf("Subprotocol() = %s, want p1", ws.Subprotocol()) 77 | ws.Close() 78 | return 79 | } 80 | op, rd, err := ws.NextReader() 81 | if err != nil { 82 | t.Logf("NextReader: %v", err) 83 | return 84 | } 85 | wr, err := ws.NextWriter(op) 86 | if err != nil { 87 | t.Logf("NextWriter: %v", err) 88 | return 89 | } 90 | if _, err = io.Copy(wr, rd); err != nil { 91 | t.Logf("NextWriter: %v", err) 92 | return 93 | } 94 | if err := wr.Close(); err != nil { 95 | t.Logf("Close: %v", err) 96 | return 97 | } 98 | } 99 | 100 | func sendRecv(t *testing.T, ws *Conn) { 101 | const message = "Hello World!" 102 | if err := ws.SetWriteDeadline(time.Now().Add(time.Second)); err != nil { 103 | t.Fatalf("SetWriteDeadline: %v", err) 104 | } 105 | if err := ws.WriteMessage(TextMessage, []byte(message)); err != nil { 106 | t.Fatalf("WriteMessage: %v", err) 107 | } 108 | if err := ws.SetReadDeadline(time.Now().Add(time.Second)); err != nil { 109 | t.Fatalf("SetReadDeadline: %v", err) 110 | } 111 | _, p, err := ws.ReadMessage() 112 | if err != nil { 113 | t.Fatalf("ReadMessage: %v", err) 114 | } 115 | if string(p) != message { 116 | t.Fatalf("message=%s, want %s", p, message) 117 | } 118 | } 119 | 120 | func TestDial(t *testing.T) { 121 | s := newServer(t) 122 | defer s.Close() 123 | 124 | ws, _, err := cstDialer.Dial(s.URL, nil) 125 | if err != nil { 126 | t.Fatalf("Dial: %v", err) 127 | } 128 | defer ws.Close() 129 | sendRecv(t, ws) 130 | } 131 | 132 | func TestDialTLS(t *testing.T) { 133 | s := newTLSServer(t) 134 | defer s.Close() 135 | 136 | certs := x509.NewCertPool() 137 | for _, c := range s.TLS.Certificates { 138 | roots, err := x509.ParseCertificates(c.Certificate[len(c.Certificate)-1]) 139 | if err != nil { 140 | t.Fatalf("error parsing server's root cert: %v", err) 141 | } 142 | for _, root := range roots { 143 | certs.AddCert(root) 144 | } 145 | } 146 | 147 | u, _ := url.Parse(s.URL) 148 | d := cstDialer 149 | d.NetDial = func(network, addr string) (net.Conn, error) { return net.Dial(network, u.Host) } 150 | d.TLSClientConfig = &tls.Config{RootCAs: certs} 151 | ws, _, err := d.Dial("wss://example.com/", nil) 152 | if err != nil { 153 | t.Fatalf("Dial: %v", err) 154 | } 155 | defer ws.Close() 156 | sendRecv(t, ws) 157 | } 158 | 159 | func xTestDialTLSBadCert(t *testing.T) { 160 | s := newTLSServer(t) 161 | defer s.Close() 162 | 163 | ws, _, err := cstDialer.Dial(s.URL, nil) 164 | if err == nil { 165 | ws.Close() 166 | t.Fatalf("Dial: nil") 167 | } 168 | } 169 | 170 | func xTestDialTLSNoVerify(t *testing.T) { 171 | s := newTLSServer(t) 172 | defer s.Close() 173 | 174 | d := cstDialer 175 | d.TLSClientConfig = &tls.Config{InsecureSkipVerify: true} 176 | ws, _, err := d.Dial(s.URL, nil) 177 | if err != nil { 178 | t.Fatalf("Dial: %v", err) 179 | } 180 | defer ws.Close() 181 | sendRecv(t, ws) 182 | } 183 | 184 | func TestDialTimeout(t *testing.T) { 185 | s := newServer(t) 186 | defer s.Close() 187 | 188 | d := cstDialer 189 | d.HandshakeTimeout = -1 190 | ws, _, err := d.Dial(s.URL, nil) 191 | if err == nil { 192 | ws.Close() 193 | t.Fatalf("Dial: nil") 194 | } 195 | } 196 | 197 | func TestDialBadScheme(t *testing.T) { 198 | s := newServer(t) 199 | defer s.Close() 200 | 201 | ws, _, err := cstDialer.Dial(s.Server.URL, nil) 202 | if err == nil { 203 | ws.Close() 204 | t.Fatalf("Dial: nil") 205 | } 206 | } 207 | 208 | func TestDialBadOrigin(t *testing.T) { 209 | s := newServer(t) 210 | defer s.Close() 211 | 212 | ws, resp, err := cstDialer.Dial(s.URL, http.Header{"Origin": {"bad"}}) 213 | if err == nil { 214 | ws.Close() 215 | t.Fatalf("Dial: nil") 216 | } 217 | if resp == nil { 218 | t.Fatalf("resp=nil, err=%v", err) 219 | } 220 | if resp.StatusCode != http.StatusForbidden { 221 | t.Fatalf("status=%d, want %d", resp.StatusCode, http.StatusForbidden) 222 | } 223 | } 224 | 225 | func TestHandshake(t *testing.T) { 226 | s := newServer(t) 227 | defer s.Close() 228 | 229 | ws, resp, err := cstDialer.Dial(s.URL, http.Header{"Origin": {s.URL}}) 230 | if err != nil { 231 | t.Fatalf("Dial: %v", err) 232 | } 233 | defer ws.Close() 234 | 235 | var sessionID string 236 | for _, c := range resp.Cookies() { 237 | if c.Name == "sessionID" { 238 | sessionID = c.Value 239 | } 240 | } 241 | if sessionID != "1234" { 242 | t.Error("Set-Cookie not received from the server.") 243 | } 244 | 245 | if ws.Subprotocol() != "p1" { 246 | t.Errorf("ws.Subprotocol() = %s, want p1", ws.Subprotocol()) 247 | } 248 | sendRecv(t, ws) 249 | } 250 | -------------------------------------------------------------------------------- /Godeps/_workspace/src/github.com/gorilla/websocket/client_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2014 The Gorilla WebSocket Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package websocket 6 | 7 | import ( 8 | "net/url" 9 | "reflect" 10 | "testing" 11 | ) 12 | 13 | var parseURLTests = []struct { 14 | s string 15 | u *url.URL 16 | }{ 17 | {"ws://example.com/", &url.URL{Scheme: "ws", Host: "example.com", Opaque: "/"}}, 18 | {"ws://example.com", &url.URL{Scheme: "ws", Host: "example.com", Opaque: "/"}}, 19 | {"ws://example.com:7777/", &url.URL{Scheme: "ws", Host: "example.com:7777", Opaque: "/"}}, 20 | {"wss://example.com/", &url.URL{Scheme: "wss", Host: "example.com", Opaque: "/"}}, 21 | {"wss://example.com/a/b", &url.URL{Scheme: "wss", Host: "example.com", Opaque: "/a/b"}}, 22 | {"ss://example.com/a/b", nil}, 23 | } 24 | 25 | func TestParseURL(t *testing.T) { 26 | for _, tt := range parseURLTests { 27 | u, err := parseURL(tt.s) 28 | if tt.u != nil && err != nil { 29 | t.Errorf("parseURL(%q) returned error %v", tt.s, err) 30 | continue 31 | } 32 | if tt.u == nil && err == nil { 33 | t.Errorf("parseURL(%q) did not return error", tt.s) 34 | continue 35 | } 36 | if !reflect.DeepEqual(u, tt.u) { 37 | t.Errorf("parseURL(%q) returned %v, want %v", tt.s, u, tt.u) 38 | continue 39 | } 40 | } 41 | } 42 | 43 | var hostPortNoPortTests = []struct { 44 | u *url.URL 45 | hostPort, hostNoPort string 46 | }{ 47 | {&url.URL{Scheme: "ws", Host: "example.com"}, "example.com:80", "example.com"}, 48 | {&url.URL{Scheme: "wss", Host: "example.com"}, "example.com:443", "example.com"}, 49 | {&url.URL{Scheme: "ws", Host: "example.com:7777"}, "example.com:7777", "example.com"}, 50 | {&url.URL{Scheme: "wss", Host: "example.com:7777"}, "example.com:7777", "example.com"}, 51 | } 52 | 53 | func TestHostPortNoPort(t *testing.T) { 54 | for _, tt := range hostPortNoPortTests { 55 | hostPort, hostNoPort := hostPortNoPort(tt.u) 56 | if hostPort != tt.hostPort { 57 | t.Errorf("hostPortNoPort(%v) returned hostPort %q, want %q", tt.u, hostPort, tt.hostPort) 58 | } 59 | if hostNoPort != tt.hostNoPort { 60 | t.Errorf("hostPortNoPort(%v) returned hostNoPort %q, want %q", tt.u, hostNoPort, tt.hostNoPort) 61 | } 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /Godeps/_workspace/src/github.com/gorilla/websocket/conn_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2013 The Gorilla WebSocket Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package websocket 6 | 7 | import ( 8 | "bytes" 9 | "fmt" 10 | "io" 11 | "io/ioutil" 12 | "net" 13 | "testing" 14 | "testing/iotest" 15 | "time" 16 | ) 17 | 18 | var _ net.Error = errWriteTimeout 19 | 20 | type fakeNetConn struct { 21 | io.Reader 22 | io.Writer 23 | } 24 | 25 | func (c fakeNetConn) Close() error { return nil } 26 | func (c fakeNetConn) LocalAddr() net.Addr { return nil } 27 | func (c fakeNetConn) RemoteAddr() net.Addr { return nil } 28 | func (c fakeNetConn) SetDeadline(t time.Time) error { return nil } 29 | func (c fakeNetConn) SetReadDeadline(t time.Time) error { return nil } 30 | func (c fakeNetConn) SetWriteDeadline(t time.Time) error { return nil } 31 | 32 | func TestFraming(t *testing.T) { 33 | frameSizes := []int{0, 1, 2, 124, 125, 126, 127, 128, 129, 65534, 65535, 65536, 65537} 34 | var readChunkers = []struct { 35 | name string 36 | f func(io.Reader) io.Reader 37 | }{ 38 | {"half", iotest.HalfReader}, 39 | {"one", iotest.OneByteReader}, 40 | {"asis", func(r io.Reader) io.Reader { return r }}, 41 | } 42 | 43 | writeBuf := make([]byte, 65537) 44 | for i := range writeBuf { 45 | writeBuf[i] = byte(i) 46 | } 47 | 48 | for _, isServer := range []bool{true, false} { 49 | for _, chunker := range readChunkers { 50 | 51 | var connBuf bytes.Buffer 52 | wc := newConn(fakeNetConn{Reader: nil, Writer: &connBuf}, isServer, 1024, 1024) 53 | rc := newConn(fakeNetConn{Reader: chunker.f(&connBuf), Writer: nil}, !isServer, 1024, 1024) 54 | 55 | for _, n := range frameSizes { 56 | for _, iocopy := range []bool{true, false} { 57 | name := fmt.Sprintf("s:%v, r:%s, n:%d c:%v", isServer, chunker.name, n, iocopy) 58 | 59 | w, err := wc.NextWriter(TextMessage) 60 | if err != nil { 61 | t.Errorf("%s: wc.NextWriter() returned %v", name, err) 62 | continue 63 | } 64 | var nn int 65 | if iocopy { 66 | var n64 int64 67 | n64, err = io.Copy(w, bytes.NewReader(writeBuf[:n])) 68 | nn = int(n64) 69 | } else { 70 | nn, err = w.Write(writeBuf[:n]) 71 | } 72 | if err != nil || nn != n { 73 | t.Errorf("%s: w.Write(writeBuf[:n]) returned %d, %v", name, nn, err) 74 | continue 75 | } 76 | err = w.Close() 77 | if err != nil { 78 | t.Errorf("%s: w.Close() returned %v", name, err) 79 | continue 80 | } 81 | 82 | opCode, r, err := rc.NextReader() 83 | if err != nil || opCode != TextMessage { 84 | t.Errorf("%s: NextReader() returned %d, r, %v", name, opCode, err) 85 | continue 86 | } 87 | rbuf, err := ioutil.ReadAll(r) 88 | if err != nil { 89 | t.Errorf("%s: ReadFull() returned rbuf, %v", name, err) 90 | continue 91 | } 92 | 93 | if len(rbuf) != n { 94 | t.Errorf("%s: len(rbuf) is %d, want %d", name, len(rbuf), n) 95 | continue 96 | } 97 | 98 | for i, b := range rbuf { 99 | if byte(i) != b { 100 | t.Errorf("%s: bad byte at offset %d", name, i) 101 | break 102 | } 103 | } 104 | } 105 | } 106 | } 107 | } 108 | } 109 | 110 | func TestControl(t *testing.T) { 111 | const message = "this is a ping/pong messsage" 112 | for _, isServer := range []bool{true, false} { 113 | for _, isWriteControl := range []bool{true, false} { 114 | name := fmt.Sprintf("s:%v, wc:%v", isServer, isWriteControl) 115 | var connBuf bytes.Buffer 116 | wc := newConn(fakeNetConn{Reader: nil, Writer: &connBuf}, isServer, 1024, 1024) 117 | rc := newConn(fakeNetConn{Reader: &connBuf, Writer: nil}, !isServer, 1024, 1024) 118 | if isWriteControl { 119 | wc.WriteControl(PongMessage, []byte(message), time.Now().Add(time.Second)) 120 | } else { 121 | w, err := wc.NextWriter(PongMessage) 122 | if err != nil { 123 | t.Errorf("%s: wc.NextWriter() returned %v", name, err) 124 | continue 125 | } 126 | if _, err := w.Write([]byte(message)); err != nil { 127 | t.Errorf("%s: w.Write() returned %v", name, err) 128 | continue 129 | } 130 | if err := w.Close(); err != nil { 131 | t.Errorf("%s: w.Close() returned %v", name, err) 132 | continue 133 | } 134 | var actualMessage string 135 | rc.SetPongHandler(func(s string) error { actualMessage = s; return nil }) 136 | rc.NextReader() 137 | if actualMessage != message { 138 | t.Errorf("%s: pong=%q, want %q", name, actualMessage, message) 139 | continue 140 | } 141 | } 142 | } 143 | } 144 | } 145 | 146 | func TestCloseBeforeFinalFrame(t *testing.T) { 147 | const bufSize = 512 148 | 149 | var b1, b2 bytes.Buffer 150 | wc := newConn(fakeNetConn{Reader: nil, Writer: &b1}, false, 1024, bufSize) 151 | rc := newConn(fakeNetConn{Reader: &b1, Writer: &b2}, true, 1024, 1024) 152 | 153 | w, _ := wc.NextWriter(BinaryMessage) 154 | w.Write(make([]byte, bufSize+bufSize/2)) 155 | wc.WriteControl(CloseMessage, FormatCloseMessage(CloseNormalClosure, ""), time.Now().Add(10*time.Second)) 156 | w.Close() 157 | 158 | op, r, err := rc.NextReader() 159 | if op != BinaryMessage || err != nil { 160 | t.Fatalf("NextReader() returned %d, %v", op, err) 161 | } 162 | _, err = io.Copy(ioutil.Discard, r) 163 | if err != errUnexpectedEOF { 164 | t.Fatalf("io.Copy() returned %v, want %v", err, errUnexpectedEOF) 165 | } 166 | _, _, err = rc.NextReader() 167 | if err != io.EOF { 168 | t.Fatalf("NextReader() returned %v, want %v", err, io.EOF) 169 | } 170 | } 171 | 172 | func TestEOFBeforeFinalFrame(t *testing.T) { 173 | const bufSize = 512 174 | 175 | var b1, b2 bytes.Buffer 176 | wc := newConn(fakeNetConn{Reader: nil, Writer: &b1}, false, 1024, bufSize) 177 | rc := newConn(fakeNetConn{Reader: &b1, Writer: &b2}, true, 1024, 1024) 178 | 179 | w, _ := wc.NextWriter(BinaryMessage) 180 | w.Write(make([]byte, bufSize+bufSize/2)) 181 | 182 | op, r, err := rc.NextReader() 183 | if op != BinaryMessage || err != nil { 184 | t.Fatalf("NextReader() returned %d, %v", op, err) 185 | } 186 | _, err = io.Copy(ioutil.Discard, r) 187 | if err != errUnexpectedEOF { 188 | t.Fatalf("io.Copy() returned %v, want %v", err, errUnexpectedEOF) 189 | } 190 | _, _, err = rc.NextReader() 191 | if err != errUnexpectedEOF { 192 | t.Fatalf("NextReader() returned %v, want %v", err, errUnexpectedEOF) 193 | } 194 | } 195 | 196 | func TestReadLimit(t *testing.T) { 197 | 198 | const readLimit = 512 199 | message := make([]byte, readLimit+1) 200 | 201 | var b1, b2 bytes.Buffer 202 | wc := newConn(fakeNetConn{Reader: nil, Writer: &b1}, false, 1024, readLimit-2) 203 | rc := newConn(fakeNetConn{Reader: &b1, Writer: &b2}, true, 1024, 1024) 204 | rc.SetReadLimit(readLimit) 205 | 206 | // Send message at the limit with interleaved pong. 207 | w, _ := wc.NextWriter(BinaryMessage) 208 | w.Write(message[:readLimit-1]) 209 | wc.WriteControl(PongMessage, []byte("this is a pong"), time.Now().Add(10*time.Second)) 210 | w.Write(message[:1]) 211 | w.Close() 212 | 213 | // Send message larger than the limit. 214 | wc.WriteMessage(BinaryMessage, message[:readLimit+1]) 215 | 216 | op, _, err := rc.NextReader() 217 | if op != BinaryMessage || err != nil { 218 | t.Fatalf("1: NextReader() returned %d, %v", op, err) 219 | } 220 | op, r, err := rc.NextReader() 221 | if op != BinaryMessage || err != nil { 222 | t.Fatalf("2: NextReader() returned %d, %v", op, err) 223 | } 224 | _, err = io.Copy(ioutil.Discard, r) 225 | if err != ErrReadLimit { 226 | t.Fatalf("io.Copy() returned %v", err) 227 | } 228 | } 229 | 230 | func TestUnderlyingConn(t *testing.T) { 231 | var b1, b2 bytes.Buffer 232 | fc := fakeNetConn{Reader: &b1, Writer: &b2} 233 | c := newConn(fc, true, 1024, 1024) 234 | ul := c.UnderlyingConn() 235 | if ul != fc { 236 | t.Fatalf("Underlying conn is not what it should be.") 237 | } 238 | } 239 | -------------------------------------------------------------------------------- /Godeps/_workspace/src/github.com/gorilla/websocket/doc.go: -------------------------------------------------------------------------------- 1 | // Copyright 2013 The Gorilla WebSocket Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | // Package websocket implements the WebSocket protocol defined in RFC 6455. 6 | // 7 | // Overview 8 | // 9 | // The Conn type represents a WebSocket connection. A server application uses 10 | // the Upgrade function from an Upgrader object with a HTTP request handler 11 | // to get a pointer to a Conn: 12 | // 13 | // var upgrader = websocket.Upgrader{ 14 | // ReadBufferSize: 1024, 15 | // WriteBufferSize: 1024, 16 | // } 17 | // 18 | // func handler(w http.ResponseWriter, r *http.Request) { 19 | // conn, err := upgrader.Upgrade(w, r, nil) 20 | // if err != nil { 21 | // log.Println(err) 22 | // return 23 | // } 24 | // ... Use conn to send and receive messages. 25 | // } 26 | // 27 | // Call the connection WriteMessage and ReadMessages methods to send and 28 | // receive messages as a slice of bytes. This snippet of code shows how to echo 29 | // messages using these methods: 30 | // 31 | // for { 32 | // messageType, p, err := conn.ReadMessage() 33 | // if err != nil { 34 | // return 35 | // } 36 | // if err = conn.WriteMessage(messageType, p); err != nil { 37 | // return err 38 | // } 39 | // } 40 | // 41 | // In above snippet of code, p is a []byte and messageType is an int with value 42 | // websocket.BinaryMessage or websocket.TextMessage. 43 | // 44 | // An application can also send and receive messages using the io.WriteCloser 45 | // and io.Reader interfaces. To send a message, call the connection NextWriter 46 | // method to get an io.WriteCloser, write the message to the writer and close 47 | // the writer when done. To receive a message, call the connection NextReader 48 | // method to get an io.Reader and read until io.EOF is returned. This snippet 49 | // snippet shows how to echo messages using the NextWriter and NextReader 50 | // methods: 51 | // 52 | // for { 53 | // messageType, r, err := conn.NextReader() 54 | // if err != nil { 55 | // return 56 | // } 57 | // w, err := conn.NextWriter(messageType) 58 | // if err != nil { 59 | // return err 60 | // } 61 | // if _, err := io.Copy(w, r); err != nil { 62 | // return err 63 | // } 64 | // if err := w.Close(); err != nil { 65 | // return err 66 | // } 67 | // } 68 | // 69 | // Data Messages 70 | // 71 | // The WebSocket protocol distinguishes between text and binary data messages. 72 | // Text messages are interpreted as UTF-8 encoded text. The interpretation of 73 | // binary messages is left to the application. 74 | // 75 | // This package uses the TextMessage and BinaryMessage integer constants to 76 | // identify the two data message types. The ReadMessage and NextReader methods 77 | // return the type of the received message. The messageType argument to the 78 | // WriteMessage and NextWriter methods specifies the type of a sent message. 79 | // 80 | // It is the application's responsibility to ensure that text messages are 81 | // valid UTF-8 encoded text. 82 | // 83 | // Control Messages 84 | // 85 | // The WebSocket protocol defines three types of control messages: close, ping 86 | // and pong. Call the connection WriteControl, WriteMessage or NextWriter 87 | // methods to send a control message to the peer. 88 | // 89 | // Connections handle received ping and pong messages by invoking a callback 90 | // function set with SetPingHandler and SetPongHandler methods. These callback 91 | // functions can be invoked from the ReadMessage method, the NextReader method 92 | // or from a call to the data message reader returned from NextReader. 93 | // 94 | // Connections handle received close messages by returning an error from the 95 | // ReadMessage method, the NextReader method or from a call to the data message 96 | // reader returned from NextReader. 97 | // 98 | // Concurrency 99 | // 100 | // Connections do not support concurrent calls to the write methods 101 | // (NextWriter, SetWriteDeadline, WriteMessage) or concurrent calls to the read 102 | // methods methods (NextReader, SetReadDeadline, ReadMessage). Connections do 103 | // support a concurrent reader and writer. 104 | // 105 | // The Close and WriteControl methods can be called concurrently with all other 106 | // methods. 107 | // 108 | // Read is Required 109 | // 110 | // The application must read the connection to process ping and close messages 111 | // sent from the peer. If the application is not otherwise interested in 112 | // messages from the peer, then the application should start a goroutine to read 113 | // and discard messages from the peer. A simple example is: 114 | // 115 | // func readLoop(c *websocket.Conn) { 116 | // for { 117 | // if _, _, err := c.NextReader(); err != nil { 118 | // c.Close() 119 | // break 120 | // } 121 | // } 122 | // } 123 | // 124 | // Origin Considerations 125 | // 126 | // Web browsers allow Javascript applications to open a WebSocket connection to 127 | // any host. It's up to the server to enforce an origin policy using the Origin 128 | // request header sent by the browser. 129 | // 130 | // The Upgrader calls the function specified in the CheckOrigin field to check 131 | // the origin. If the CheckOrigin function returns false, then the Upgrade 132 | // method fails the WebSocket handshake with HTTP status 403. 133 | // 134 | // If the CheckOrigin field is nil, then the Upgrader uses a safe default: fail 135 | // the handshake if the Origin request header is present and not equal to the 136 | // Host request header. 137 | // 138 | // An application can allow connections from any origin by specifying a 139 | // function that always returns true: 140 | // 141 | // var upgrader = websocket.Upgrader{ 142 | // CheckOrigin: func(r *http.Request) bool { return true }, 143 | // } 144 | // 145 | // The deprecated Upgrade function does not enforce an origin policy. It's the 146 | // application's responsibility to check the Origin header before calling 147 | // Upgrade. 148 | package websocket 149 | -------------------------------------------------------------------------------- /Godeps/_workspace/src/github.com/gorilla/websocket/examples/autobahn/README.md: -------------------------------------------------------------------------------- 1 | # Test Server 2 | 3 | This package contains a server for the [Autobahn WebSockets Test Suite](http://autobahn.ws/testsuite). 4 | 5 | To test the server, run 6 | 7 | go run server.go 8 | 9 | and start the client test driver 10 | 11 | wstest -m fuzzingclient -s fuzzingclient.json 12 | 13 | When the client completes, it writes a report to reports/clients/index.html. 14 | -------------------------------------------------------------------------------- /Godeps/_workspace/src/github.com/gorilla/websocket/examples/autobahn/fuzzingclient.json: -------------------------------------------------------------------------------- 1 | 2 | { 3 | "options": {"failByDrop": false}, 4 | "outdir": "./reports/clients", 5 | "servers": [ 6 | {"agent": "ReadAllWriteMessage", "url": "ws://localhost:9000/m", "options": {"version": 18}}, 7 | {"agent": "ReadAllWrite", "url": "ws://localhost:9000/r", "options": {"version": 18}}, 8 | {"agent": "CopyFull", "url": "ws://localhost:9000/f", "options": {"version": 18}}, 9 | {"agent": "CopyWriterOnly", "url": "ws://localhost:9000/c", "options": {"version": 18}} 10 | ], 11 | "cases": ["*"], 12 | "exclude-cases": [], 13 | "exclude-agent-cases": {} 14 | } 15 | -------------------------------------------------------------------------------- /Godeps/_workspace/src/github.com/gorilla/websocket/examples/chat/README.md: -------------------------------------------------------------------------------- 1 | # Chat Example 2 | 3 | This application shows how to use use the 4 | [websocket](https://github.com/gorilla/websocket) package and 5 | [jQuery](http://jquery.com) to implement a simple web chat application. 6 | 7 | ## Running the example 8 | 9 | The example requires a working Go development environment. The [Getting 10 | Started](http://golang.org/doc/install) page describes how to install the 11 | development environment. 12 | 13 | Once you have Go up and running, you can download, build and run the example 14 | using the following commands. 15 | 16 | $ go get github.com/gorilla/websocket 17 | $ cd `go list -f '{{.Dir}}' github.com/gorilla/websocket/examples/chat` 18 | $ go run *.go 19 | 20 | -------------------------------------------------------------------------------- /Godeps/_workspace/src/github.com/gorilla/websocket/examples/chat/conn.go: -------------------------------------------------------------------------------- 1 | // Copyright 2013 The Gorilla WebSocket Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package main 6 | 7 | import ( 8 | "github.com/gorilla/websocket" 9 | "log" 10 | "net/http" 11 | "time" 12 | ) 13 | 14 | const ( 15 | // Time allowed to write a message to the peer. 16 | writeWait = 10 * time.Second 17 | 18 | // Time allowed to read the next pong message from the peer. 19 | pongWait = 60 * time.Second 20 | 21 | // Send pings to peer with this period. Must be less than pongWait. 22 | pingPeriod = (pongWait * 9) / 10 23 | 24 | // Maximum message size allowed from peer. 25 | maxMessageSize = 512 26 | ) 27 | 28 | var upgrader = websocket.Upgrader{ 29 | ReadBufferSize: 1024, 30 | WriteBufferSize: 1024, 31 | } 32 | 33 | // connection is an middleman between the websocket connection and the hub. 34 | type connection struct { 35 | // The websocket connection. 36 | ws *websocket.Conn 37 | 38 | // Buffered channel of outbound messages. 39 | send chan []byte 40 | } 41 | 42 | // readPump pumps messages from the websocket connection to the hub. 43 | func (c *connection) readPump() { 44 | defer func() { 45 | h.unregister <- c 46 | c.ws.Close() 47 | }() 48 | c.ws.SetReadLimit(maxMessageSize) 49 | c.ws.SetReadDeadline(time.Now().Add(pongWait)) 50 | c.ws.SetPongHandler(func(string) error { c.ws.SetReadDeadline(time.Now().Add(pongWait)); return nil }) 51 | for { 52 | _, message, err := c.ws.ReadMessage() 53 | if err != nil { 54 | break 55 | } 56 | h.broadcast <- message 57 | } 58 | } 59 | 60 | // write writes a message with the given message type and payload. 61 | func (c *connection) write(mt int, payload []byte) error { 62 | c.ws.SetWriteDeadline(time.Now().Add(writeWait)) 63 | return c.ws.WriteMessage(mt, payload) 64 | } 65 | 66 | // writePump pumps messages from the hub to the websocket connection. 67 | func (c *connection) writePump() { 68 | ticker := time.NewTicker(pingPeriod) 69 | defer func() { 70 | ticker.Stop() 71 | c.ws.Close() 72 | }() 73 | for { 74 | select { 75 | case message, ok := <-c.send: 76 | if !ok { 77 | c.write(websocket.CloseMessage, []byte{}) 78 | return 79 | } 80 | if err := c.write(websocket.TextMessage, message); err != nil { 81 | return 82 | } 83 | case <-ticker.C: 84 | if err := c.write(websocket.PingMessage, []byte{}); err != nil { 85 | return 86 | } 87 | } 88 | } 89 | } 90 | 91 | // serverWs handles websocket requests from the peer. 92 | func serveWs(w http.ResponseWriter, r *http.Request) { 93 | if r.Method != "GET" { 94 | http.Error(w, "Method not allowed", 405) 95 | return 96 | } 97 | ws, err := upgrader.Upgrade(w, r, nil) 98 | if err != nil { 99 | log.Println(err) 100 | return 101 | } 102 | c := &connection{send: make(chan []byte, 256), ws: ws} 103 | h.register <- c 104 | go c.writePump() 105 | c.readPump() 106 | } 107 | -------------------------------------------------------------------------------- /Godeps/_workspace/src/github.com/gorilla/websocket/examples/chat/home.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Chat Example 5 | 6 | 47 | 84 | 85 | 86 |
87 |
88 | 89 | 90 |
91 | 92 | 93 | -------------------------------------------------------------------------------- /Godeps/_workspace/src/github.com/gorilla/websocket/examples/chat/hub.go: -------------------------------------------------------------------------------- 1 | // Copyright 2013 The Gorilla WebSocket Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package main 6 | 7 | // hub maintains the set of active connections and broadcasts messages to the 8 | // connections. 9 | type hub struct { 10 | // Registered connections. 11 | connections map[*connection]bool 12 | 13 | // Inbound messages from the connections. 14 | broadcast chan []byte 15 | 16 | // Register requests from the connections. 17 | register chan *connection 18 | 19 | // Unregister requests from connections. 20 | unregister chan *connection 21 | } 22 | 23 | var h = hub{ 24 | broadcast: make(chan []byte), 25 | register: make(chan *connection), 26 | unregister: make(chan *connection), 27 | connections: make(map[*connection]bool), 28 | } 29 | 30 | func (h *hub) run() { 31 | for { 32 | select { 33 | case c := <-h.register: 34 | h.connections[c] = true 35 | case c := <-h.unregister: 36 | if _, ok := h.connections[c]; ok { 37 | delete(h.connections, c) 38 | close(c.send) 39 | } 40 | case m := <-h.broadcast: 41 | for c := range h.connections { 42 | select { 43 | case c.send <- m: 44 | default: 45 | close(c.send) 46 | delete(h.connections, c) 47 | } 48 | } 49 | } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /Godeps/_workspace/src/github.com/gorilla/websocket/examples/chat/main.go: -------------------------------------------------------------------------------- 1 | // Copyright 2013 The Gorilla WebSocket Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package main 6 | 7 | import ( 8 | "flag" 9 | "log" 10 | "net/http" 11 | "text/template" 12 | ) 13 | 14 | var addr = flag.String("addr", ":8080", "http service address") 15 | var homeTempl = template.Must(template.ParseFiles("home.html")) 16 | 17 | func serveHome(w http.ResponseWriter, r *http.Request) { 18 | if r.URL.Path != "/" { 19 | http.Error(w, "Not found", 404) 20 | return 21 | } 22 | if r.Method != "GET" { 23 | http.Error(w, "Method not allowed", 405) 24 | return 25 | } 26 | w.Header().Set("Content-Type", "text/html; charset=utf-8") 27 | homeTempl.Execute(w, r.Host) 28 | } 29 | 30 | func main() { 31 | flag.Parse() 32 | go h.run() 33 | http.HandleFunc("/", serveHome) 34 | http.HandleFunc("/ws", serveWs) 35 | err := http.ListenAndServe(*addr, nil) 36 | if err != nil { 37 | log.Fatal("ListenAndServe: ", err) 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /Godeps/_workspace/src/github.com/gorilla/websocket/examples/filewatch/README.md: -------------------------------------------------------------------------------- 1 | # File Watch example. 2 | 3 | This example sends a file to the browser client for display whenever the file is modified. 4 | 5 | $ go get github.com/gorilla/websocket 6 | $ cd `go list -f '{{.Dir}}' github.com/gorilla/websocket/examples/filewatch` 7 | $ go run main.go 8 | # Open http://localhost:8080/ . 9 | # Modify the file to see it update in the browser. 10 | -------------------------------------------------------------------------------- /Godeps/_workspace/src/github.com/gorilla/websocket/examples/filewatch/main.go: -------------------------------------------------------------------------------- 1 | // Copyright 2013 The Gorilla WebSocket Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package main 6 | 7 | import ( 8 | "flag" 9 | "io/ioutil" 10 | "log" 11 | "net/http" 12 | "os" 13 | "strconv" 14 | "text/template" 15 | "time" 16 | 17 | "github.com/gorilla/websocket" 18 | ) 19 | 20 | const ( 21 | // Time allowed to write the file to the client. 22 | writeWait = 10 * time.Second 23 | 24 | // Time allowed to read the next pong message from the client. 25 | pongWait = 60 * time.Second 26 | 27 | // Send pings to client with this period. Must be less than pongWait. 28 | pingPeriod = (pongWait * 9) / 10 29 | 30 | // Poll file for changes with this period. 31 | filePeriod = 10 * time.Second 32 | ) 33 | 34 | var ( 35 | addr = flag.String("addr", ":8080", "http service address") 36 | homeTempl = template.Must(template.New("").Parse(homeHTML)) 37 | filename string 38 | upgrader = websocket.Upgrader{ 39 | ReadBufferSize: 1024, 40 | WriteBufferSize: 1024, 41 | } 42 | ) 43 | 44 | func readFileIfModified(lastMod time.Time) ([]byte, time.Time, error) { 45 | fi, err := os.Stat(filename) 46 | if err != nil { 47 | return nil, lastMod, err 48 | } 49 | if !fi.ModTime().After(lastMod) { 50 | return nil, lastMod, nil 51 | } 52 | p, err := ioutil.ReadFile(filename) 53 | if err != nil { 54 | return nil, fi.ModTime(), err 55 | } 56 | return p, fi.ModTime(), nil 57 | } 58 | 59 | func reader(ws *websocket.Conn) { 60 | defer ws.Close() 61 | ws.SetReadLimit(512) 62 | ws.SetReadDeadline(time.Now().Add(pongWait)) 63 | ws.SetPongHandler(func(string) error { ws.SetReadDeadline(time.Now().Add(pongWait)); return nil }) 64 | for { 65 | _, _, err := ws.ReadMessage() 66 | if err != nil { 67 | break 68 | } 69 | } 70 | } 71 | 72 | func writer(ws *websocket.Conn, lastMod time.Time) { 73 | lastError := "" 74 | pingTicker := time.NewTicker(pingPeriod) 75 | fileTicker := time.NewTicker(filePeriod) 76 | defer func() { 77 | pingTicker.Stop() 78 | fileTicker.Stop() 79 | ws.Close() 80 | }() 81 | for { 82 | select { 83 | case <-fileTicker.C: 84 | var p []byte 85 | var err error 86 | 87 | p, lastMod, err = readFileIfModified(lastMod) 88 | 89 | if err != nil { 90 | if s := err.Error(); s != lastError { 91 | lastError = s 92 | p = []byte(lastError) 93 | } 94 | } else { 95 | lastError = "" 96 | } 97 | 98 | if p != nil { 99 | ws.SetWriteDeadline(time.Now().Add(writeWait)) 100 | if err := ws.WriteMessage(websocket.TextMessage, p); err != nil { 101 | return 102 | } 103 | } 104 | case <-pingTicker.C: 105 | ws.SetWriteDeadline(time.Now().Add(writeWait)) 106 | if err := ws.WriteMessage(websocket.PingMessage, []byte{}); err != nil { 107 | return 108 | } 109 | } 110 | } 111 | } 112 | 113 | func serveWs(w http.ResponseWriter, r *http.Request) { 114 | ws, err := upgrader.Upgrade(w, r, nil) 115 | if err != nil { 116 | if _, ok := err.(websocket.HandshakeError); !ok { 117 | log.Println(err) 118 | } 119 | return 120 | } 121 | 122 | var lastMod time.Time 123 | if n, err := strconv.ParseInt(r.FormValue("lastMod"), 16, 64); err != nil { 124 | lastMod = time.Unix(0, n) 125 | } 126 | 127 | go writer(ws, lastMod) 128 | reader(ws) 129 | } 130 | 131 | func serveHome(w http.ResponseWriter, r *http.Request) { 132 | if r.URL.Path != "/" { 133 | http.Error(w, "Not found", 404) 134 | return 135 | } 136 | if r.Method != "GET" { 137 | http.Error(w, "Method not allowed", 405) 138 | return 139 | } 140 | w.Header().Set("Content-Type", "text/html; charset=utf-8") 141 | p, lastMod, err := readFileIfModified(time.Time{}) 142 | if err != nil { 143 | p = []byte(err.Error()) 144 | lastMod = time.Unix(0, 0) 145 | } 146 | var v = struct { 147 | Host string 148 | Data string 149 | LastMod string 150 | }{ 151 | r.Host, 152 | string(p), 153 | strconv.FormatInt(lastMod.UnixNano(), 16), 154 | } 155 | homeTempl.Execute(w, &v) 156 | } 157 | 158 | func main() { 159 | flag.Parse() 160 | if flag.NArg() != 1 { 161 | log.Fatal("filename not specified") 162 | } 163 | filename = flag.Args()[0] 164 | http.HandleFunc("/", serveHome) 165 | http.HandleFunc("/ws", serveWs) 166 | if err := http.ListenAndServe(*addr, nil); err != nil { 167 | log.Fatal(err) 168 | } 169 | } 170 | 171 | const homeHTML = ` 172 | 173 | 174 | WebSocket Example 175 | 176 | 177 |
{{.Data}}
178 | 191 | 192 | 193 | ` 194 | -------------------------------------------------------------------------------- /Godeps/_workspace/src/github.com/gorilla/websocket/json.go: -------------------------------------------------------------------------------- 1 | // Copyright 2013 The Gorilla WebSocket Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package websocket 6 | 7 | import ( 8 | "encoding/json" 9 | ) 10 | 11 | // WriteJSON is deprecated, use c.WriteJSON instead. 12 | func WriteJSON(c *Conn, v interface{}) error { 13 | return c.WriteJSON(v) 14 | } 15 | 16 | // WriteJSON writes the JSON encoding of v to the connection. 17 | // 18 | // See the documentation for encoding/json Marshal for details about the 19 | // conversion of Go values to JSON. 20 | func (c *Conn) WriteJSON(v interface{}) error { 21 | w, err := c.NextWriter(TextMessage) 22 | if err != nil { 23 | return err 24 | } 25 | err1 := json.NewEncoder(w).Encode(v) 26 | err2 := w.Close() 27 | if err1 != nil { 28 | return err1 29 | } 30 | return err2 31 | } 32 | 33 | // ReadJSON is deprecated, use c.ReadJSON instead. 34 | func ReadJSON(c *Conn, v interface{}) error { 35 | return c.ReadJSON(v) 36 | } 37 | 38 | // ReadJSON reads the next JSON-encoded message from the connection and stores 39 | // it in the value pointed to by v. 40 | // 41 | // See the documentation for the encoding/json Unmarshal function for details 42 | // about the conversion of JSON to a Go value. 43 | func (c *Conn) ReadJSON(v interface{}) error { 44 | _, r, err := c.NextReader() 45 | if err != nil { 46 | return err 47 | } 48 | return json.NewDecoder(r).Decode(v) 49 | } 50 | -------------------------------------------------------------------------------- /Godeps/_workspace/src/github.com/gorilla/websocket/json_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2013 The Gorilla WebSocket Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package websocket 6 | 7 | import ( 8 | "bytes" 9 | "reflect" 10 | "testing" 11 | ) 12 | 13 | func TestJSON(t *testing.T) { 14 | var buf bytes.Buffer 15 | c := fakeNetConn{&buf, &buf} 16 | wc := newConn(c, true, 1024, 1024) 17 | rc := newConn(c, false, 1024, 1024) 18 | 19 | var actual, expect struct { 20 | A int 21 | B string 22 | } 23 | expect.A = 1 24 | expect.B = "hello" 25 | 26 | if err := wc.WriteJSON(&expect); err != nil { 27 | t.Fatal("write", err) 28 | } 29 | 30 | if err := rc.ReadJSON(&actual); err != nil { 31 | t.Fatal("read", err) 32 | } 33 | 34 | if !reflect.DeepEqual(&actual, &expect) { 35 | t.Fatal("equal", actual, expect) 36 | } 37 | } 38 | 39 | func TestDeprecatedJSON(t *testing.T) { 40 | var buf bytes.Buffer 41 | c := fakeNetConn{&buf, &buf} 42 | wc := newConn(c, true, 1024, 1024) 43 | rc := newConn(c, false, 1024, 1024) 44 | 45 | var actual, expect struct { 46 | A int 47 | B string 48 | } 49 | expect.A = 1 50 | expect.B = "hello" 51 | 52 | if err := WriteJSON(wc, &expect); err != nil { 53 | t.Fatal("write", err) 54 | } 55 | 56 | if err := ReadJSON(rc, &actual); err != nil { 57 | t.Fatal("read", err) 58 | } 59 | 60 | if !reflect.DeepEqual(&actual, &expect) { 61 | t.Fatal("equal", actual, expect) 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /Godeps/_workspace/src/github.com/gorilla/websocket/server_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2013 The Gorilla WebSocket Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package websocket 6 | 7 | import ( 8 | "net/http" 9 | "reflect" 10 | "testing" 11 | ) 12 | 13 | var subprotocolTests = []struct { 14 | h string 15 | protocols []string 16 | }{ 17 | {"", nil}, 18 | {"foo", []string{"foo"}}, 19 | {"foo,bar", []string{"foo", "bar"}}, 20 | {"foo, bar", []string{"foo", "bar"}}, 21 | {" foo, bar", []string{"foo", "bar"}}, 22 | {" foo, bar ", []string{"foo", "bar"}}, 23 | } 24 | 25 | func TestSubprotocols(t *testing.T) { 26 | for _, st := range subprotocolTests { 27 | r := http.Request{Header: http.Header{"Sec-Websocket-Protocol": {st.h}}} 28 | protocols := Subprotocols(&r) 29 | if !reflect.DeepEqual(st.protocols, protocols) { 30 | t.Errorf("SubProtocols(%q) returned %#v, want %#v", st.h, protocols, st.protocols) 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /Godeps/_workspace/src/github.com/gorilla/websocket/util.go: -------------------------------------------------------------------------------- 1 | // Copyright 2013 The Gorilla WebSocket Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package websocket 6 | 7 | import ( 8 | "crypto/rand" 9 | "crypto/sha1" 10 | "encoding/base64" 11 | "io" 12 | "net/http" 13 | "strings" 14 | ) 15 | 16 | // tokenListContainsValue returns true if the 1#token header with the given 17 | // name contains token. 18 | func tokenListContainsValue(header http.Header, name string, value string) bool { 19 | for _, v := range header[name] { 20 | for _, s := range strings.Split(v, ",") { 21 | if strings.EqualFold(value, strings.TrimSpace(s)) { 22 | return true 23 | } 24 | } 25 | } 26 | return false 27 | } 28 | 29 | var keyGUID = []byte("258EAFA5-E914-47DA-95CA-C5AB0DC85B11") 30 | 31 | func computeAcceptKey(challengeKey string) string { 32 | h := sha1.New() 33 | h.Write([]byte(challengeKey)) 34 | h.Write(keyGUID) 35 | return base64.StdEncoding.EncodeToString(h.Sum(nil)) 36 | } 37 | 38 | func generateChallengeKey() (string, error) { 39 | p := make([]byte, 16) 40 | if _, err := io.ReadFull(rand.Reader, p); err != nil { 41 | return "", err 42 | } 43 | return base64.StdEncoding.EncodeToString(p), nil 44 | } 45 | -------------------------------------------------------------------------------- /Godeps/_workspace/src/github.com/gorilla/websocket/util_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2014 The Gorilla WebSocket Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package websocket 6 | 7 | import ( 8 | "net/http" 9 | "testing" 10 | ) 11 | 12 | var tokenListContainsValueTests = []struct { 13 | value string 14 | ok bool 15 | }{ 16 | {"WebSocket", true}, 17 | {"WEBSOCKET", true}, 18 | {"websocket", true}, 19 | {"websockets", false}, 20 | {"x websocket", false}, 21 | {"websocket x", false}, 22 | {"other,websocket,more", true}, 23 | {"other, websocket, more", true}, 24 | } 25 | 26 | func TestTokenListContainsValue(t *testing.T) { 27 | for _, tt := range tokenListContainsValueTests { 28 | h := http.Header{"Upgrade": {tt.value}} 29 | ok := tokenListContainsValue(h, "Upgrade", "websocket") 30 | if ok != tt.ok { 31 | t.Errorf("tokenListContainsValue(h, n, %q) = %v, want %v", tt.value, ok, tt.ok) 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /Godeps/_workspace/src/github.com/juju/errgo/errors/errors_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2014 Canonical Ltd. 2 | // Licensed under the LGPLv3, see LICENCE file for details. 3 | 4 | package errors_test 5 | 6 | import ( 7 | "fmt" 8 | "github.com/juju/errgo/errors" 9 | "io/ioutil" 10 | "runtime" 11 | "strings" 12 | "testing" 13 | ) 14 | 15 | var ( 16 | _ errors.Wrapper = (*errors.Err)(nil) 17 | _ errors.Locationer = (*errors.Err)(nil) 18 | _ errors.Causer = (*errors.Err)(nil) 19 | ) 20 | 21 | func TestNew(t *testing.T) { 22 | err := errors.New("foo") //err TestNew 23 | checkErr(t, err, nil, "foo", "[{$TestNew$: foo}]", err) 24 | } 25 | 26 | func TestNewf(t *testing.T) { 27 | err := errors.Newf("foo %d", 5) //err TestNewf 28 | checkErr(t, err, nil, "foo 5", "[{$TestNewf$: foo 5}]", err) 29 | } 30 | 31 | var someErr = errors.New("some error") 32 | 33 | func TestMask(t *testing.T) { 34 | err0 := errors.WithCausef(nil, someErr, "foo") //err TestMask#0 35 | err := errors.Mask(err0) //err TestMask#1 36 | checkErr(t, err, err0, "foo", "[{$TestMask#1$: } {$TestMask#0$: foo}]", err) 37 | 38 | err = errors.Mask(nil) 39 | if err != nil { 40 | t.Fatalf("expected nil got %#v", err) 41 | } 42 | } 43 | 44 | func TestNotef(t *testing.T) { 45 | err0 := errors.WithCausef(nil, someErr, "foo") //err TestNotef#0 46 | err := errors.Notef(err0, "bar") //err TestNotef#1 47 | checkErr(t, err, err0, "bar: foo", "[{$TestNotef#1$: bar} {$TestNotef#0$: foo}]", err) 48 | 49 | err = errors.Notef(nil, "bar") //err TestNotef#2 50 | checkErr(t, err, nil, "bar", "[{$TestNotef#2$: bar}]", err) 51 | } 52 | 53 | func TestMaskFunc(t *testing.T) { 54 | err0 := errors.New("zero") 55 | err1 := errors.New("one") 56 | 57 | allowVals := func(vals ...error) (r []func(error) bool) { 58 | for _, val := range vals { 59 | r = append(r, errors.Is(val)) 60 | } 61 | return 62 | } 63 | tests := []struct { 64 | err error 65 | allow0 []func(error) bool 66 | allow1 []func(error) bool 67 | cause error 68 | }{{ 69 | err: err0, 70 | allow0: allowVals(err0), 71 | cause: err0, 72 | }, { 73 | err: err1, 74 | allow0: allowVals(err0), 75 | cause: nil, 76 | }, { 77 | err: err0, 78 | allow1: allowVals(err0), 79 | cause: err0, 80 | }, { 81 | err: err0, 82 | allow0: allowVals(err1), 83 | allow1: allowVals(err0), 84 | cause: err0, 85 | }, { 86 | err: err0, 87 | allow0: allowVals(err0, err1), 88 | cause: err0, 89 | }, { 90 | err: err1, 91 | allow0: allowVals(err0, err1), 92 | cause: err1, 93 | }, { 94 | err: err0, 95 | allow1: allowVals(err0, err1), 96 | cause: err0, 97 | }, { 98 | err: err1, 99 | allow1: allowVals(err0, err1), 100 | cause: err1, 101 | }} 102 | for i, test := range tests { 103 | wrap := errors.MaskFunc(test.allow0...) 104 | err := wrap(test.err, test.allow1...) 105 | cause := errors.Cause(err) 106 | wantCause := test.cause 107 | if wantCause == nil { 108 | wantCause = err 109 | } 110 | if cause != wantCause { 111 | t.Errorf("test %d. got %#v want %#v", i, cause, err) 112 | } 113 | } 114 | } 115 | 116 | type embed struct { 117 | *errors.Err 118 | } 119 | 120 | func TestCause(t *testing.T) { 121 | if cause := errors.Cause(someErr); cause != someErr { 122 | t.Fatalf("expected %q kind; got %#v", someErr, cause) 123 | } 124 | causeErr := errors.New("cause error") 125 | underlyingErr := errors.New("underlying error") //err TestCause#1 126 | err := errors.WithCausef(underlyingErr, causeErr, "foo %d", 99) //err TestCause#2 127 | if errors.Cause(err) != causeErr { 128 | t.Fatalf("expected %q; got %#v", causeErr, errors.Cause(err)) 129 | } 130 | checkErr(t, err, underlyingErr, "foo 99: underlying error", "[{$TestCause#2$: foo 99} {$TestCause#1$: underlying error}]", causeErr) 131 | err = &embed{err.(*errors.Err)} 132 | if errors.Cause(err) != causeErr { 133 | t.Fatalf("expected %q; got %#v", causeErr, errors.Cause(err)) 134 | } 135 | } 136 | 137 | func TestDetails(t *testing.T) { 138 | if details := errors.Details(nil); details != "[]" { 139 | t.Fatalf("errors.Details(nil) got %q want %q", details, "[]") 140 | } 141 | 142 | otherErr := fmt.Errorf("other") 143 | checkErr(t, otherErr, nil, "other", "[{other}]", otherErr) 144 | 145 | err0 := &embed{errors.New("foo").(*errors.Err)} //err TestStack#0 146 | checkErr(t, err0, nil, "foo", "[{$TestStack#0$: foo}]", err0) 147 | 148 | err1 := &embed{errors.Notef(err0, "bar").(*errors.Err)} //err TestStack#1 149 | checkErr(t, err1, err0, "bar: foo", "[{$TestStack#1$: bar} {$TestStack#0$: foo}]", err1) 150 | 151 | err2 := errors.Mask(err1) //err TestStack#2 152 | checkErr(t, err2, err1, "bar: foo", "[{$TestStack#2$: } {$TestStack#1$: bar} {$TestStack#0$: foo}]", err2) 153 | } 154 | 155 | func TestMatch(t *testing.T) { 156 | type errTest func(error) bool 157 | allow := func(ss ...string) []func(error) bool { 158 | fns := make([]func(error) bool, len(ss)) 159 | for i, s := range ss { 160 | s := s 161 | fns[i] = func(err error) bool { 162 | return err != nil && err.Error() == s 163 | } 164 | } 165 | return fns 166 | } 167 | tests := []struct { 168 | err error 169 | fns []func(error) bool 170 | ok bool 171 | }{{ 172 | err: errors.New("foo"), 173 | fns: allow("foo"), 174 | ok: true, 175 | }, { 176 | err: errors.New("foo"), 177 | fns: allow("bar"), 178 | ok: false, 179 | }, { 180 | err: errors.New("foo"), 181 | fns: allow("bar", "foo"), 182 | ok: true, 183 | }, { 184 | err: errors.New("foo"), 185 | fns: nil, 186 | ok: false, 187 | }, { 188 | err: nil, 189 | fns: nil, 190 | ok: false, 191 | }} 192 | 193 | for i, test := range tests { 194 | ok := errors.Match(test.err, test.fns...) 195 | if ok != test.ok { 196 | t.Fatalf("test %d: expected %v got %v", i, test.ok, ok) 197 | } 198 | } 199 | } 200 | 201 | func TestLocation(t *testing.T) { 202 | loc := errors.Location{"foo", 35} 203 | if loc.String() != "foo:35" { 204 | t.Fatalf("expected \"foo:35\" got %q", loc.String) 205 | } 206 | } 207 | 208 | func checkErr(t *testing.T, err, underlying error, msg string, details string, cause error) { 209 | if err == nil { 210 | t.Fatalf("err is nil; want %q", msg) 211 | } 212 | if err.Error() != msg { 213 | t.Fatalf("unexpected message: want %q; got %q", msg, err.Error()) 214 | } 215 | if err, ok := err.(errors.Wrapper); ok { 216 | if err.Underlying() != underlying { 217 | t.Fatalf("unexpected underlying error: want %q; got %v", underlying, err.Underlying()) 218 | } 219 | } else if underlying != nil { 220 | t.Fatalf("no underlying error found; want %q", underlying) 221 | } 222 | if errors.Cause(err) != cause { 223 | t.Fatalf("unexpected cause: want %#v; got %#v", cause, errors.Cause(err)) 224 | } 225 | wantDetails := replaceLocations(details) 226 | if gotDetails := errors.Details(err); gotDetails != wantDetails { 227 | t.Fatalf("unexpected details: want %q; got %q", wantDetails, gotDetails) 228 | } 229 | } 230 | 231 | func replaceLocations(s string) string { 232 | t := "" 233 | for { 234 | i := strings.Index(s, "$") 235 | if i == -1 { 236 | break 237 | } 238 | t += s[0:i] 239 | s = s[i+1:] 240 | i = strings.Index(s, "$") 241 | if i == -1 { 242 | panic("no second $") 243 | } 244 | t += location(s[0:i]).String() 245 | s = s[i+1:] 246 | } 247 | t += s 248 | return t 249 | } 250 | 251 | func location(tag string) errors.Location { 252 | line, ok := tagToLine[tag] 253 | if !ok { 254 | panic(fmt.Errorf("tag %q not found", tag)) 255 | } 256 | return errors.Location{ 257 | File: filename, 258 | Line: line, 259 | } 260 | } 261 | 262 | var tagToLine = make(map[string]int) 263 | var filename string 264 | 265 | func init() { 266 | data, err := ioutil.ReadFile("errors_test.go") 267 | if err != nil { 268 | panic(err) 269 | } 270 | lines := strings.Split(string(data), "\n") 271 | for i, line := range lines { 272 | if j := strings.Index(line, "//err "); j >= 0 { 273 | tagToLine[line[j+len("//err "):]] = i + 1 274 | } 275 | } 276 | _, filename, _, _ = runtime.Caller(0) 277 | } 278 | -------------------------------------------------------------------------------- /Godeps/_workspace/src/github.com/juju/errgo/errors/export_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2014 Canonical Ltd. 2 | // Licensed under the LGPLv3, see LICENCE file for details. 3 | 4 | package errors 5 | 6 | var Match = match 7 | -------------------------------------------------------------------------------- /Godeps/_workspace/src/github.com/juju/errgo/errors_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2014 Canonical Ltd. 2 | // Licensed under the LGPLv3, see LICENCE file for details. 3 | 4 | package errgo_test 5 | 6 | import ( 7 | "fmt" 8 | "io/ioutil" 9 | "runtime" 10 | "strings" 11 | "testing" 12 | 13 | gc "gopkg.in/check.v1" 14 | 15 | "github.com/juju/errgo" 16 | ) 17 | 18 | var ( 19 | _ errgo.Wrapper = (*errgo.Err)(nil) 20 | _ errgo.Locationer = (*errgo.Err)(nil) 21 | _ errgo.Causer = (*errgo.Err)(nil) 22 | ) 23 | 24 | func Test(t *testing.T) { 25 | gc.TestingT(t) 26 | } 27 | 28 | type errorsSuite struct{} 29 | 30 | var _ = gc.Suite(&errorsSuite{}) 31 | 32 | func (*errorsSuite) TestNew(c *gc.C) { 33 | err := errgo.New("foo") //err TestNew 34 | checkErr(c, err, nil, "foo", "[{$TestNew$: foo}]", err) 35 | } 36 | 37 | func (*errorsSuite) TestNewf(c *gc.C) { 38 | err := errgo.Newf("foo %d", 5) //err TestNewf 39 | checkErr(c, err, nil, "foo 5", "[{$TestNewf$: foo 5}]", err) 40 | } 41 | 42 | var someErr = errgo.New("some error") //err varSomeErr 43 | 44 | func annotate1() error { 45 | err := errgo.Notef(someErr, "annotate1") //err annotate1 46 | return err 47 | } 48 | 49 | func annotate2() error { 50 | err := annotate1() 51 | err = errgo.Notef(err, "annotate2") //err annotate2 52 | return err 53 | } 54 | 55 | func (*errorsSuite) TestNoteUsage(c *gc.C) { 56 | err0 := annotate2() 57 | err, ok := err0.(errgo.Wrapper) 58 | c.Assert(ok, gc.Equals, true) 59 | underlying := err.Underlying() 60 | checkErr( 61 | c, err0, underlying, 62 | "annotate2: annotate1: some error", 63 | "[{$annotate2$: annotate2} {$annotate1$: annotate1} {$varSomeErr$: some error}]", 64 | err0) 65 | } 66 | 67 | func (*errorsSuite) TestMask(c *gc.C) { 68 | err0 := errgo.WithCausef(nil, someErr, "foo") //err TestMask#0 69 | err := errgo.Mask(err0) //err TestMask#1 70 | checkErr(c, err, err0, "foo", "[{$TestMask#1$: } {$TestMask#0$: foo}]", err) 71 | 72 | err = errgo.Mask(nil) 73 | c.Assert(err, gc.IsNil) 74 | } 75 | 76 | func (*errorsSuite) TestNotef(c *gc.C) { 77 | err0 := errgo.WithCausef(nil, someErr, "foo") //err TestNotef#0 78 | err := errgo.Notef(err0, "bar") //err TestNotef#1 79 | checkErr(c, err, err0, "bar: foo", "[{$TestNotef#1$: bar} {$TestNotef#0$: foo}]", err) 80 | 81 | err = errgo.Notef(nil, "bar") //err TestNotef#2 82 | checkErr(c, err, nil, "bar", "[{$TestNotef#2$: bar}]", err) 83 | } 84 | 85 | func (*errorsSuite) TestMaskFunc(c *gc.C) { 86 | err0 := errgo.New("zero") 87 | err1 := errgo.New("one") 88 | 89 | allowVals := func(vals ...error) (r []func(error) bool) { 90 | for _, val := range vals { 91 | r = append(r, errgo.Is(val)) 92 | } 93 | return 94 | } 95 | tests := []struct { 96 | err error 97 | allow0 []func(error) bool 98 | allow1 []func(error) bool 99 | cause error 100 | }{{ 101 | err: err0, 102 | allow0: allowVals(err0), 103 | cause: err0, 104 | }, { 105 | err: err1, 106 | allow0: allowVals(err0), 107 | cause: nil, 108 | }, { 109 | err: err0, 110 | allow1: allowVals(err0), 111 | cause: err0, 112 | }, { 113 | err: err0, 114 | allow0: allowVals(err1), 115 | allow1: allowVals(err0), 116 | cause: err0, 117 | }, { 118 | err: err0, 119 | allow0: allowVals(err0, err1), 120 | cause: err0, 121 | }, { 122 | err: err1, 123 | allow0: allowVals(err0, err1), 124 | cause: err1, 125 | }, { 126 | err: err0, 127 | allow1: allowVals(err0, err1), 128 | cause: err0, 129 | }, { 130 | err: err1, 131 | allow1: allowVals(err0, err1), 132 | cause: err1, 133 | }} 134 | for i, test := range tests { 135 | c.Logf("test %d", i) 136 | wrap := errgo.MaskFunc(test.allow0...) 137 | err := wrap(test.err, test.allow1...) 138 | cause := errgo.Cause(err) 139 | wantCause := test.cause 140 | if wantCause == nil { 141 | wantCause = err 142 | } 143 | c.Check(cause, gc.Equals, wantCause) 144 | } 145 | } 146 | 147 | type embed struct { 148 | *errgo.Err 149 | } 150 | 151 | func (*errorsSuite) TestCause(c *gc.C) { 152 | c.Assert(errgo.Cause(someErr), gc.Equals, someErr) 153 | 154 | causeErr := errgo.New("cause error") 155 | underlyingErr := errgo.New("underlying error") //err TestCause#1 156 | err := errgo.WithCausef(underlyingErr, causeErr, "foo %d", 99) //err TestCause#2 157 | c.Assert(errgo.Cause(err), gc.Equals, causeErr) 158 | 159 | checkErr(c, err, underlyingErr, "foo 99: underlying error", "[{$TestCause#2$: foo 99} {$TestCause#1$: underlying error}]", causeErr) 160 | 161 | err = &embed{err.(*errgo.Err)} 162 | c.Assert(errgo.Cause(err), gc.Equals, causeErr) 163 | } 164 | 165 | func (*errorsSuite) TestDetails(c *gc.C) { 166 | c.Assert(errgo.Details(nil), gc.Equals, "[]") 167 | 168 | otherErr := fmt.Errorf("other") 169 | checkErr(c, otherErr, nil, "other", "[{other}]", otherErr) 170 | 171 | err0 := &embed{errgo.New("foo").(*errgo.Err)} //err TestStack#0 172 | checkErr(c, err0, nil, "foo", "[{$TestStack#0$: foo}]", err0) 173 | 174 | err1 := &embed{errgo.Notef(err0, "bar").(*errgo.Err)} //err TestStack#1 175 | checkErr(c, err1, err0, "bar: foo", "[{$TestStack#1$: bar} {$TestStack#0$: foo}]", err1) 176 | 177 | err2 := errgo.Mask(err1) //err TestStack#2 178 | checkErr(c, err2, err1, "bar: foo", "[{$TestStack#2$: } {$TestStack#1$: bar} {$TestStack#0$: foo}]", err2) 179 | } 180 | 181 | func (*errorsSuite) TestMatch(c *gc.C) { 182 | type errTest func(error) bool 183 | allow := func(ss ...string) []func(error) bool { 184 | fns := make([]func(error) bool, len(ss)) 185 | for i, s := range ss { 186 | s := s 187 | fns[i] = func(err error) bool { 188 | return err != nil && err.Error() == s 189 | } 190 | } 191 | return fns 192 | } 193 | tests := []struct { 194 | err error 195 | fns []func(error) bool 196 | ok bool 197 | }{{ 198 | err: errgo.New("foo"), 199 | fns: allow("foo"), 200 | ok: true, 201 | }, { 202 | err: errgo.New("foo"), 203 | fns: allow("bar"), 204 | ok: false, 205 | }, { 206 | err: errgo.New("foo"), 207 | fns: allow("bar", "foo"), 208 | ok: true, 209 | }, { 210 | err: errgo.New("foo"), 211 | fns: nil, 212 | ok: false, 213 | }, { 214 | err: nil, 215 | fns: nil, 216 | ok: false, 217 | }} 218 | 219 | for i, test := range tests { 220 | c.Logf("test %d", i) 221 | c.Assert(errgo.Match(test.err, test.fns...), gc.Equals, test.ok) 222 | } 223 | } 224 | 225 | func (*errorsSuite) TestLocation(c *gc.C) { 226 | loc := errgo.Location{File: "foo", Line: 35} 227 | c.Assert(loc.String(), gc.Equals, "foo:35") 228 | } 229 | 230 | func checkErr(c *gc.C, err, underlying error, msg string, details string, cause error) { 231 | c.Assert(err, gc.NotNil) 232 | c.Assert(err.Error(), gc.Equals, msg) 233 | if err, ok := err.(errgo.Wrapper); ok { 234 | c.Assert(err.Underlying(), gc.Equals, underlying) 235 | } else { 236 | c.Assert(underlying, gc.IsNil) 237 | } 238 | c.Assert(errgo.Cause(err), gc.Equals, cause) 239 | wantDetails := replaceLocations(details) 240 | c.Assert(errgo.Details(err), gc.Equals, wantDetails) 241 | } 242 | 243 | func replaceLocations(s string) string { 244 | t := "" 245 | for { 246 | i := strings.Index(s, "$") 247 | if i == -1 { 248 | break 249 | } 250 | t += s[0:i] 251 | s = s[i+1:] 252 | i = strings.Index(s, "$") 253 | if i == -1 { 254 | panic("no second $") 255 | } 256 | t += location(s[0:i]).String() 257 | s = s[i+1:] 258 | } 259 | t += s 260 | return t 261 | } 262 | 263 | func location(tag string) errgo.Location { 264 | line, ok := tagToLine[tag] 265 | if !ok { 266 | panic(fmt.Errorf("tag %q not found", tag)) 267 | } 268 | return errgo.Location{ 269 | File: filename, 270 | Line: line, 271 | } 272 | } 273 | 274 | var tagToLine = make(map[string]int) 275 | var filename string 276 | 277 | func init() { 278 | data, err := ioutil.ReadFile("errors_test.go") 279 | if err != nil { 280 | panic(err) 281 | } 282 | lines := strings.Split(string(data), "\n") 283 | for i, line := range lines { 284 | if j := strings.Index(line, "//err "); j >= 0 { 285 | tagToLine[line[j+len("//err "):]] = i + 1 286 | } 287 | } 288 | _, filename, _, _ = runtime.Caller(0) 289 | } 290 | -------------------------------------------------------------------------------- /Godeps/_workspace/src/github.com/juju/errgo/export_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2014 Canonical Ltd. 2 | // Licensed under the LGPLv3, see LICENCE file for details. 3 | 4 | package errgo 5 | 6 | var Match = match 7 | -------------------------------------------------------------------------------- /Godeps/_workspace/src/gopkg.in/unrolled/render.v1/.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled Object files, Static and Dynamic libs (Shared Objects) 2 | *.o 3 | *.a 4 | *.so 5 | 6 | # Folders 7 | _obj 8 | _test 9 | 10 | # Architecture specific extensions/prefixes 11 | *.[568vq] 12 | [568vq].out 13 | 14 | *.cgo1.go 15 | *.cgo2.c 16 | _cgo_defun.c 17 | _cgo_gotypes.go 18 | _cgo_export.* 19 | 20 | _testmain.go 21 | 22 | *.exe 23 | *.test 24 | 25 | 26 | *.pem 27 | .DS_Store 28 | -------------------------------------------------------------------------------- /Godeps/_workspace/src/gopkg.in/unrolled/render.v1/LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 Cory Jacobsen 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so, 10 | subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /Godeps/_workspace/src/gopkg.in/unrolled/render.v1/doc.go: -------------------------------------------------------------------------------- 1 | /*Package render is a package that provides functionality for easily rendering JSON, XML, binary data, and HTML templates. 2 | 3 | package main 4 | 5 | import ( 6 | "encoding/xml" 7 | "net/http" 8 | 9 | "github.com/unrolled/render" // or "gopkg.in/unrolled/render.v1" 10 | ) 11 | 12 | type ExampleXml struct { 13 | XMLName xml.Name `xml:"example"` 14 | One string `xml:"one,attr"` 15 | Two string `xml:"two,attr"` 16 | } 17 | 18 | func main() { 19 | r := render.New() 20 | mux := http.NewServeMux() 21 | 22 | mux.HandleFunc("/", func(w http.ResponseWriter, req *http.Request) { 23 | w.Write([]byte("Welcome, visit sub pages now.")) 24 | }) 25 | 26 | mux.HandleFunc("/data", func(w http.ResponseWriter, req *http.Request) { 27 | r.Data(w, http.StatusOK, []byte("Some binary data here.")) 28 | }) 29 | 30 | mux.HandleFunc("/json", func(w http.ResponseWriter, req *http.Request) { 31 | r.JSON(w, http.StatusOK, map[string]string{"hello": "json"}) 32 | }) 33 | 34 | mux.HandleFunc("/xml", func(w http.ResponseWriter, req *http.Request) { 35 | r.XML(w, http.StatusOK, ExampleXml{One: "hello", Two: "xml"}) 36 | }) 37 | 38 | mux.HandleFunc("/html", func(w http.ResponseWriter, req *http.Request) { 39 | // Assumes you have a template in ./templates called "example.tmpl" 40 | // $ mkdir -p templates && echo "

Hello HTML world.

" > templates/example.tmpl 41 | r.HTML(w, http.StatusOK, "example", nil) 42 | }) 43 | 44 | http.ListenAndServe("0.0.0.0:3000", mux) 45 | } 46 | */ 47 | package render 48 | -------------------------------------------------------------------------------- /Godeps/_workspace/src/gopkg.in/unrolled/render.v1/engine.go: -------------------------------------------------------------------------------- 1 | package render 2 | 3 | import ( 4 | "bytes" 5 | "encoding/json" 6 | "encoding/xml" 7 | "html/template" 8 | "net/http" 9 | ) 10 | 11 | // Engine is the generic interface for all responses. 12 | type Engine interface { 13 | Render(http.ResponseWriter, interface{}) error 14 | } 15 | 16 | // Head defines the basic ContentType and Status fields. 17 | type Head struct { 18 | ContentType string 19 | Status int 20 | } 21 | 22 | // XML built-in renderer. 23 | type XML struct { 24 | Head 25 | Indent bool 26 | Prefix []byte 27 | } 28 | 29 | // JSON built-in renderer. 30 | type JSON struct { 31 | Head 32 | Indent bool 33 | Prefix []byte 34 | } 35 | 36 | // HTML built-in renderer. 37 | type HTML struct { 38 | Head 39 | Name string 40 | Templates *template.Template 41 | } 42 | 43 | // Data built-in renderer. 44 | type Data struct { 45 | Head 46 | } 47 | 48 | // Write outputs the header content. 49 | func (h Head) Write(w http.ResponseWriter) { 50 | w.Header().Set(ContentType, h.ContentType) 51 | w.WriteHeader(h.Status) 52 | } 53 | 54 | // Render a data response. 55 | func (d Data) Render(w http.ResponseWriter, v interface{}) error { 56 | c := w.Header().Get(ContentType) 57 | if c != "" { 58 | d.Head.ContentType = c 59 | } 60 | 61 | d.Head.Write(w) 62 | w.Write(v.([]byte)) 63 | return nil 64 | } 65 | 66 | // Render a JSON response. 67 | func (j JSON) Render(w http.ResponseWriter, v interface{}) error { 68 | var result []byte 69 | var err error 70 | 71 | if j.Indent { 72 | result, err = json.MarshalIndent(v, "", " ") 73 | } else { 74 | result, err = json.Marshal(v) 75 | } 76 | if err != nil { 77 | return err 78 | } 79 | 80 | // JSON marshaled fine, write out the result. 81 | j.Head.Write(w) 82 | if len(j.Prefix) > 0 { 83 | w.Write(j.Prefix) 84 | } 85 | w.Write(result) 86 | return nil 87 | } 88 | 89 | // Render an XML response. 90 | func (x XML) Render(w http.ResponseWriter, v interface{}) error { 91 | var result []byte 92 | var err error 93 | 94 | if x.Indent { 95 | result, err = xml.MarshalIndent(v, "", " ") 96 | } else { 97 | result, err = xml.Marshal(v) 98 | } 99 | if err != nil { 100 | return err 101 | } 102 | 103 | // XML marshaled fine, write out the result. 104 | x.Head.Write(w) 105 | if len(x.Prefix) > 0 { 106 | w.Write(x.Prefix) 107 | } 108 | w.Write(result) 109 | return nil 110 | } 111 | 112 | // Render a HTML response. 113 | func (h HTML) Render(w http.ResponseWriter, binding interface{}) error { 114 | out := new(bytes.Buffer) 115 | err := h.Templates.ExecuteTemplate(out, h.Name, binding) 116 | if err != nil { 117 | return err 118 | } 119 | 120 | h.Head.Write(w) 121 | w.Write(out.Bytes()) 122 | return nil 123 | } 124 | -------------------------------------------------------------------------------- /Godeps/_workspace/src/gopkg.in/unrolled/render.v1/engine_integration_test.go: -------------------------------------------------------------------------------- 1 | // +build integration 2 | 3 | package render 4 | 5 | import ( 6 | "html/template" 7 | "net/http" 8 | "net/http/httptest" 9 | "strings" 10 | "testing" 11 | 12 | "github.com/eknkc/amber" 13 | ) 14 | 15 | // go test -tags=integration 16 | 17 | type Amber struct { 18 | Head 19 | Template *template.Template 20 | } 21 | 22 | func (a Amber) Render(w http.ResponseWriter, v interface{}) error { 23 | a.Head.Write(w) 24 | return a.Template.Execute(w, v) 25 | } 26 | 27 | func TestRenderAmberTemplate(t *testing.T) { 28 | dir := "fixtures/amber/" 29 | render := New(Options{}) 30 | 31 | templates, err := amber.CompileDir(dir, amber.DefaultDirOptions, amber.DefaultOptions) 32 | if err != nil { 33 | t.Errorf("Could not compile Amber templates at " + dir) 34 | } 35 | 36 | a := Amber{ 37 | Head: Head{ 38 | ContentType: ContentHTML, 39 | Status: http.StatusOK, 40 | }, 41 | Template: templates["example"], 42 | } 43 | 44 | v := struct { 45 | VarOne string 46 | VarTwo string 47 | }{ 48 | "Contact", 49 | "Content!", 50 | } 51 | 52 | h := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 53 | render.Render(w, a, v) 54 | }) 55 | 56 | res := httptest.NewRecorder() 57 | req, _ := http.NewRequest("GET", "/foo", nil) 58 | h.ServeHTTP(res, req) 59 | 60 | body := res.Body.String() 61 | 62 | checkCompile := strings.Index(body, ` 4 |
5 | 6 |
7 | -------------------------------------------------------------------------------- /angular/app/views/main.html: -------------------------------------------------------------------------------- 1 |
2 | 3 | New Unit 4 | 5 |
6 |
7 | 8 | 9 |
10 |
11 |
12 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /angular/app/views/new-unit.html: -------------------------------------------------------------------------------- 1 |
2 | 4 | 40 |
41 | -------------------------------------------------------------------------------- /angular/app/views/unit.html: -------------------------------------------------------------------------------- 1 |
2 | 4 | 51 |
52 | -------------------------------------------------------------------------------- /angular/bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "fleetui", 3 | "version": "0.0.0", 4 | "dependencies": { 5 | "angular": "~1.2.0", 6 | "json3": "~3.3.1", 7 | "es5-shim": "~3.1.0", 8 | "bootstrap-sass-official": "~3.2.0", 9 | "angular-resource": "~1.2.0", 10 | "angular-cookies": "~1.2.0", 11 | "angular-sanitize": "~1.2.0", 12 | "angular-animate": "~1.2.0", 13 | "angular-touch": "~1.2.0", 14 | "angular-ui-router": "~0.2.0", 15 | "font-awesome": "~4.2.0", 16 | "angular-websocket": "~0.0.5", 17 | "angular-ladda": "~0.1.6", 18 | "animate.css": "~3.2.0", 19 | "angular-plupload": "~0.1.4" 20 | }, 21 | "devDependencies": { 22 | "angular-mocks": "~1.2.0", 23 | "angular-scenario": "~1.2.0" 24 | }, 25 | "appPath": "app" 26 | } 27 | -------------------------------------------------------------------------------- /angular/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "fleetui", 3 | "version": "0.0.0", 4 | "description": "Fleet UI", 5 | "repository": "https://github.com/purpleworks/fleet-ui.git", 6 | "dependencies": { 7 | "grunt": "^0.4.5", 8 | "grunt-autoprefixer": "^0.7.3", 9 | "grunt-concurrent": "^0.5.0", 10 | "grunt-contrib-clean": "^0.5.0", 11 | "grunt-contrib-compass": "^0.7.2", 12 | "grunt-contrib-concat": "^0.4.0", 13 | "grunt-contrib-connect": "^0.7.1", 14 | "grunt-contrib-copy": "^0.5.0", 15 | "grunt-contrib-cssmin": "^0.9.0", 16 | "grunt-contrib-htmlmin": "^0.3.0", 17 | "grunt-contrib-imagemin": "^0.9.2", 18 | "grunt-contrib-jshint": "^0.10.0", 19 | "grunt-contrib-uglify": "^0.4.0", 20 | "grunt-contrib-watch": "^0.6.1", 21 | "grunt-filerev": "^0.2.1", 22 | "grunt-google-cdn": "^0.4.0", 23 | "grunt-newer": "^0.7.0", 24 | "grunt-ng-annotate": "^0.3.0", 25 | "grunt-svgmin": "^0.4.0", 26 | "grunt-usemin": "^2.1.1", 27 | "grunt-wiredep": "^1.7.0", 28 | "jshint-stylish": "^0.2.0", 29 | "load-grunt-tasks": "^0.4.0", 30 | "time-grunt": "^0.3.1", 31 | "grunt-connect-proxy": "0.1.11", 32 | "grunt-ng-constant": "1.0.0" 33 | }, 34 | "devDependencies": { 35 | "grunt-karma": "^0.9.0", 36 | "karma": "^0.12.24", 37 | "karma-jasmine": "^0.1.5", 38 | "karma-phantomjs-launcher": "^0.1.4" 39 | }, 40 | "engines": { 41 | "node": ">=0.10.0" 42 | }, 43 | "scripts": { 44 | "test": "grunt test" 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /angular/test/.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "node": true, 3 | "browser": true, 4 | "esnext": true, 5 | "bitwise": true, 6 | "camelcase": true, 7 | "curly": true, 8 | "eqeqeq": true, 9 | "immed": true, 10 | "indent": 2, 11 | "latedef": true, 12 | "newcap": true, 13 | "noarg": true, 14 | "quotmark": "single", 15 | "regexp": true, 16 | "undef": true, 17 | "unused": true, 18 | "strict": true, 19 | "trailing": true, 20 | "smarttabs": true, 21 | "globals": { 22 | "after": false, 23 | "afterEach": false, 24 | "angular": false, 25 | "before": false, 26 | "beforeEach": false, 27 | "browser": false, 28 | "describe": false, 29 | "expect": false, 30 | "inject": false, 31 | "it": false, 32 | "jasmine": false, 33 | "spyOn": false 34 | } 35 | } 36 | 37 | -------------------------------------------------------------------------------- /angular/test/karma.conf.js: -------------------------------------------------------------------------------- 1 | // Karma configuration 2 | // http://karma-runner.github.io/0.12/config/configuration-file.html 3 | // Generated on 2014-10-30 using 4 | // generator-karma 0.8.3 5 | 6 | module.exports = function(config) { 7 | 'use strict'; 8 | 9 | config.set({ 10 | // enable / disable watching file and executing tests whenever any file changes 11 | autoWatch: true, 12 | 13 | // base path, that will be used to resolve files and exclude 14 | basePath: '../', 15 | 16 | // testing framework to use (jasmine/mocha/qunit/...) 17 | frameworks: ['jasmine'], 18 | 19 | // list of files / patterns to load in the browser 20 | files: [ 21 | 'bower_components/angular/angular.js', 22 | 'bower_components/angular-mocks/angular-mocks.js', 23 | 'bower_components/angular-animate/angular-animate.js', 24 | 'bower_components/angular-cookies/angular-cookies.js', 25 | 'bower_components/angular-resource/angular-resource.js', 26 | 'bower_components/angular-sanitize/angular-sanitize.js', 27 | 'bower_components/angular-touch/angular-touch.js', 28 | 'app/scripts/**/*.js', 29 | 'test/mock/**/*.js', 30 | 'test/spec/**/*.js' 31 | ], 32 | 33 | // list of files / patterns to exclude 34 | exclude: [], 35 | 36 | // web server port 37 | port: 8080, 38 | 39 | // Start these browsers, currently available: 40 | // - Chrome 41 | // - ChromeCanary 42 | // - Firefox 43 | // - Opera 44 | // - Safari (only Mac) 45 | // - PhantomJS 46 | // - IE (only Windows) 47 | browsers: [ 48 | 'PhantomJS' 49 | ], 50 | 51 | // Which plugins to enable 52 | plugins: [ 53 | 'karma-phantomjs-launcher', 54 | 'karma-jasmine' 55 | ], 56 | 57 | // Continuous Integration mode 58 | // if true, it capture browsers, run tests and exit 59 | singleRun: false, 60 | 61 | colors: true, 62 | 63 | // level of logging 64 | // possible values: LOG_DISABLE || LOG_ERROR || LOG_WARN || LOG_INFO || LOG_DEBUG 65 | logLevel: config.LOG_INFO, 66 | 67 | // Uncomment the following lines if you are using grunt's server to run the tests 68 | // proxies: { 69 | // '/': 'http://localhost:9000/' 70 | // }, 71 | // URL root prevent conflicts with the site root 72 | // urlRoot: '_karma_' 73 | }); 74 | }; 75 | -------------------------------------------------------------------------------- /angular/test/spec/controllers/main.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | describe('Controller: MainCtrl', function () { 4 | 5 | // load the controller's module 6 | beforeEach(module('fleetuiApp')); 7 | 8 | var MainCtrl, 9 | scope; 10 | 11 | // Initialize the controller and a mock scope 12 | beforeEach(inject(function ($controller, $rootScope) { 13 | scope = $rootScope.$new(); 14 | MainCtrl = $controller('MainCtrl', { 15 | $scope: scope 16 | }); 17 | })); 18 | 19 | }); 20 | -------------------------------------------------------------------------------- /angular/test/spec/controllers/new-unit.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | describe('Controller: NewUnitCtrl', function () { 4 | 5 | // load the controller's module 6 | beforeEach(module('fleetuiApp')); 7 | 8 | var NewUnitCtrl, 9 | scope; 10 | 11 | // Initialize the controller and a mock scope 12 | beforeEach(inject(function ($controller, $rootScope) { 13 | scope = $rootScope.$new(); 14 | NewUnitCtrl = $controller('NewUnitCtrl', { 15 | $scope: scope 16 | }); 17 | })); 18 | 19 | it('should attach a list of awesomeThings to the scope', function () { 20 | expect(scope.awesomeThings.length).toBe(3); 21 | }); 22 | }); 23 | -------------------------------------------------------------------------------- /angular/test/spec/controllers/unit.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | describe('Controller: UnitCtrl', function () { 4 | 5 | // load the controller's module 6 | beforeEach(module('fleetuiApp')); 7 | 8 | var UnitCtrl, 9 | scope; 10 | 11 | // Initialize the controller and a mock scope 12 | beforeEach(inject(function ($controller, $rootScope) { 13 | scope = $rootScope.$new(); 14 | UnitCtrl = $controller('UnitCtrl', { 15 | $scope: scope 16 | }); 17 | })); 18 | 19 | it('should attach a list of awesomeThings to the scope', function () { 20 | expect(scope.awesomeThings.length).toBe(3); 21 | }); 22 | }); 23 | -------------------------------------------------------------------------------- /angular/test/spec/filters/service-name.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | describe('Filter: serviceName', function () { 4 | 5 | // load the filter's module 6 | beforeEach(module('fleetuiApp')); 7 | 8 | // initialize a new instance of the filter before each test 9 | var serviceName; 10 | beforeEach(inject(function ($filter) { 11 | serviceName = $filter('serviceName'); 12 | })); 13 | 14 | it('should return the input prefixed with "serviceName filter:"', function () { 15 | var text = 'angularjs'; 16 | expect(serviceName(text)).toBe('serviceName filter: ' + text); 17 | }); 18 | 19 | }); 20 | -------------------------------------------------------------------------------- /angular/test/spec/services/machine-service.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | describe('Service: machineService', function () { 4 | 5 | // load the service's module 6 | beforeEach(module('fleetuiApp')); 7 | 8 | // instantiate service 9 | var machineService; 10 | beforeEach(inject(function (_machineService_) { 11 | machineService = _machineService_; 12 | })); 13 | 14 | it('should do something', function () { 15 | expect(!!machineService).toBe(true); 16 | }); 17 | 18 | }); 19 | -------------------------------------------------------------------------------- /angular/test/spec/services/unit-service.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | describe('Service: unitService', function () { 4 | 5 | // load the service's module 6 | beforeEach(module('fleetuiApp')); 7 | 8 | // instantiate service 9 | var unitService; 10 | beforeEach(inject(function (_unitService_) { 11 | unitService = _unitService_; 12 | })); 13 | 14 | it('should do something', function () { 15 | expect(!!unitService).toBe(true); 16 | }); 17 | 18 | }); 19 | -------------------------------------------------------------------------------- /app.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "flag" 5 | "fmt" 6 | "github.com/codegangsta/negroni" 7 | "github.com/gorilla/mux" 8 | "gopkg.in/unrolled/render.v1" 9 | "os" 10 | ) 11 | 12 | var ( 13 | renderer *render.Render 14 | fleetClient FleetClient 15 | tempDir string 16 | etcdPeer string 17 | etcdPeerFlag = flag.String("etcd-peer", "172.17.42.1", "ETCD_PEER") 18 | bind string 19 | bindFlag = flag.String("bind", "0.0.0.0:3000", "BIND") 20 | ) 21 | 22 | func init() { 23 | // parse command argument 24 | flag.Parse() 25 | if v := os.Getenv("ETCD_PEER"); v != "" { 26 | etcdPeer = v 27 | } else { 28 | etcdPeer = *etcdPeerFlag 29 | } 30 | if v := os.Getenv("BIND"); v != "" { 31 | bind = v 32 | } else { 33 | bind = *bindFlag 34 | } 35 | 36 | // init global variables 37 | renderer = render.New(render.Options{}) 38 | fleetClient = NewClientCLIWithPeer(fmt.Sprintf("%s", etcdPeer)) 39 | tempDir = "./tmp" 40 | if _, err := os.Stat(tempDir); os.IsNotExist(err) { 41 | os.Mkdir(tempDir, 0755) 42 | } 43 | } 44 | 45 | func main() { 46 | r := mux.NewRouter().StrictSlash(false) 47 | 48 | api := r.PathPrefix("/api/v1").Subrouter() 49 | 50 | // routing machines collection 51 | machines := api.Path("/machines").Subrouter() 52 | machines.Methods("GET").HandlerFunc(machineAllHandler) 53 | 54 | // routing units collection 55 | units := api.Path("/units").Subrouter() 56 | units.Methods("GET").HandlerFunc(statusAllHandler) 57 | units.Methods("POST").HandlerFunc(submitUnitHandler) 58 | api.Path("/units/upload").Methods("POST").HandlerFunc(uploadUnitHandler) 59 | 60 | // routing units singular 61 | unit := api.PathPrefix("/units/{id}").Subrouter() 62 | unit.Methods("GET").HandlerFunc(statusHandler) 63 | unit.Methods("DELETE").HandlerFunc(destroyHandler) 64 | unit.Path("/start").Methods("POST").HandlerFunc(startHandler) 65 | unit.Path("/stop").Methods("POST").HandlerFunc(stopHandler) 66 | unit.Path("/load").Methods("POST").HandlerFunc(loadHandler) 67 | 68 | // routing websocket 69 | r.Path("/ws/journal/{id}").HandlerFunc(wsHandler) 70 | 71 | n := negroni.Classic() 72 | n.UseHandler(r) 73 | 74 | n.Run(bind) 75 | } 76 | -------------------------------------------------------------------------------- /build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | FLEET_VERSION=0.10.2 4 | DOCKER_IMAGE_VERSION=${1:-"latest"} 5 | 6 | # echo 7 | echo "FLEET VERSION - "$FLEET_VERSION 8 | echo "BUILD DOCKER IMAGE VERSION - "$DOCKER_IMAGE_VERSION 9 | 10 | # build angular 11 | cd angular 12 | npm install 13 | bower install 14 | grunt build 15 | cd .. 16 | 17 | # build go app 18 | go install 19 | cp $GOPATH/bin/fleet-ui tmp/ 20 | curl -s -L https://github.com/coreos/fleet/releases/download/v${FLEET_VERSION}/fleet-v${FLEET_VERSION}-linux-amd64.tar.gz | \ 21 | tar xz fleet-v${FLEET_VERSION}-linux-amd64/fleetctl -O > tmp/fleetctl 22 | chmod +x tmp/fleetctl 23 | docker build -t purpleworks/fleet-ui:$DOCKER_IMAGE_VERSION . 24 | -------------------------------------------------------------------------------- /fleet_client.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | type UnitStatus struct { 4 | Unit, Load, Active, Sub, Machine string 5 | } 6 | 7 | type MachineStatus struct { 8 | Machine, IPAddress, Metadata string 9 | } 10 | 11 | type Status struct { 12 | Running bool 13 | ContainerIP string 14 | } 15 | 16 | type FleetClient interface { 17 | Submit(name, filePath string) error 18 | Start(name string) error 19 | Stop(name string) error 20 | Load(name string) error 21 | Destroy(name string) error 22 | StatusUnit(name string) (UnitStatus, error) 23 | StatusAll() ([]UnitStatus, error) 24 | JournalF(name string) (chan string, chan string, error) 25 | MachineAll() ([]MachineStatus, error) 26 | } 27 | 28 | func NewClient() FleetClient { 29 | return NewClientCLI() 30 | } 31 | -------------------------------------------------------------------------------- /fleet_client_cli.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "github.com/juju/errgo" 5 | "os/exec" 6 | ) 7 | 8 | const ( 9 | FLEETCTL = "fleetctl" 10 | ENDPOINT_OPTION = "--endpoint" 11 | ENDPOINT_VALUE = "http://172.17.42.1:4001" 12 | ) 13 | 14 | type ClientCLI struct { 15 | etcdPeer string 16 | } 17 | 18 | func NewClientCLI() FleetClient { 19 | return NewClientCLIWithPeer(ENDPOINT_VALUE) 20 | } 21 | 22 | func NewClientCLIWithPeer(etcdPeer string) FleetClient { 23 | return &ClientCLI{ 24 | etcdPeer: etcdPeer, 25 | } 26 | } 27 | 28 | func (this *ClientCLI) Submit(name, filePath string) error { 29 | cmd := exec.Command(FLEETCTL, ENDPOINT_OPTION, this.etcdPeer, "submit", filePath) 30 | _, err := execCmd(cmd) 31 | 32 | if err != nil { 33 | return errgo.Mask(err) 34 | } 35 | 36 | return nil 37 | } 38 | 39 | func (this *ClientCLI) Start(name string) error { 40 | cmd := exec.Command(FLEETCTL, ENDPOINT_OPTION, this.etcdPeer, "start", "--no-block=true", name) 41 | _, err := execCmd(cmd) 42 | 43 | if err != nil { 44 | return errgo.Mask(err) 45 | } 46 | 47 | return nil 48 | } 49 | 50 | func (this *ClientCLI) Stop(name string) error { 51 | cmd := exec.Command(FLEETCTL, ENDPOINT_OPTION, this.etcdPeer, "stop", "--no-block=true", name) 52 | _, err := execCmd(cmd) 53 | 54 | if err != nil { 55 | return errgo.Mask(err) 56 | } 57 | 58 | return nil 59 | } 60 | 61 | func (this *ClientCLI) Load(name string) error { 62 | cmd := exec.Command(FLEETCTL, ENDPOINT_OPTION, this.etcdPeer, "load", "--no-block=true", name) 63 | _, err := execCmd(cmd) 64 | 65 | if err != nil { 66 | return errgo.Mask(err) 67 | } 68 | 69 | return nil 70 | } 71 | 72 | func (this *ClientCLI) Destroy(name string) error { 73 | cmd := exec.Command(FLEETCTL, ENDPOINT_OPTION, this.etcdPeer, "destroy", name) 74 | _, err := execCmd(cmd) 75 | 76 | if err != nil { 77 | return errgo.Mask(err) 78 | } 79 | 80 | return nil 81 | } 82 | -------------------------------------------------------------------------------- /fleet_client_cli_status.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | // Parse for `fleet status` output 4 | import ( 5 | "bufio" 6 | "fmt" 7 | "io" 8 | "os/exec" 9 | "strings" 10 | ) 11 | 12 | func (this *ClientCLI) StatusAll() ([]UnitStatus, error) { 13 | cmd := exec.Command(FLEETCTL, ENDPOINT_OPTION, this.etcdPeer, "list-units", "--full=true", "-l=true", "--fields=unit,load,active,sub,machine") 14 | stdout, err := execCmd(cmd) 15 | if err != nil { 16 | return []UnitStatus{}, err 17 | } 18 | 19 | return parseFleetStatusOutput(stdout) 20 | } 21 | 22 | func parseFleetStatusOutput(output string) ([]UnitStatus, error) { 23 | result := make([]UnitStatus, 0) 24 | 25 | scanner := bufio.NewScanner(strings.NewReader(output)) 26 | // Scan each line of input. 27 | lineCount := 0 28 | for scanner.Scan() { 29 | line := scanner.Text() 30 | lineCount++ 31 | if lineCount == 1 { 32 | continue 33 | } 34 | 35 | words := filterEmpty(strings.Split(line, "\t")) 36 | unitStatus := UnitStatus{ 37 | Unit: words[0], 38 | Load: words[1], 39 | Active: words[2], 40 | Sub: words[3], 41 | Machine: words[4], 42 | } 43 | result = append(result, unitStatus) 44 | } 45 | 46 | if err := scanner.Err(); err != nil { 47 | return result, scanner.Err() 48 | } 49 | return result, nil 50 | } 51 | 52 | func (this *ClientCLI) StatusUnit(name string) (UnitStatus, error) { 53 | status, err := this.StatusAll() 54 | if err != nil { 55 | return UnitStatus{}, err 56 | } 57 | 58 | for _, s := range status { 59 | if s.Unit == name { 60 | return s, nil 61 | } 62 | } 63 | 64 | return UnitStatus{}, NewFleetClientError(ERROR_TYPE_NOT_FOUND, fmt.Sprintf("Cannot fetch status. Unit (%s) not found. Aborting...", name)) 65 | } 66 | 67 | func (this *ClientCLI) JournalF(name string) (outc, errc chan string, err error) { 68 | var stdout, stderr io.ReadCloser 69 | 70 | cmdY := exec.Command("echo", "y") 71 | cmd := exec.Command(FLEETCTL, ENDPOINT_OPTION, this.etcdPeer, "journal", "-f", name) 72 | 73 | cmd.Stdin, _ = cmdY.StdoutPipe() 74 | stdout, err = cmd.StdoutPipe() 75 | if err != nil { 76 | return 77 | } 78 | 79 | stderr, err = cmd.StderrPipe() 80 | if err != nil { 81 | return 82 | } 83 | 84 | if err = cmd.Start(); err != nil { 85 | return 86 | } 87 | 88 | cmdY.Run() 89 | 90 | outc = make(chan string) 91 | errc = make(chan string) 92 | 93 | go func() { 94 | scanner := bufio.NewScanner(stdout) 95 | for scanner.Scan() { 96 | outc <- scanner.Text() 97 | } 98 | }() 99 | 100 | go func() { 101 | scanner := bufio.NewScanner(stderr) 102 | for scanner.Scan() { 103 | errc <- scanner.Text() 104 | } 105 | }() 106 | 107 | return 108 | } 109 | 110 | func (this *ClientCLI) MachineAll() ([]MachineStatus, error) { 111 | cmd := exec.Command(FLEETCTL, ENDPOINT_OPTION, this.etcdPeer, "list-machines", "--full=true") 112 | stdout, err := execCmd(cmd) 113 | if err != nil { 114 | return []MachineStatus{}, err 115 | } 116 | 117 | return parseMachineStatusOutput(stdout) 118 | } 119 | 120 | func parseMachineStatusOutput(output string) ([]MachineStatus, error) { 121 | result := make([]MachineStatus, 0) 122 | 123 | scanner := bufio.NewScanner(strings.NewReader(output)) 124 | // Scan each line of input. 125 | lineCount := 0 126 | for scanner.Scan() { 127 | line := scanner.Text() 128 | lineCount++ 129 | if lineCount == 1 { 130 | continue 131 | } 132 | 133 | words := filterEmpty(strings.Split(line, "\t")) 134 | unitStatus := MachineStatus{ 135 | Machine: words[0], 136 | IPAddress: words[1], 137 | Metadata: words[2], 138 | } 139 | result = append(result, unitStatus) 140 | } 141 | 142 | if err := scanner.Err(); err != nil { 143 | return result, scanner.Err() 144 | } 145 | return result, nil 146 | } 147 | -------------------------------------------------------------------------------- /handler.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "github.com/gorilla/mux" 7 | "github.com/gorilla/websocket" 8 | "io" 9 | "io/ioutil" 10 | "log" 11 | "net/http" 12 | "os" 13 | "strings" 14 | ) 15 | 16 | func destroyHandler(w http.ResponseWriter, req *http.Request) { 17 | key := mux.Vars(req)["id"] 18 | log.Printf("destroy %s unit", key) 19 | if err := fleetClient.Destroy(key); err != nil { 20 | // log.Printf("unit destroy error: %s", err) 21 | // renderer.JSON(w, http.StatusBadRequest, err) 22 | // return 23 | } 24 | renderer.JSON(w, http.StatusOK, map[string]string{"result": "success"}) 25 | } 26 | 27 | func startHandler(w http.ResponseWriter, req *http.Request) { 28 | key := mux.Vars(req)["id"] 29 | log.Printf("start unit %s", key) 30 | if err := fleetClient.Start(key); err != nil { 31 | // log.Printf("unit start error: %s", err) 32 | // renderer.JSON(w, http.StatusBadRequest, err) 33 | // return 34 | } 35 | renderer.JSON(w, http.StatusOK, map[string]string{"result": "success"}) 36 | } 37 | 38 | func stopHandler(w http.ResponseWriter, req *http.Request) { 39 | key := mux.Vars(req)["id"] 40 | log.Printf("stop %s unit", key) 41 | if err := fleetClient.Stop(key); err != nil { 42 | // log.Printf("unit stop error: %s", err) 43 | // renderer.JSON(w, http.StatusBadRequest, err) 44 | // return 45 | } 46 | renderer.JSON(w, http.StatusOK, map[string]string{"result": "success"}) 47 | } 48 | 49 | func loadHandler(w http.ResponseWriter, req *http.Request) { 50 | key := mux.Vars(req)["id"] 51 | log.Printf("load unit - %s", key) 52 | if err := fleetClient.Load(key); err != nil { 53 | // log.Printf("unit load error: %s", err) 54 | // renderer.JSON(w, http.StatusBadRequest, err) 55 | // return 56 | } 57 | renderer.JSON(w, http.StatusOK, map[string]string{"result": "success"}) 58 | } 59 | 60 | func uploadUnitHandler(w http.ResponseWriter, req *http.Request) { 61 | log.Println("upload unit") 62 | if _, err := os.Stat(tempDir); err != nil { 63 | log.Printf("err: %s", err) 64 | } else if os.IsNotExist(err) { 65 | os.Mkdir(tempDir, 0755) 66 | } 67 | 68 | file, header, err := req.FormFile("file") 69 | if err != nil { 70 | log.Printf("read file error : %s", err) 71 | renderer.JSON(w, http.StatusBadRequest, err) 72 | return 73 | } 74 | defer file.Close() 75 | 76 | serviceFile := fmt.Sprintf("%s/%s", tempDir, header.Filename) 77 | out, err := os.Create(serviceFile) 78 | if err != nil { 79 | log.Printf("Open file errpr: %s", err) 80 | renderer.JSON(w, http.StatusBadRequest, err) 81 | return 82 | } 83 | defer out.Close() 84 | 85 | // write the content from POST to the file 86 | _, err = io.Copy(out, file) 87 | if err != nil { 88 | fmt.Fprintln(w, err) 89 | } 90 | 91 | err = fleetClient.Submit(header.Filename, serviceFile) 92 | if err != nil { 93 | // log.Printf("Fleet submit error: %s", err) 94 | // renderer.JSON(w, http.StatusBadRequest, err) 95 | // return 96 | } 97 | if err := fleetClient.Start(header.Filename); err != nil { 98 | // log.Printf("unit start error: %s", err) 99 | // renderer.JSON(w, http.StatusBadRequest, err) 100 | // return 101 | } 102 | renderer.JSON(w, http.StatusOK, map[string]string{"result": "success"}) 103 | 104 | } 105 | 106 | func submitUnitHandler(w http.ResponseWriter, req *http.Request) { 107 | log.Println("submit unit") 108 | type UnitForm struct { 109 | Name string `json:"name"` 110 | Service string `json:"service"` 111 | } 112 | 113 | body, err := ioutil.ReadAll(req.Body) 114 | if err != nil { 115 | log.Printf("read request body error : %s", err) 116 | renderer.JSON(w, http.StatusBadRequest, err) 117 | return 118 | } 119 | 120 | var t UnitForm 121 | dec := json.NewDecoder(strings.NewReader(string(body))) 122 | dec.Decode(&t) 123 | if err != nil { 124 | log.Printf("json decode error : %s", err) 125 | } 126 | 127 | if _, err := os.Stat(tempDir); err != nil { 128 | log.Printf("err: %s", err) 129 | } else if os.IsNotExist(err) { 130 | os.Mkdir(tempDir, 0755) 131 | } 132 | 133 | serviceFile := fmt.Sprintf("%s/%s", tempDir, t.Name) 134 | lines := strings.Split(string(t.Service), "\\n") 135 | 136 | fo, err := os.Create(serviceFile) 137 | if err != nil { 138 | log.Printf("Open file errpr: %s", err) 139 | renderer.JSON(w, http.StatusBadRequest, err) 140 | return 141 | } 142 | 143 | defer func() { 144 | if err := fo.Close(); err != nil { 145 | panic(err) 146 | } 147 | }() 148 | 149 | for _, str := range lines { 150 | fmt.Fprintln(fo, str) 151 | } 152 | 153 | err = fleetClient.Submit(t.Name, serviceFile) 154 | if err != nil { 155 | // log.Printf("Fleet submit error: %s", err) 156 | // renderer.JSON(w, http.StatusBadRequest, err) 157 | // return 158 | } 159 | 160 | if err := fleetClient.Start(t.Name); err != nil { 161 | // log.Printf("unit start error: %s", err) 162 | // renderer.JSON(w, http.StatusBadRequest, err) 163 | // return 164 | } 165 | renderer.JSON(w, http.StatusOK, map[string]string{"result": "success"}) 166 | } 167 | 168 | func machineAllHandler(w http.ResponseWriter, req *http.Request) { 169 | log.Println("machine all") 170 | status, _ := fleetClient.MachineAll() 171 | renderer.JSON(w, http.StatusOK, status) 172 | } 173 | 174 | func statusAllHandler(w http.ResponseWriter, req *http.Request) { 175 | log.Println("unit all") 176 | status, _ := fleetClient.StatusAll() 177 | renderer.JSON(w, http.StatusOK, status) 178 | } 179 | 180 | func statusHandler(w http.ResponseWriter, req *http.Request) { 181 | key := mux.Vars(req)["id"] 182 | log.Printf("unit detail - %s", key) 183 | status, _ := fleetClient.StatusUnit(key) 184 | renderer.JSON(w, http.StatusOK, status) 185 | } 186 | 187 | // websocket handler 188 | 189 | var upgrader = websocket.Upgrader{ 190 | ReadBufferSize: 1024, 191 | WriteBufferSize: 1024, 192 | CheckOrigin: func(r *http.Request) bool { return true }, 193 | } 194 | 195 | func wsHandler(w http.ResponseWriter, r *http.Request) { 196 | conn, err := upgrader.Upgrade(w, r, nil) 197 | if err != nil { 198 | log.Println(err) 199 | return 200 | } 201 | 202 | // write journal message 203 | key := mux.Vars(r)["id"] 204 | stdout, errout, err := fleetClient.JournalF(key) 205 | if err != nil { 206 | conn.WriteMessage(websocket.TextMessage, []byte(fmt.Sprintf("Error: %v", err))) 207 | return 208 | } 209 | go func() { 210 | for line := range stdout { 211 | conn.WriteMessage(websocket.TextMessage, []byte(line)) 212 | } 213 | }() 214 | go func() { 215 | for line := range errout { 216 | conn.WriteMessage(websocket.TextMessage, []byte(fmt.Sprintf("Stderr: %s", line))) 217 | } 218 | }() 219 | } 220 | -------------------------------------------------------------------------------- /images/screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/purpleworks/fleet-ui/08ed2de5d92d894061ef27969932536e4d7e4c39/images/screenshot.png -------------------------------------------------------------------------------- /images/screenshot2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/purpleworks/fleet-ui/08ed2de5d92d894061ef27969932536e4d7e4c39/images/screenshot2.png -------------------------------------------------------------------------------- /images/screenshot3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/purpleworks/fleet-ui/08ed2de5d92d894061ef27969932536e4d7e4c39/images/screenshot3.png -------------------------------------------------------------------------------- /run.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | export ETCD_PEER=${ETCD_PEER:-http://$(/sbin/ip route|awk '/default/ { print $3 }'):4001} 4 | 5 | if [ -f /root/id_rsa ]; then 6 | eval `ssh-agent -s` && ssh-add /root/id_rsa 7 | fi 8 | 9 | /root/fleet-ui/fleet-ui 10 | 11 | -------------------------------------------------------------------------------- /tmp/app.1.service: -------------------------------------------------------------------------------- 1 | [Unit] 2 | Description=My Service 1 3 | Requires=docker.service 4 | After=docker.service 5 | 6 | [Service] 7 | ExecStart=/usr/bin/docker run --rm --name app1 busybox /bin/sh -c "while true; do echo Hello World; sleep 1; done" 8 | ExecStop=/usr/bin/docker kill app1 9 | 10 | [X-Fleet] 11 | X-Conflicts=app.*.service 12 | -------------------------------------------------------------------------------- /tmp/app@1.service: -------------------------------------------------------------------------------- 1 | [Unit] 2 | Description=My Service 1 3 | Requires=docker.service 4 | After=docker.service 5 | 6 | [Service] 7 | ExecStart=/usr/bin/docker run --rm --name app1 busybox /bin/sh -c "while true; do echo Hello World; sleep 1; done" 8 | ExecStop=/usr/bin/docker kill app1 9 | 10 | [X-Fleet] 11 | X-Conflicts=app.*.service 12 | -------------------------------------------------------------------------------- /util.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | "os/exec" 7 | "strings" 8 | ) 9 | 10 | // GetMachineIP parses the unitMachine in format "uuid/ip" and returns only the IP part. 11 | // Can be used with the {UnitStatus.Machine} field. 12 | // Returns an empty string, if no ip was found. 13 | func GetMachineIP(unitMachine string) string { 14 | fields := strings.Split(unitMachine, "/") 15 | if len(fields) < 2 { 16 | return "" 17 | } 18 | return fields[1] 19 | } 20 | 21 | func execCmd(cmd *exec.Cmd) (string, error) { 22 | var ( 23 | stdout bytes.Buffer 24 | stderr bytes.Buffer 25 | ) 26 | 27 | cmd.Stdout = &stdout 28 | cmd.Stderr = &stderr 29 | 30 | err := cmd.Run() 31 | 32 | if result := stdout.String(); result != "" { 33 | return result, nil 34 | } 35 | 36 | if err != nil { 37 | return "", err 38 | } 39 | 40 | if err := stderr.String(); err != "" { 41 | return "", fmt.Errorf(err) 42 | } 43 | 44 | return "", nil 45 | } 46 | 47 | // filterEmpty returns an array containing all non-empty strings of the input array. 48 | // Non-empty as in `strings.TrimSpace(v) != ""`. 49 | func filterEmpty(values []string) []string { 50 | result := make([]string, 0) 51 | for _, v := range values { 52 | if strings.TrimSpace(v) != "" { 53 | result = append(result, v) 54 | } 55 | } 56 | return result 57 | } 58 | 59 | // error handling 60 | 61 | const ( 62 | ERROR_TYPE_NOT_FOUND = 10000 + iota 63 | ) 64 | 65 | type FleetClientError struct { 66 | StatusCode int 67 | StatusText string 68 | } 69 | 70 | func (this FleetClientError) Error() string { 71 | return this.StatusText 72 | } 73 | 74 | func NewFleetClientError(code int, text string) FleetClientError { 75 | return FleetClientError{ 76 | StatusCode: code, 77 | StatusText: text, 78 | } 79 | } 80 | --------------------------------------------------------------------------------