ducky/devices
15:c24a6c5fcd8c Browse Files
Begin implementation on apiv1. Begin implementing the apiv1 package, which will define the first iteration of our API endpoints and logic. Each API package should be self-contained and able to run without depending on each other. Think of them as interfaces into manipulating the business logic defined in the devices package. The point is to have total control over backwards compatibility, as long as our business logic doesn't change. If that happens, we're in a bad place, but not as bad as it could be. This required us to pull in all our API tools; the api package, its dependencies, the scopeTypes package (so we can define scopes for our API), the trout router, etc. We also updated uuid to the latest, which now includes a license. Hooray? The new apiv1 package consists of a few things: * The devices.go file defines the types the API will use to communicate, along with some helpers to convert from API types to devices types. There's also a stub for validating the device creation requests, which I haven't implemented yet because I'm a pretty bad person. * endpoints.go just contains a helper function that builds our routes and assigns handlers to them, giving us an http.Handler in the returns that we can listen with. * handlers.go defines our HTTP handlers, which will read requests and write responses, after doing the appropriate validation and executing the appropriating business logic. Right now, we only have a handler for creating devices, and it doesn't actually do any validation. Also, we have some user-correctable errors being returned as 500s right now, which is Bad. Fortunately, they're all marked with BUG, so I can at least come back to them. * response.go defines the Response type that will be used for returning information after a request is executed. It may eventually get some helpers, but for now it's pretty basic. * scopes.go defines the Scopes that we're going to be using in the package to control access. It should probably (eventually) include a helper to register the Scopes, or we should have a collector service that pulls in all the packages, finds all their Scopes, and registers them. I haven't decided how I want to manage Scope registration just yet. We exported the getStorer function (now GetStorer) so other packages can use it. I'm not sure how I feel about this just yet. We also had to create a WithStorer helper method that embeds the Storer into a context.Context, so we can bootstrap in devicesd. We erroneously had Created in the DeviceChange struct, but there's no reason the Created property of a Device should ever change, so it was removed from the logic, from the struct, and from the tests. Our CreateMany helper was erroneously creating the un-modified Devices that were passed in, instead of the Devices that had sensible defaults filled. We created a _very minimal_ (e.g., needs some work before it's ready for production) devicesd package that will spin up a simple server, just so we could take a peek at our apiv1 endpoints as they'd actually be used. (It worked. Yay?) We should continue to expand on this with configuration, more information being logged, etc.
.hgignore Godeps/Godeps.json apiv1/devices.go apiv1/endpoints.go apiv1/handlers.go apiv1/response.go apiv1/scopes.go context.go devices.go devicesd/server.go storer_test.go vendor/bitbucket.org/ww/goautoneg/Makefile vendor/bitbucket.org/ww/goautoneg/README.txt vendor/bitbucket.org/ww/goautoneg/autoneg.go vendor/code.secondbit.org/api.hg/api.go vendor/code.secondbit.org/scopes.hg/types/scope.go vendor/code.secondbit.org/scopes.hg/types/scope_postgres.go vendor/code.secondbit.org/trout.hg/LICENSE vendor/code.secondbit.org/trout.hg/doc.go vendor/code.secondbit.org/trout.hg/route.go vendor/code.secondbit.org/trout.hg/trie.go vendor/code.secondbit.org/uuid.hg/LICENSE
1.1 --- a/.hgignore Sun Nov 29 21:31:47 2015 -0800 1.2 +++ b/.hgignore Mon Dec 14 00:12:33 2015 -0800 1.3 @@ -1,1 +1,2 @@ 1.4 cover.out 1.5 +devicesd/devicesd
2.1 --- a/Godeps/Godeps.json Sun Nov 29 21:31:47 2015 -0800 2.2 +++ b/Godeps/Godeps.json Mon Dec 14 00:12:33 2015 -0800 2.3 @@ -2,18 +2,38 @@ 2.4 "ImportPath": "code.secondbit.org/ducky/devices.hg", 2.5 "GoVersion": "go1.5.1", 2.6 "Packages": [ 2.7 - "." 2.8 + "./..." 2.9 ], 2.10 "Deps": [ 2.11 { 2.12 + "ImportPath": "bitbucket.org/ww/goautoneg", 2.13 + "Comment": "null-5", 2.14 + "Rev": "'75cd24fc2f2c2a2088577d12123ddee5f54e0675'" 2.15 + }, 2.16 + { 2.17 "ImportPath": "code.google.com/p/go-uuid/uuid", 2.18 "Comment": "null-15", 2.19 "Rev": "'35bc42037350f0078e3c974c6ea690f1926603ab'" 2.20 }, 2.21 { 2.22 + "ImportPath": "code.secondbit.org/api.hg", 2.23 + "Comment": "null-2", 2.24 + "Rev": "'57c9412e80007115060984bcc29fdc24418ff5ce'" 2.25 + }, 2.26 + { 2.27 + "ImportPath": "code.secondbit.org/scopes.hg/types", 2.28 + "Comment": "null-3", 2.29 + "Rev": "'a64a25ae2db1fe949b0ea2f552774bc3544613af'" 2.30 + }, 2.31 + { 2.32 + "ImportPath": "code.secondbit.org/trout.hg", 2.33 + "Comment": "null-3", 2.34 + "Rev": "'b966f38379ddb51f07e8adeb121599e107998409'" 2.35 + }, 2.36 + { 2.37 "ImportPath": "code.secondbit.org/uuid.hg", 2.38 - "Comment": "null-5", 2.39 - "Rev": "'cda03b52c8c99d986df4e7601c7b071bb7bd6333'" 2.40 + "Comment": "null-6", 2.41 + "Rev": "'6b0a9efd13182f3a12bf8f79e01ba19e75a5076e'" 2.42 }, 2.43 { 2.44 "ImportPath": "golang.org/x/net/context",
3.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 3.2 +++ b/apiv1/devices.go Mon Dec 14 00:12:33 2015 -0800 3.3 @@ -0,0 +1,72 @@ 3.4 +package apiv1 3.5 + 3.6 +import ( 3.7 + "time" 3.8 + 3.9 + "code.secondbit.org/ducky/devices.hg" 3.10 + "code.secondbit.org/uuid.hg" 3.11 +) 3.12 + 3.13 +// Device represents a device as exposed through the API. It is its 3.14 +// own type as the business logic and the API have different requirements 3.15 +// for the Device type, and require different representations. 3.16 +type Device struct { 3.17 + ID uuid.ID `json:"id"` 3.18 + Name string `json:"name,omitempty"` 3.19 + Owner uuid.ID `json:"owner,omitempty"` 3.20 + Type devices.DeviceType `json:"type,omitempty"` 3.21 + Created time.Time `json:"created,omitempty"` 3.22 + LastSeen time.Time `json:"lastSeen,omitempty"` 3.23 + PushToken string `json:"pushToken,omitempty"` 3.24 +} 3.25 + 3.26 +// DeviceChange represents a set of changes to a device that will be used 3.27 +// to update that device. It is its own type as the business logic and the 3.28 +// API have different requirements for the DeviceChange type, and require 3.29 +// different representations. 3.30 +type DeviceChange struct { 3.31 + DeviceID uuid.ID `json:"id"` 3.32 + Name *string `json:"name,omitempty"` 3.33 + Owner *uuid.ID `json:"owner,omitempty"` 3.34 + Type *devices.DeviceType `json:"type,omitempty"` 3.35 + LastSeen *time.Time `json:"lastSeen,omitempty"` 3.36 + PushToken *string `json:"pushToken,omitempty"` 3.37 +} 3.38 + 3.39 +func apiDeviceFromCore(d devices.Device, includePushToken bool) Device { 3.40 + device := Device{ 3.41 + ID: d.ID, 3.42 + Name: d.Name, 3.43 + Owner: d.Owner, 3.44 + Type: d.Type, 3.45 + Created: d.Created, 3.46 + LastSeen: d.LastSeen, 3.47 + } 3.48 + if includePushToken { 3.49 + device.PushToken = d.PushToken 3.50 + } 3.51 + return device 3.52 +} 3.53 + 3.54 +func changeFromAPI(d DeviceChange) devices.DeviceChange { 3.55 + return devices.DeviceChange{ 3.56 + DeviceID: d.DeviceID, 3.57 + Name: d.Name, 3.58 + Owner: d.Owner, 3.59 + Type: d.Type, 3.60 + LastSeen: d.LastSeen, 3.61 + PushToken: d.PushToken, 3.62 + } 3.63 +} 3.64 + 3.65 +func createDevicesFromChanges(changes []DeviceChange) []devices.Device { 3.66 + newDevices := make([]devices.Device, 0, len(changes)) 3.67 + for _, change := range changes { 3.68 + newDevices = append(newDevices, devices.ApplyChange(devices.Device{}, changeFromAPI(change))) 3.69 + } 3.70 + return newDevices 3.71 +} 3.72 + 3.73 +func validateDeviceCreation(d devices.Device) error { 3.74 + return nil 3.75 +}
4.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 4.2 +++ b/apiv1/endpoints.go Mon Dec 14 00:12:33 2015 -0800 4.3 @@ -0,0 +1,18 @@ 4.4 +package apiv1 4.5 + 4.6 +import ( 4.7 + "net/http" 4.8 + 4.9 + "code.secondbit.org/api.hg" 4.10 + "code.secondbit.org/trout.hg" 4.11 + "golang.org/x/net/context" 4.12 +) 4.13 + 4.14 +// GetRouter returns a trout Router that is configured to handle 4.15 +// all the routes necessary to serve a devices API server. 4.16 +func GetRouter(ctx context.Context) http.Handler { 4.17 + var router trout.Router 4.18 + router.Endpoint("/").Methods("POST").Handler(api.ContextWrapper(ctx, api.ContextHandler(handleCreateDevices))) 4.19 + 4.20 + return router 4.21 +}
5.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 5.2 +++ b/apiv1/handlers.go Mon Dec 14 00:12:33 2015 -0800 5.3 @@ -0,0 +1,45 @@ 5.4 +package apiv1 5.5 + 5.6 +import ( 5.7 + "log" 5.8 + "net/http" 5.9 + 5.10 + "code.secondbit.org/api.hg" 5.11 + "code.secondbit.org/ducky/devices.hg" 5.12 + 5.13 + "golang.org/x/net/context" 5.14 +) 5.15 + 5.16 +type createRequest struct { 5.17 + Devices []DeviceChange `json:"devices"` 5.18 +} 5.19 + 5.20 +func handleCreateDevices(ctx context.Context, w http.ResponseWriter, r *http.Request) { 5.21 + var req createRequest 5.22 + var resp Response 5.23 + err := api.Decode(r, &req) 5.24 + if err != nil { 5.25 + api.Encode(w, r, http.StatusBadRequest, Response{Errors: api.InvalidFormatError}) 5.26 + return 5.27 + } 5.28 + 5.29 + devicesToCreate := createDevicesFromChanges(req.Devices) 5.30 + for _, device := range devicesToCreate { 5.31 + err := validateDeviceCreation(device) 5.32 + if err != nil { 5.33 + // BUG(paddy): We still need to expose the validation error 5.34 + return 5.35 + } 5.36 + } 5.37 + createdDevices, err := devices.CreateMany(devicesToCreate, ctx) 5.38 + if err != nil { 5.39 + // BUG(paddy): we should filter out non-internal errors here and expose better error responses 5.40 + log.Printf("Error creating devices: %+v\n", err) 5.41 + api.Encode(w, r, http.StatusInternalServerError, Response{Errors: api.ActOfGodError}) 5.42 + return 5.43 + } 5.44 + for _, device := range createdDevices { 5.45 + resp.Devices = append(resp.Devices, apiDeviceFromCore(device, api.CheckScopes(r, ScopeViewPushToken.ID))) 5.46 + } 5.47 + api.Encode(w, r, http.StatusCreated, resp) 5.48 +}
6.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 6.2 +++ b/apiv1/response.go Mon Dec 14 00:12:33 2015 -0800 6.3 @@ -0,0 +1,12 @@ 6.4 +package apiv1 6.5 + 6.6 +import ( 6.7 + "code.secondbit.org/api.hg" 6.8 +) 6.9 + 6.10 +// Response is used to structure the output returned as HTTP responses 6.11 +// to requests. 6.12 +type Response struct { 6.13 + Devices []Device `json:"devices,omitempty"` 6.14 + Errors []api.RequestError `json:"errors,omitempty"` 6.15 +}
7.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 7.2 +++ b/apiv1/scopes.go Mon Dec 14 00:12:33 2015 -0800 7.3 @@ -0,0 +1,13 @@ 7.4 +package apiv1 7.5 + 7.6 +import "code.secondbit.org/scopes.hg/types" 7.7 + 7.8 +var ( 7.9 + // ScopeViewPushToken is a Scope that grants access to viewing pushTokens for 7.10 + // Devices. 7.11 + ScopeViewPushToken = scopeTypes.Scope{ 7.12 + ID: "https://scopes.useducky.com/devices/pushToken/view", 7.13 + Name: "View device push tokens.", 7.14 + Description: "View the push tokens that allow sending messages and notifications to your device. This can be used to force your device to open links, and should be granted with extreme caution.", 7.15 + } 7.16 +)
8.1 --- a/context.go Sun Nov 29 21:31:47 2015 -0800 8.2 +++ b/context.go Mon Dec 14 00:12:33 2015 -0800 8.3 @@ -17,7 +17,11 @@ 8.4 ErrStorerKeyNotStorer = errors.New("the value for storerKey does not fulfill the Storer interface") 8.5 ) 8.6 8.7 -func getStorer(c context.Context) (Storer, error) { 8.8 +// GetStorer returns the Storer associated with the passed Context. 8.9 +// If no Storer is set, ErrNoStorerSet will be returned. 8.10 +// If something that is not a Storer is set using the Storer's key, 8.11 +// ErrStorerKeyNotStorer will be returned. 8.12 +func GetStorer(c context.Context) (Storer, error) { 8.13 val := c.Value(storerKey) 8.14 if val == nil { 8.15 return nil, ErrNoStorerSet 8.16 @@ -28,3 +32,9 @@ 8.17 } 8.18 return storer, nil 8.19 } 8.20 + 8.21 +// WithStorer returns a Context that extends from the passed Context, 8.22 +// but includes or overwrites the Storer key with the passed Storer. 8.23 +func WithStorer(c context.Context, storer Storer) context.Context { 8.24 + return context.WithValue(c, storerKey, storer) 8.25 +}
9.1 --- a/devices.go Sun Nov 29 21:31:47 2015 -0800 9.2 +++ b/devices.go Mon Dec 14 00:12:33 2015 -0800 9.3 @@ -44,9 +44,6 @@ 9.4 if change.Type != nil { 9.5 result.Type = *change.Type 9.6 } 9.7 - if change.Created != nil { 9.8 - result.Created = *change.Created 9.9 - } 9.10 if change.LastSeen != nil { 9.11 result.LastSeen = *change.LastSeen 9.12 } 9.13 @@ -63,7 +60,6 @@ 9.14 Name *string 9.15 Owner *uuid.ID 9.16 Type *DeviceType 9.17 - Created *time.Time 9.18 LastSeen *time.Time 9.19 PushToken *string 9.20 } 9.21 @@ -92,7 +88,7 @@ 9.22 // No error will be returned if a Device can't be found. 9.23 func GetMany(ids []uuid.ID, c context.Context) (map[string]Device, error) { 9.24 results := map[string]Device{} 9.25 - storer, err := getStorer(c) 9.26 + storer, err := GetStorer(c) 9.27 if err != nil { 9.28 log.Printf("Error retrieving Storer: %+v\n", err) 9.29 return results, err 9.30 @@ -122,7 +118,7 @@ 9.31 // Update applies the DeviceChange to the passed Device, and returns the result. If 9.32 // the Device can't be found, an ErrDeviceNotFound error was returned. 9.33 func Update(device Device, change DeviceChange, c context.Context) (Device, error) { 9.34 - storer, err := getStorer(c) 9.35 + storer, err := GetStorer(c) 9.36 if err != nil { 9.37 log.Printf("Error retrieving Storer: %+v\n", err) 9.38 return Device{}, err 9.39 @@ -138,7 +134,7 @@ 9.40 // DeleteMany removes the passed IDs from the datastore. No error is returned if the 9.41 // ID doesn't correspond to a Device in the datastore. 9.42 func DeleteMany(ids []uuid.ID, c context.Context) error { 9.43 - storer, err := getStorer(c) 9.44 + storer, err := GetStorer(c) 9.45 if err != nil { 9.46 log.Printf("Error retrieving Storer: %+v\n", err) 9.47 return err 9.48 @@ -156,7 +152,7 @@ 9.49 // necessary. The Devices that were ultimately stored (including any default values, if 9.50 // applicable) are returned. 9.51 func CreateMany(devices []Device, c context.Context) ([]Device, error) { 9.52 - storer, err := getStorer(c) 9.53 + storer, err := GetStorer(c) 9.54 if err != nil { 9.55 log.Printf("Error retrieving Storer: %+v\n", err) 9.56 return []Device{}, err 9.57 @@ -174,7 +170,7 @@ 9.58 } 9.59 modified = append(modified, device) 9.60 } 9.61 - err = storer.CreateDevices(devices, c) 9.62 + err = storer.CreateDevices(modified, c) 9.63 if err != nil { 9.64 return []Device{}, err 9.65 } 9.66 @@ -201,7 +197,7 @@ 9.67 // returned in. 9.68 func ListByOwner(user uuid.ID, c context.Context) ([]Device, error) { 9.69 // BUG(paddy): Eventually, we'll need to support paging for devices. But right now, I don't foresee any user creating enough of them to make pagination worthwhile. 9.70 - storer, err := getStorer(c) 9.71 + storer, err := GetStorer(c) 9.72 if err != nil { 9.73 log.Printf("Error retrieving Storer: %+v\n", err) 9.74 return []Device{}, err
10.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 10.2 +++ b/devicesd/server.go Mon Dec 14 00:12:33 2015 -0800 10.3 @@ -0,0 +1,22 @@ 10.4 +package main 10.5 + 10.6 +import ( 10.7 + "net/http" 10.8 + 10.9 + "code.secondbit.org/ducky/devices.hg" 10.10 + "code.secondbit.org/ducky/devices.hg/apiv1" 10.11 + "golang.org/x/net/context" 10.12 +) 10.13 + 10.14 +func main() { 10.15 + ctx := devices.WithStorer(context.Background(), devices.NewMemstore()) 10.16 + 10.17 + router := apiv1.GetRouter(ctx) 10.18 + 10.19 + http.Handle("/", router) 10.20 + 10.21 + err := http.ListenAndServe(":9000", nil) 10.22 + if err != nil { 10.23 + panic(err) 10.24 + } 10.25 +}
11.1 --- a/storer_test.go Sun Nov 29 21:31:47 2015 -0800 11.2 +++ b/storer_test.go Mon Dec 14 00:12:33 2015 -0800 11.3 @@ -20,7 +20,6 @@ 11.4 changeName = 1 << iota 11.5 changeOwner 11.6 changeType 11.7 - changeCreated 11.8 changeLastSeen 11.9 changePushToken 11.10 changeVariations 11.11 @@ -251,7 +250,7 @@ 11.12 var change DeviceChange 11.13 var owner uuid.ID 11.14 var name, pushToken string 11.15 - var created, lastSeen time.Time 11.16 + var lastSeen time.Time 11.17 var deviceType DeviceType 11.18 11.19 device.ID = uuid.NewID() 11.20 @@ -276,11 +275,6 @@ 11.21 change.Type = &deviceType 11.22 expectation.Type = deviceType 11.23 } 11.24 - if i&changeCreated != 0 { 11.25 - created = time.Now().Add(time.Hour * time.Duration(i)) 11.26 - change.Created = &created 11.27 - expectation.Created = created 11.28 - } 11.29 if i&changeLastSeen != 0 { 11.30 lastSeen = time.Now().Add(time.Minute * time.Duration(i*-1)) 11.31 change.LastSeen = &lastSeen
12.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 12.2 +++ b/vendor/bitbucket.org/ww/goautoneg/Makefile Mon Dec 14 00:12:33 2015 -0800 12.3 @@ -0,0 +1,13 @@ 12.4 +include $(GOROOT)/src/Make.inc 12.5 + 12.6 +TARG=bitbucket.org/ww/goautoneg 12.7 +GOFILES=autoneg.go 12.8 + 12.9 +include $(GOROOT)/src/Make.pkg 12.10 + 12.11 +format: 12.12 + gofmt -w *.go 12.13 + 12.14 +docs: 12.15 + gomake clean 12.16 + godoc ${TARG} > README.txt
13.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 13.2 +++ b/vendor/bitbucket.org/ww/goautoneg/README.txt Mon Dec 14 00:12:33 2015 -0800 13.3 @@ -0,0 +1,67 @@ 13.4 +PACKAGE 13.5 + 13.6 +package goautoneg 13.7 +import "bitbucket.org/ww/goautoneg" 13.8 + 13.9 +HTTP Content-Type Autonegotiation. 13.10 + 13.11 +The functions in this package implement the behaviour specified in 13.12 +http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html 13.13 + 13.14 +Copyright (c) 2011, Open Knowledge Foundation Ltd. 13.15 +All rights reserved. 13.16 + 13.17 +Redistribution and use in source and binary forms, with or without 13.18 +modification, are permitted provided that the following conditions are 13.19 +met: 13.20 + 13.21 + Redistributions of source code must retain the above copyright 13.22 + notice, this list of conditions and the following disclaimer. 13.23 + 13.24 + Redistributions in binary form must reproduce the above copyright 13.25 + notice, this list of conditions and the following disclaimer in 13.26 + the documentation and/or other materials provided with the 13.27 + distribution. 13.28 + 13.29 + Neither the name of the Open Knowledge Foundation Ltd. nor the 13.30 + names of its contributors may be used to endorse or promote 13.31 + products derived from this software without specific prior written 13.32 + permission. 13.33 + 13.34 +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 13.35 +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 13.36 +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 13.37 +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 13.38 +HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 13.39 +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 13.40 +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 13.41 +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 13.42 +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 13.43 +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 13.44 +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 13.45 + 13.46 + 13.47 +FUNCTIONS 13.48 + 13.49 +func Negotiate(header string, alternatives []string) (content_type string) 13.50 +Negotiate the most appropriate content_type given the accept header 13.51 +and a list of alternatives. 13.52 + 13.53 +func ParseAccept(header string) (accept []Accept) 13.54 +Parse an Accept Header string returning a sorted list 13.55 +of clauses 13.56 + 13.57 + 13.58 +TYPES 13.59 + 13.60 +type Accept struct { 13.61 + Type, SubType string 13.62 + Q float32 13.63 + Params map[string]string 13.64 +} 13.65 +Structure to represent a clause in an HTTP Accept Header 13.66 + 13.67 + 13.68 +SUBDIRECTORIES 13.69 + 13.70 + .hg
14.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 14.2 +++ b/vendor/bitbucket.org/ww/goautoneg/autoneg.go Mon Dec 14 00:12:33 2015 -0800 14.3 @@ -0,0 +1,162 @@ 14.4 +/* 14.5 +HTTP Content-Type Autonegotiation. 14.6 + 14.7 +The functions in this package implement the behaviour specified in 14.8 +http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html 14.9 + 14.10 +Copyright (c) 2011, Open Knowledge Foundation Ltd. 14.11 +All rights reserved. 14.12 + 14.13 +Redistribution and use in source and binary forms, with or without 14.14 +modification, are permitted provided that the following conditions are 14.15 +met: 14.16 + 14.17 + Redistributions of source code must retain the above copyright 14.18 + notice, this list of conditions and the following disclaimer. 14.19 + 14.20 + Redistributions in binary form must reproduce the above copyright 14.21 + notice, this list of conditions and the following disclaimer in 14.22 + the documentation and/or other materials provided with the 14.23 + distribution. 14.24 + 14.25 + Neither the name of the Open Knowledge Foundation Ltd. nor the 14.26 + names of its contributors may be used to endorse or promote 14.27 + products derived from this software without specific prior written 14.28 + permission. 14.29 + 14.30 +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 14.31 +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 14.32 +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 14.33 +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 14.34 +HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 14.35 +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 14.36 +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 14.37 +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 14.38 +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 14.39 +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 14.40 +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 14.41 + 14.42 + 14.43 +*/ 14.44 +package goautoneg 14.45 + 14.46 +import ( 14.47 + "sort" 14.48 + "strconv" 14.49 + "strings" 14.50 +) 14.51 + 14.52 +// Structure to represent a clause in an HTTP Accept Header 14.53 +type Accept struct { 14.54 + Type, SubType string 14.55 + Q float64 14.56 + Params map[string]string 14.57 +} 14.58 + 14.59 +// For internal use, so that we can use the sort interface 14.60 +type accept_slice []Accept 14.61 + 14.62 +func (accept accept_slice) Len() int { 14.63 + slice := []Accept(accept) 14.64 + return len(slice) 14.65 +} 14.66 + 14.67 +func (accept accept_slice) Less(i, j int) bool { 14.68 + slice := []Accept(accept) 14.69 + ai, aj := slice[i], slice[j] 14.70 + if ai.Q > aj.Q { 14.71 + return true 14.72 + } 14.73 + if ai.Type != "*" && aj.Type == "*" { 14.74 + return true 14.75 + } 14.76 + if ai.SubType != "*" && aj.SubType == "*" { 14.77 + return true 14.78 + } 14.79 + return false 14.80 +} 14.81 + 14.82 +func (accept accept_slice) Swap(i, j int) { 14.83 + slice := []Accept(accept) 14.84 + slice[i], slice[j] = slice[j], slice[i] 14.85 +} 14.86 + 14.87 +// Parse an Accept Header string returning a sorted list 14.88 +// of clauses 14.89 +func ParseAccept(header string) (accept []Accept) { 14.90 + parts := strings.Split(header, ",") 14.91 + accept = make([]Accept, 0, len(parts)) 14.92 + for _, part := range parts { 14.93 + part := strings.Trim(part, " ") 14.94 + 14.95 + a := Accept{} 14.96 + a.Params = make(map[string]string) 14.97 + a.Q = 1.0 14.98 + 14.99 + mrp := strings.Split(part, ";") 14.100 + 14.101 + media_range := mrp[0] 14.102 + sp := strings.Split(media_range, "/") 14.103 + a.Type = strings.Trim(sp[0], " ") 14.104 + 14.105 + switch { 14.106 + case len(sp) == 1 && a.Type == "*": 14.107 + a.SubType = "*" 14.108 + case len(sp) == 2: 14.109 + a.SubType = strings.Trim(sp[1], " ") 14.110 + default: 14.111 + continue 14.112 + } 14.113 + 14.114 + if len(mrp) == 1 { 14.115 + accept = append(accept, a) 14.116 + continue 14.117 + } 14.118 + 14.119 + for _, param := range mrp[1:] { 14.120 + sp := strings.SplitN(param, "=", 2) 14.121 + if len(sp) != 2 { 14.122 + continue 14.123 + } 14.124 + token := strings.Trim(sp[0], " ") 14.125 + if token == "q" { 14.126 + a.Q, _ = strconv.ParseFloat(sp[1], 32) 14.127 + } else { 14.128 + a.Params[token] = strings.Trim(sp[1], " ") 14.129 + } 14.130 + } 14.131 + 14.132 + accept = append(accept, a) 14.133 + } 14.134 + 14.135 + slice := accept_slice(accept) 14.136 + sort.Sort(slice) 14.137 + 14.138 + return 14.139 +} 14.140 + 14.141 +// Negotiate the most appropriate content_type given the accept header 14.142 +// and a list of alternatives. 14.143 +func Negotiate(header string, alternatives []string) (content_type string) { 14.144 + asp := make([][]string, 0, len(alternatives)) 14.145 + for _, ctype := range alternatives { 14.146 + asp = append(asp, strings.SplitN(ctype, "/", 2)) 14.147 + } 14.148 + for _, clause := range ParseAccept(header) { 14.149 + for i, ctsp := range asp { 14.150 + if clause.Type == ctsp[0] && clause.SubType == ctsp[1] { 14.151 + content_type = alternatives[i] 14.152 + return 14.153 + } 14.154 + if clause.Type == ctsp[0] && clause.SubType == "*" { 14.155 + content_type = alternatives[i] 14.156 + return 14.157 + } 14.158 + if clause.Type == "*" && clause.SubType == "*" { 14.159 + content_type = alternatives[i] 14.160 + return 14.161 + } 14.162 + } 14.163 + } 14.164 + return 14.165 +}
15.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 15.2 +++ b/vendor/code.secondbit.org/api.hg/api.go Mon Dec 14 00:12:33 2015 -0800 15.3 @@ -0,0 +1,137 @@ 15.4 +package api 15.5 + 15.6 +import ( 15.7 + "encoding/json" 15.8 + "errors" 15.9 + "log" 15.10 + "net/http" 15.11 + "strings" 15.12 + 15.13 + "bitbucket.org/ww/goautoneg" 15.14 + 15.15 + "code.secondbit.org/uuid.hg" 15.16 + 15.17 + "golang.org/x/net/context" 15.18 +) 15.19 + 15.20 +const ( 15.21 + RequestErrAccessDenied = "access_denied" 15.22 + RequestErrInsufficient = "insufficient" 15.23 + RequestErrOverflow = "overflow" 15.24 + RequestErrInvalidValue = "invalid_value" 15.25 + RequestErrInvalidFormat = "invalid_format" 15.26 + RequestErrMissing = "missing" 15.27 + RequestErrNotFound = "not_found" 15.28 + RequestErrConflict = "conflict" 15.29 + RequestErrActOfGod = "act_of_god" 15.30 +) 15.31 + 15.32 +var ( 15.33 + ActOfGodError = []RequestError{{Slug: RequestErrActOfGod}} 15.34 + InvalidFormatError = []RequestError{{Slug: RequestErrInvalidFormat, Field: "/"}} 15.35 + 15.36 + Encoders = []string{"application/json"} 15.37 + 15.38 + ErrUserIDNotSet = errors.New("user ID not set") 15.39 +) 15.40 + 15.41 +type RequestError struct { 15.42 + Slug string `json:"error,omitempty"` 15.43 + Field string `json:"field,omitempty"` 15.44 + Param string `json:"param,omitempty"` 15.45 + Header string `json:"header,omitempty"` 15.46 +} 15.47 + 15.48 +type ContextHandler func(context.Context, http.ResponseWriter, *http.Request) 15.49 + 15.50 +func NegotiateMiddleware(h http.Handler) http.Handler { 15.51 + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 15.52 + if r.Header.Get("Accept") != "" { 15.53 + contentType := goautoneg.Negotiate(r.Header.Get("Accept"), Encoders) 15.54 + if contentType == "" { 15.55 + w.WriteHeader(http.StatusNotAcceptable) 15.56 + w.Write([]byte("Unsupported content type requested: " + r.Header.Get("Accept"))) 15.57 + return 15.58 + } 15.59 + } 15.60 + h.ServeHTTP(w, r) 15.61 + }) 15.62 +} 15.63 + 15.64 +func Encode(w http.ResponseWriter, r *http.Request, status int, resp interface{}) { 15.65 + contentType := goautoneg.Negotiate(r.Header.Get("Accept"), Encoders) 15.66 + w.Header().Set("content-type", contentType) 15.67 + w.WriteHeader(status) 15.68 + var err error 15.69 + switch contentType { 15.70 + case "application/json": 15.71 + enc := json.NewEncoder(w) 15.72 + err = enc.Encode(resp) 15.73 + default: 15.74 + enc := json.NewEncoder(w) 15.75 + err = enc.Encode(resp) 15.76 + } 15.77 + if err != nil { 15.78 + log.Println(err) 15.79 + } 15.80 +} 15.81 + 15.82 +func Decode(r *http.Request, target interface{}) error { 15.83 + defer r.Body.Close() 15.84 + switch r.Header.Get("Content-Type") { 15.85 + case "application/json": 15.86 + dec := json.NewDecoder(r.Body) 15.87 + return dec.Decode(target) 15.88 + default: 15.89 + dec := json.NewDecoder(r.Body) 15.90 + return dec.Decode(target) 15.91 + } 15.92 +} 15.93 + 15.94 +func CORSMiddleware(h http.Handler) http.Handler { 15.95 + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 15.96 + w.Header().Set("Access-Control-Allow-Origin", "*") 15.97 + w.Header().Set("Access-Control-Allow-Headers", r.Header.Get("Access-Control-Request-Headers")) 15.98 + w.Header().Set("Access-Control-Allow-Credentials", "true") 15.99 + if strings.ToLower(r.Method) == "options" { 15.100 + methods := strings.Join(r.Header[http.CanonicalHeaderKey("Trout-Methods")], ", ") 15.101 + w.Header().Set("Access-Control-Allow-Methods", methods) 15.102 + w.Header().Set("Allow", methods) 15.103 + w.WriteHeader(http.StatusOK) 15.104 + return 15.105 + } 15.106 + h.ServeHTTP(w, r) 15.107 + }) 15.108 +} 15.109 + 15.110 +func ContextWrapper(c context.Context, handler ContextHandler) http.Handler { 15.111 + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 15.112 + handler(c, w, r) 15.113 + }) 15.114 +} 15.115 + 15.116 +func CheckScopes(r *http.Request, scopes ...string) bool { 15.117 + passedStr := r.Header.Get("scopes") 15.118 + passed := strings.Split(passedStr, " ") 15.119 + for _, scope := range scopes { 15.120 + var found bool 15.121 + for _, p := range passed { 15.122 + if scope == strings.TrimSpace(p) { 15.123 + found = true 15.124 + break 15.125 + } 15.126 + } 15.127 + if !found { 15.128 + return false 15.129 + } 15.130 + } 15.131 + return true 15.132 +} 15.133 + 15.134 +func AuthUser(r *http.Request) (uuid.ID, error) { 15.135 + rawID := r.Header.Get("User-ID") 15.136 + if rawID == "" { 15.137 + return nil, ErrUserIDNotSet 15.138 + } 15.139 + return uuid.Parse(rawID) 15.140 +}
16.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 16.2 +++ b/vendor/code.secondbit.org/scopes.hg/types/scope.go Mon Dec 14 00:12:33 2015 -0800 16.3 @@ -0,0 +1,60 @@ 16.4 +package scopeTypes 16.5 + 16.6 +// Scope represents a limit on the access that a grant provides. 16.7 +type Scope struct { 16.8 + ID string 16.9 + Name string 16.10 + Description string 16.11 +} 16.12 + 16.13 +func ApplyChange(change ScopeChange, scope Scope) Scope { 16.14 + changed := scope 16.15 + if change.Name != nil { 16.16 + changed.Name = *change.Name 16.17 + } 16.18 + if change.Description != nil { 16.19 + changed.Description = *change.Description 16.20 + } 16.21 + return changed 16.22 +} 16.23 + 16.24 +type Scopes []Scope 16.25 + 16.26 +func (s Scopes) Len() int { 16.27 + return len(s) 16.28 +} 16.29 + 16.30 +func (s Scopes) Swap(i, j int) { 16.31 + s[i], s[j] = s[j], s[i] 16.32 +} 16.33 + 16.34 +func (s Scopes) Less(i, j int) bool { 16.35 + return s[i].ID < s[j].ID 16.36 +} 16.37 + 16.38 +func (s Scopes) Strings() []string { 16.39 + res := make([]string, len(s)) 16.40 + for pos, scope := range s { 16.41 + res[pos] = scope.ID 16.42 + } 16.43 + return res 16.44 +} 16.45 + 16.46 +// ScopeChange represents a change to a Scope. 16.47 +type ScopeChange struct { 16.48 + ScopeID string 16.49 + Name *string 16.50 + Description *string 16.51 +} 16.52 + 16.53 +func (s ScopeChange) Empty() bool { 16.54 + return s.Name == nil && s.Description == nil 16.55 +} 16.56 + 16.57 +func StringsToScopes(s []string) Scopes { 16.58 + res := make(Scopes, len(s)) 16.59 + for pos, scope := range s { 16.60 + res[pos] = Scope{ID: scope} 16.61 + } 16.62 + return res 16.63 +}
17.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 17.2 +++ b/vendor/code.secondbit.org/scopes.hg/types/scope_postgres.go Mon Dec 14 00:12:33 2015 -0800 17.3 @@ -0,0 +1,32 @@ 17.4 +package scopeTypes 17.5 + 17.6 +import ( 17.7 + "database/sql/driver" 17.8 + 17.9 + "code.secondbit.org/pqarrays.hg" 17.10 +) 17.11 + 17.12 +func (s Scope) GetSQLTableName() string { 17.13 + return "scopes" 17.14 +} 17.15 + 17.16 +func (s Scopes) Value() (driver.Value, error) { 17.17 + ids := make(pqarrays.StringArray, 0, len(s)) 17.18 + for _, scope := range s { 17.19 + ids = append(ids, scope.ID) 17.20 + } 17.21 + return ids.Value() 17.22 +} 17.23 + 17.24 +func (s *Scopes) Scan(value interface{}) error { 17.25 + *s = (*s)[:0] 17.26 + var ids pqarrays.StringArray 17.27 + err := ids.Scan(value) 17.28 + if err != nil { 17.29 + return err 17.30 + } 17.31 + for _, id := range ids { 17.32 + *s = append(*s, Scope{ID: id}) 17.33 + } 17.34 + return nil 17.35 +}
18.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 18.2 +++ b/vendor/code.secondbit.org/trout.hg/LICENSE Mon Dec 14 00:12:33 2015 -0800 18.3 @@ -0,0 +1,10 @@ 18.4 +Copyright (c) 2015, Second Bit, LLC 18.5 + 18.6 +All rights reserved. 18.7 + 18.8 +Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 18.9 + 18.10 +Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 18.11 +Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 18.12 +Neither the name of the Second Bit, LLC nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. 18.13 +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
19.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 19.2 +++ b/vendor/code.secondbit.org/trout.hg/doc.go Mon Dec 14 00:12:33 2015 -0800 19.3 @@ -0,0 +1,66 @@ 19.4 +/* 19.5 +Package trout provides an opinionated router that's implemented 19.6 +using a basic trie. 19.7 + 19.8 +The router is opinionated and biased towards basic RESTful 19.9 +services. Its main constraint is that its URL templating is very 19.10 +basic and has no support for regular expressions, prefix matching, 19.11 +or anything other than a direct equality comparison, unlike many 19.12 +routing libraries. 19.13 + 19.14 +The router is specifically designed to support users that want to 19.15 +return correct information with HEAD requests, so it enables users 19.16 +to retrieve a list of HTTP methods an Endpoint is configured to 19.17 +respond to. It will not return the configurations an Endpoint is 19.18 +implicitly configured to respond to by associated a Handler with the 19.19 +Endpoint itself. These HTTP methods can be accessed through the 19.20 +Trout-Methods header that is injected into the http.Request object. 19.21 +Each method will be its own string in the slice. 19.22 + 19.23 +The router is also specifically designed to differentiate between a 19.24 +404 (Not Found) response and a 405 (Method Not Allowed) response. It 19.25 +will use the configured Handle404 http.Handler when no Endpoint is found 19.26 +that matches the http.Request's Path property. It will use the 19.27 +configured Handle405 http.Handler when an Endpoint is found for the 19.28 +http.Request's Path, but the http.Request's Method has no Handler 19.29 +associated with it. Setting a default http.Handler for the Endpoint will 19.30 +result in the Handle405 http.Handler never being used for that Endpoint. 19.31 + 19.32 +To map an Endpoint to a http.Handler: 19.33 + 19.34 + var router trout.Router 19.35 + router.Endpoint("/posts/{slug}/comments/{id}").Handler(postsHandler) 19.36 + 19.37 +All requests that match that URL structure will be passed to the postsHandler, 19.38 +no matter what HTTP method they use. 19.39 + 19.40 +To map specific Methods to a http.Handler: 19.41 + 19.42 + var router trout.Router 19.43 + router.Endpoint("/posts/{slug}").Methods("GET", "POST").Handler(postsHandler) 19.44 + 19.45 +Only requests that match that URL structure will be passed to the postsHandler, 19.46 +and only if they use the GET or POST HTTP method. 19.47 + 19.48 +To access the URL parameter values inside a request, use the RequestVars helper: 19.49 + 19.50 + func handler(w http.ResponseWriter, r *http.Request) { 19.51 + vars := trout.RequestVars(r) 19.52 + ... 19.53 + } 19.54 + 19.55 +This will return an http.Header object containing the parameter values. They are 19.56 +passed into the http.Handler by injecting them into the http.Request's Header property, 19.57 +with the header key of "Trout-Params-{parameter}". The RequestVars helper is just a 19.58 +convenience function to strip the prefix. Parameters are always passed without the curly 19.59 +braces. Finally, if a parameter name is used multiple times in a single URL template, values 19.60 +will be stored in the slice in the order they appeared in the template: 19.61 + 19.62 + // for the template /posts/{id}/comments/{id} 19.63 + // filled with /posts/hello-world/comments/1 19.64 + vars := trout.RequestVars(r) 19.65 + fmt.Println(vars.Get("id")) // outputs `hello-world` 19.66 + fmt.Println(vars[http.CanonicalHeaderKey("id")]) // outputs `["hello-world", "1"]` 19.67 + fmt.Println(vars[http.CanonicalHeaderKey("id"})][1]) // outputs `1` 19.68 +*/ 19.69 +package trout
20.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 20.2 +++ b/vendor/code.secondbit.org/trout.hg/route.go Mon Dec 14 00:12:33 2015 -0800 20.3 @@ -0,0 +1,265 @@ 20.4 +package trout 20.5 + 20.6 +import ( 20.7 + "net/http" 20.8 + "strconv" 20.9 + "strings" 20.10 + "time" 20.11 +) 20.12 + 20.13 +var ( 20.14 + default404Handler = http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 20.15 + w.WriteHeader(http.StatusNotFound) 20.16 + w.Write([]byte("404 Not Found")) 20.17 + return 20.18 + })) 20.19 + default405Handler = http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 20.20 + w.WriteHeader(http.StatusMethodNotAllowed) 20.21 + w.Write([]byte("405 Method Not Allowed")) 20.22 + return 20.23 + })) 20.24 +) 20.25 + 20.26 +// RequestVars returns easy-to-access mappings of parameters to values for URL templates. Any {parameter} in 20.27 +// your URL template will be available in the returned Header as a slice of strings, one for each instance of 20.28 +// the {parameter}. In the case of a parameter name being used more than once in the same URL template, the 20.29 +// values will be in the slice in the order they appeared in the template. 20.30 +// 20.31 +// Values can easily be accessed by using the .Get() method of the returned Header, though to access multiple 20.32 +// values, they must be accessed through the map. All parameters use http.CanonicalHeaderKey for their formatting. 20.33 +// When using .Get(), the parameter name will be transformed automatically. When utilising the Header as a map, 20.34 +// the parameter name needs to have http.CanonicalHeaderKey applied manually. 20.35 +func RequestVars(r *http.Request) http.Header { 20.36 + res := http.Header{} 20.37 + for h, v := range r.Header { 20.38 + stripped := strings.TrimPrefix(h, http.CanonicalHeaderKey("Trout-Param-")) 20.39 + if stripped != h { 20.40 + res[stripped] = v 20.41 + } 20.42 + } 20.43 + return res 20.44 +} 20.45 + 20.46 +// Router defines a set of Endpoints that map requests to the http.Handlers. The http.Handler assigned to 20.47 +// Handle404, if set, will be called when no Endpoint matches the current request. The http.Handler assigned 20.48 +// to Handle405, if set, will be called when an Endpoint matches the current request, but has no http.Handler 20.49 +// set for the HTTP method that the request used. Should either of these properties be unset, a default 20.50 +// http.Handler will be used. 20.51 +// 20.52 +// The Router type is safe for use with empty values, but makes no attempt at concurrency-safety in adding 20.53 +// Endpoints or in setting properties. It should also be noted that the adding Endpoints while simultaneously 20.54 +// routing requests will lead to undefined and (almost certainly) undesirable behaviour. Routers are intended 20.55 +// to be initialised with a set of Endpoints, and then start serving requests. Using them outside of this use 20.56 +// case is unsupported. 20.57 +type Router struct { 20.58 + t *trie 20.59 + Handle404 http.Handler 20.60 + Handle405 http.Handler 20.61 +} 20.62 + 20.63 +func (router *Router) serve404(w http.ResponseWriter, r *http.Request, t time.Time) { 20.64 + h := default404Handler 20.65 + if router.Handle404 != nil { 20.66 + h = router.Handle404 20.67 + } 20.68 + r.Header.Set("Trout-Timer", strconv.FormatInt(time.Now().Sub(t).Nanoseconds(), 10)) 20.69 + h.ServeHTTP(w, r) 20.70 +} 20.71 + 20.72 +func (router *Router) serve405(w http.ResponseWriter, r *http.Request, t time.Time) { 20.73 + h := default405Handler 20.74 + if router.Handle405 != nil { 20.75 + h = router.Handle405 20.76 + } 20.77 + r.Header.Set("Trout-Timer", strconv.FormatInt(time.Now().Sub(t).Nanoseconds(), 10)) 20.78 + h.ServeHTTP(w, r) 20.79 +} 20.80 + 20.81 +func (router Router) ServeHTTP(w http.ResponseWriter, r *http.Request) { 20.82 + start := time.Now() 20.83 + if router.t == nil { 20.84 + router.serve404(w, r, start) 20.85 + return 20.86 + } 20.87 + pieces := strings.Split(strings.ToLower(strings.Trim(r.URL.Path, "/")), "/") 20.88 + router.t.RLock() 20.89 + defer router.t.RUnlock() 20.90 + branches := make([]*branch, len(pieces)) 20.91 + path, ok := router.t.match(pieces) 20.92 + if !ok { 20.93 + router.serve404(w, r, start) 20.94 + return 20.95 + } 20.96 + b := router.t.branch 20.97 + for i, pos := range path { 20.98 + b = b.children[pos] 20.99 + branches[i] = b 20.100 + } 20.101 + v := vars(branches, pieces) 20.102 + for key, vals := range v { 20.103 + r.Header[http.CanonicalHeaderKey("Trout-Param-"+key)] = vals 20.104 + } 20.105 + ms := make([]string, len(b.methods)) 20.106 + i := 0 20.107 + for m := range b.methods { 20.108 + ms[i] = m 20.109 + i = i + 1 20.110 + } 20.111 + r.Header[http.CanonicalHeaderKey("Trout-Methods")] = ms 20.112 + h := b.methods[r.Method] 20.113 + if h == nil { 20.114 + router.serve405(w, r, start) 20.115 + return 20.116 + } 20.117 + r.Header.Set("Trout-Timer", strconv.FormatInt(time.Now().Sub(start).Nanoseconds(), 10)) 20.118 + h.ServeHTTP(w, r) 20.119 +} 20.120 + 20.121 +// Endpoint defines a new Endpoint on the Router. The Endpoint should be a URL template, using curly braces 20.122 +// to denote parameters that should be filled at runtime. For example, `{id}` denotes a parameter named `id` 20.123 +// that should be filled with whatever the request has in that space. 20.124 +// 20.125 +// Parameters are always `/`-separated strings. There is no support for regular expressions or other limitations 20.126 +// on what may be in those strings. A parameter is simply defined as "whatever is between these two / characters". 20.127 +func (router *Router) Endpoint(e string) *Endpoint { 20.128 + e = strings.Trim(e, "/") 20.129 + e = strings.ToLower(e) 20.130 + pieces := strings.Split(e, "/") 20.131 + if router.t == nil { 20.132 + router.t = &trie{} 20.133 + } 20.134 + router.t.Lock() 20.135 + defer router.t.Unlock() 20.136 + if router.t.branch == nil { 20.137 + router.t.branch = &branch{ 20.138 + parent: nil, 20.139 + children: []*branch{}, 20.140 + key: "", 20.141 + isParam: false, 20.142 + methods: map[string]http.Handler{}, 20.143 + } 20.144 + } 20.145 + closest := findClosestLeaf(pieces, router.t.branch) 20.146 + b := router.t.branch 20.147 + for _, pos := range closest { 20.148 + b = b.children[pos] 20.149 + } 20.150 + if len(closest) == len(pieces) { 20.151 + return (*Endpoint)(b) 20.152 + } 20.153 + offset := len(closest) 20.154 + for i := offset; i < len(pieces); i++ { 20.155 + piece := pieces[i] 20.156 + var isParam bool 20.157 + if len(piece) > 0 && piece[0:1] == "{" && piece[len(piece)-1:] == "}" { 20.158 + isParam = true 20.159 + piece = piece[1 : len(piece)-1] 20.160 + } 20.161 + b = b.addChild(piece, isParam) 20.162 + } 20.163 + return (*Endpoint)(b) 20.164 +} 20.165 + 20.166 +func vars(path []*branch, pieces []string) map[string][]string { 20.167 + v := map[string][]string{} 20.168 + for pos, p := range path { 20.169 + if !p.isParam { 20.170 + continue 20.171 + } 20.172 + _, ok := v[p.key] 20.173 + if !ok { 20.174 + v[p.key] = []string{pieces[pos]} 20.175 + continue 20.176 + } 20.177 + v[p.key] = append(v[p.key], pieces[pos]) 20.178 + } 20.179 + return v 20.180 +} 20.181 + 20.182 +func findClosestLeaf(pieces []string, b *branch) []int { 20.183 + offset := 0 20.184 + path := []int{} 20.185 + longest := []int{} 20.186 + num := len(pieces) 20.187 + for i := 0; i < num; i++ { 20.188 + piece := pieces[i] 20.189 + var isParam bool 20.190 + if len(piece) > 0 && piece[0:1] == "{" && piece[len(piece)-1:] == "}" { 20.191 + isParam = true 20.192 + piece = piece[1 : len(piece)-1] 20.193 + } 20.194 + offset = pickNextRoute(b, offset, piece, isParam) 20.195 + if offset == -1 { 20.196 + if len(path) == 0 { 20.197 + // exhausted our options, bail 20.198 + break 20.199 + } 20.200 + // no match, maybe save this and backup 20.201 + if len(path) > len(longest) { 20.202 + longest = append([]int{}, path...) // copy them over so they don't get modified 20.203 + } 20.204 + path, offset = backup(path) 20.205 + offset = offset + 1 20.206 + b = b.parent 20.207 + i = i - 2 20.208 + } else { 20.209 + path = append(path, offset) 20.210 + b = b.children[offset] 20.211 + offset = 0 20.212 + } 20.213 + } 20.214 + if len(longest) < len(path) { 20.215 + longest = append([]int{}, path...) 20.216 + } 20.217 + return longest 20.218 +} 20.219 + 20.220 +func pickNextRoute(b *branch, offset int, input string, variable bool) int { 20.221 + count := len(b.children) 20.222 + for i := offset; i < count; i++ { 20.223 + if b.children[i].key == input && b.isParam == variable { 20.224 + return i 20.225 + } 20.226 + } 20.227 + return -1 20.228 +} 20.229 + 20.230 +// Endpoint defines a single URL template that requests can be matched against. It uses 20.231 +// URL parameters to accept variables in the URL structure and make them available to 20.232 +// the Handlers associated with the Endpoint. 20.233 +type Endpoint branch 20.234 + 20.235 +// Handler associates the passed http.Handler with the Endpoint. This http.Handler will be 20.236 +// used for all requests, regardless of the HTTP method they are using, unless overridden by 20.237 +// the Methods method. Endpoints without a http.Handler associated with them will not be 20.238 +// considered matches for requests, unless the request was made using an HTTP method that the 20.239 +// Endpoint has an http.Handler mapped to. 20.240 +func (e *Endpoint) Handler(h http.Handler) { 20.241 + (*branch)(e).setHandler("", h) 20.242 +} 20.243 + 20.244 +// Methods simple returns a Methods object that will enable the mapping of the passed HTTP 20.245 +// request methods to a Methods object. On its own, this function does not modify anything. It 20.246 +// should, instead, be used as a friendly shorthand to get to the Methods.Handler method. 20.247 +func (e *Endpoint) Methods(m ...string) Methods { 20.248 + return Methods{ 20.249 + e: e, 20.250 + m: m, 20.251 + } 20.252 +} 20.253 + 20.254 +// Methods defines a pairing of an Endpoint to the HTTP request methods that should be mapped to 20.255 +// specific http.Handlers. Its sole purpose is to enable the Methods.Handler method. 20.256 +type Methods struct { 20.257 + e *Endpoint 20.258 + m []string 20.259 +} 20.260 + 20.261 +// Handler maps a Methods object to a specific http.Handler. This overrides the http.Handler 20.262 +// associated with the Endpoint to only handle specific HTTP method(s). 20.263 +func (m Methods) Handler(h http.Handler) { 20.264 + b := (*branch)(m.e) 20.265 + for _, method := range m.m { 20.266 + b.setHandler(method, h) 20.267 + } 20.268 +}
21.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 21.2 +++ b/vendor/code.secondbit.org/trout.hg/trie.go Mon Dec 14 00:12:33 2015 -0800 21.3 @@ -0,0 +1,113 @@ 21.4 +package trout 21.5 + 21.6 +import ( 21.7 + "net/http" 21.8 + "sort" 21.9 + "sync" 21.10 +) 21.11 + 21.12 +type trie struct { 21.13 + branch *branch 21.14 + sync.RWMutex 21.15 +} 21.16 + 21.17 +func (t *trie) match(input []string) ([]int, bool) { 21.18 + if t.branch == nil { 21.19 + t.branch = &branch{} 21.20 + } 21.21 + b := t.branch 21.22 + path := []int{} 21.23 + offset := 0 21.24 + num := len(input) 21.25 + for i := 0; i < num; i++ { 21.26 + // if we're on a nil branch, we're at the end of our line 21.27 + if b == nil { 21.28 + return path, false 21.29 + } 21.30 + offset = pickNextBranch(b, offset, input[i]) 21.31 + if offset == -1 { 21.32 + if len(path) == 0 { 21.33 + // can't find it, bail 21.34 + return path, false 21.35 + } 21.36 + // no match, backup 21.37 + path, offset = backup(path) 21.38 + offset = offset + 1 // we want the next index from the previously matched one 21.39 + b = b.parent // we need to be picking a branch from our parent, again 21.40 + i = i - 2 // back up to the choice before the one that got us here 21.41 + } else { 21.42 + path = append(path, offset) 21.43 + b = b.children[offset] 21.44 + offset = 0 21.45 + } 21.46 + } 21.47 + return path, true 21.48 +} 21.49 + 21.50 +type branch struct { 21.51 + parent *branch 21.52 + children []*branch 21.53 + key string 21.54 + isParam bool 21.55 + methods map[string]http.Handler 21.56 +} 21.57 + 21.58 +func (b *branch) Less(i, j int) bool { 21.59 + if b.children[i].key == b.children[j].key { 21.60 + return b.children[j].isParam && !b.children[i].isParam 21.61 + } 21.62 + return b.children[i].key < b.children[j].key 21.63 +} 21.64 + 21.65 +func (b *branch) Len() int { 21.66 + return len(b.children) 21.67 +} 21.68 + 21.69 +func (b *branch) Swap(i, j int) { 21.70 + b.children[i], b.children[j] = b.children[j], b.children[i] 21.71 +} 21.72 + 21.73 +func (b *branch) addChild(key string, param bool) *branch { 21.74 + child := &branch{key: key, parent: b, isParam: param} 21.75 + if b.children == nil { 21.76 + b.children = []*branch{child} 21.77 + return child 21.78 + } 21.79 + b.children = append(b.children, child) 21.80 + sort.Sort(b) 21.81 + return child 21.82 +} 21.83 + 21.84 +func (b *branch) check(input string) bool { 21.85 + if b.isParam && b.key != "" { 21.86 + return true 21.87 + } 21.88 + if b.key == input { 21.89 + return true 21.90 + } 21.91 + return false 21.92 +} 21.93 + 21.94 +func (b *branch) setHandler(method string, handler http.Handler) { 21.95 + if b.methods == nil { 21.96 + b.methods = map[string]http.Handler{} 21.97 + } 21.98 + b.methods[method] = handler 21.99 +} 21.100 + 21.101 +func pickNextBranch(b *branch, offset int, input string) int { 21.102 + count := len(b.children) 21.103 + for i := offset; i < count; i++ { 21.104 + if b.children[i].check(input) { 21.105 + return i 21.106 + } 21.107 + } 21.108 + return -1 21.109 +} 21.110 + 21.111 +func backup(path []int) ([]int, int) { 21.112 + last := len(path) - 1 21.113 + pos := path[last] 21.114 + path = path[:last] 21.115 + return path, pos 21.116 +}
22.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 22.2 +++ b/vendor/code.secondbit.org/uuid.hg/LICENSE Mon Dec 14 00:12:33 2015 -0800 22.3 @@ -0,0 +1,10 @@ 22.4 +Copyright (c) 2015, Second Bit, LLC 22.5 + 22.6 +All rights reserved. 22.7 + 22.8 +Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 22.9 + 22.10 +Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 22.11 +Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 22.12 +Neither the name of the Second Bit, LLC nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. 22.13 +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.