├── vendor ├── vendor.json ├── github.com │ └── limbo-services │ │ └── proc │ │ ├── http.go │ │ └── runner.go └── golang.org │ └── x │ └── net │ ├── PATENTS │ ├── LICENSE │ └── context │ ├── go17.go │ ├── context.go │ └── pre_go17.go ├── main.go ├── watcher.go ├── discover.go └── handler.go /vendor/vendor.json: -------------------------------------------------------------------------------- 1 | { 2 | "comment": "", 3 | "ignore": "test", 4 | "package": [ 5 | { 6 | "path": "context", 7 | "revision": "" 8 | }, 9 | { 10 | "checksumSHA1": "d/VvfctTYNJS/KLdUx/J+M0SJsU=", 11 | "path": "github.com/limbo-services/proc", 12 | "revision": "9ecb472ed77116c4ff4a1f2f3c74e28349d1c04d", 13 | "revisionTime": "2016-04-04T14:30:38Z" 14 | }, 15 | { 16 | "checksumSHA1": "pancewZW3HwGvpDwfH5Imrbadc4=", 17 | "path": "golang.org/x/net/context", 18 | "revision": "fb93926129b8ec0056f2f458b1f519654814edf0", 19 | "revisionTime": "2016-04-12T22:48:50Z" 20 | } 21 | ], 22 | "rootPath": "github.com/fd/fhdevd" 23 | } 24 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "net/http" 6 | "os" 7 | "syscall" 8 | 9 | "github.com/limbo-services/proc" 10 | "golang.org/x/net/context" 11 | ) 12 | 13 | var bind = <-discover() 14 | 15 | func main() { 16 | fmt.Printf("listening on: http://%s/\n", bind.Host) 17 | 18 | var handler SwappingHandler 19 | 20 | errs := proc.Run(context.Background(), 21 | proc.TerminateOnSignal(os.Interrupt, syscall.SIGTERM), 22 | proc.ServeHTTP(bind.Addr, &http.Server{Handler: &handler}), 23 | NewBuilder(&handler)) 24 | 25 | exitcode := 0 26 | for err := range errs { 27 | if err == context.Canceled { 28 | continue 29 | } 30 | fmt.Fprintf(os.Stderr, "error: %s\n", err) 31 | exitcode = 1 32 | } 33 | os.Exit(exitcode) 34 | } 35 | -------------------------------------------------------------------------------- /vendor/github.com/limbo-services/proc/http.go: -------------------------------------------------------------------------------- 1 | package proc 2 | 3 | import ( 4 | "net" 5 | "net/http" 6 | "strings" 7 | "sync" 8 | 9 | "golang.org/x/net/context" 10 | ) 11 | 12 | func ServeHTTP(addr string, server *http.Server) Runner { 13 | return func(ctx context.Context) <-chan error { 14 | out := make(chan error) 15 | go func() { 16 | defer close(out) 17 | 18 | var wg = &sync.WaitGroup{} 19 | server.Handler = httpWaitGroupHandler(wg, server.Handler) 20 | 21 | l, err := net.Listen("tcp", addr) 22 | if err != nil { 23 | out <- err 24 | return 25 | } 26 | defer l.Close() 27 | 28 | go func() { 29 | <-ctx.Done() 30 | l.Close() 31 | }() 32 | 33 | wg.Add(1) 34 | go func() { 35 | defer wg.Done() 36 | err := server.Serve(l) 37 | if err != nil { 38 | if !strings.Contains(err.Error(), "use of closed network connection") { 39 | out <- err 40 | } 41 | } 42 | }() 43 | 44 | wg.Wait() 45 | }() 46 | return out 47 | } 48 | } 49 | 50 | func httpWaitGroupHandler(wg *sync.WaitGroup, h http.Handler) http.HandlerFunc { 51 | if h == nil { 52 | h = http.DefaultServeMux 53 | } 54 | 55 | return func(w http.ResponseWriter, r *http.Request) { 56 | wg.Add(1) 57 | defer wg.Done() 58 | 59 | h.ServeHTTP(w, r) 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /vendor/golang.org/x/net/PATENTS: -------------------------------------------------------------------------------- 1 | Additional IP Rights Grant (Patents) 2 | 3 | "This implementation" means the copyrightable works distributed by 4 | Google as part of the Go project. 5 | 6 | Google hereby grants to You a perpetual, worldwide, non-exclusive, 7 | no-charge, royalty-free, irrevocable (except as stated in this section) 8 | patent license to make, have made, use, offer to sell, sell, import, 9 | transfer and otherwise run, modify and propagate the contents of this 10 | implementation of Go, where such license applies only to those patent 11 | claims, both currently owned or controlled by Google and acquired in 12 | the future, licensable by Google that are necessarily infringed by this 13 | implementation of Go. This grant does not include claims that would be 14 | infringed only as a consequence of further modification of this 15 | implementation. If you or your agent or exclusive licensee institute or 16 | order or agree to the institution of patent litigation against any 17 | entity (including a cross-claim or counterclaim in a lawsuit) alleging 18 | that this implementation of Go or any code incorporated within this 19 | implementation of Go constitutes direct or contributory patent 20 | infringement, or inducement of patent infringement, then any patent 21 | rights granted to you under this License for this implementation of Go 22 | shall terminate as of the date such litigation is filed. 23 | -------------------------------------------------------------------------------- /vendor/golang.org/x/net/LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2009 The Go 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 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 | -------------------------------------------------------------------------------- /watcher.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | "time" 7 | 8 | "github.com/limbo-services/proc" 9 | "golang.org/x/net/context" 10 | ) 11 | 12 | func NewWatcher(file string) proc.Runner { 13 | return func(ctx context.Context) <-chan error { 14 | out := make(chan error) 15 | go func() { 16 | defer close(out) 17 | 18 | fi, err := os.Stat(file) 19 | 20 | ticker := time.NewTicker(time.Second) 21 | defer ticker.Stop() 22 | 23 | for { 24 | select { 25 | case <-ctx.Done(): 26 | return 27 | case <-ticker.C: 28 | newFi, newErr := os.Stat(file) 29 | if err != nil { 30 | if newErr == nil { 31 | fmt.Printf("[%s]: error disapeared (was: %s)\n", file, err) 32 | return 33 | } 34 | if err.Error() != newErr.Error() { 35 | fmt.Printf("[%s]: error changed (was: %s; became: %s)\n", file, err, newErr) 36 | return 37 | } 38 | } 39 | if fi != nil { 40 | if newFi == nil { 41 | fmt.Printf("[%s]: file info disapeared\n", file) 42 | return 43 | } 44 | if newFi.ModTime().Unix() != fi.ModTime().Unix() { 45 | fmt.Printf("[%s]: mod time changed (was: %d; became: %d)\n", file, fi.ModTime().Unix(), newFi.ModTime().Unix()) 46 | return 47 | } 48 | if newFi.Mode() != fi.Mode() { 49 | fmt.Printf("[%s]: mode changed (was: %s; became: %s)\n", file, fi.Mode(), newFi.Mode()) 50 | return 51 | } 52 | if newFi.Size() != fi.Size() { 53 | fmt.Printf("[%s]: size changed (was: %d; became: %d)\n", file, fi.Size(), newFi.Size()) 54 | return 55 | } 56 | } 57 | 58 | } 59 | } 60 | 61 | }() 62 | return out 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /vendor/github.com/limbo-services/proc/runner.go: -------------------------------------------------------------------------------- 1 | package proc 2 | 3 | import ( 4 | "os" 5 | "os/signal" 6 | "reflect" 7 | 8 | "golang.org/x/net/context" 9 | ) 10 | 11 | type Runner func(ctx context.Context) <-chan error 12 | 13 | func Run(parentCtx context.Context, runners ...Runner) <-chan error { 14 | out := make(chan error) 15 | go func() { 16 | defer close(out) 17 | 18 | var ( 19 | cases = make([]reflect.SelectCase, len(runners)) 20 | pending = len(runners) 21 | ) 22 | 23 | ctx, cancel := context.WithCancel(parentCtx) 24 | defer cancel() 25 | 26 | for i, runner := range runners { 27 | errChan := runner(ctx) 28 | cases[i] = reflect.SelectCase{ 29 | Chan: reflect.ValueOf(errChan), 30 | Dir: reflect.SelectRecv, 31 | } 32 | } 33 | 34 | for pending > 0 { 35 | chosen, recv, recvOK := reflect.Select(cases) 36 | // log.Printf("chosen=%v, recv=%v, recvOK=%v", chosen, recv, recvOK) 37 | 38 | if recv.IsValid() && !recv.IsNil() { 39 | // error received 40 | err, _ := recv.Interface().(error) 41 | if err != nil { 42 | out <- err 43 | } 44 | } 45 | 46 | if !recvOK { 47 | // chanel was closed 48 | cancel() 49 | cases[chosen].Chan = reflect.Value{} 50 | pending-- 51 | } 52 | } 53 | }() 54 | return out 55 | } 56 | 57 | func TerminateOnSignal(signals ...os.Signal) Runner { 58 | return func(ctx context.Context) <-chan error { 59 | out := make(chan error) 60 | go func() { 61 | defer close(out) 62 | 63 | c := make(chan os.Signal) 64 | defer close(c) 65 | 66 | go signal.Notify(c, signals...) 67 | defer signal.Stop(c) 68 | 69 | select { 70 | case <-c: 71 | case <-ctx.Done(): 72 | } 73 | }() 74 | return out 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /discover.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "encoding/json" 5 | "io" 6 | "io/ioutil" 7 | "net/http" 8 | "os" 9 | "time" 10 | ) 11 | 12 | type Bind struct { 13 | Addr string 14 | Host string 15 | } 16 | 17 | func discover() <-chan Bind { 18 | out := make(chan Bind, 1) 19 | go func() { 20 | select { 21 | case b := <-discoverDNSDock(): 22 | out <- b 23 | case b := <-discoverHeroku(): 24 | out <- b 25 | case b := <-discoverLocal(): 26 | out <- b 27 | case b := <-discoverDefault(): 28 | out <- b 29 | } 30 | }() 31 | return out 32 | } 33 | 34 | func discoverDNSDock() <-chan Bind { 35 | out := make(chan Bind, 1) 36 | go func() { 37 | if os.Getenv("USE_DNSDOCK") != "true" { 38 | return 39 | } 40 | 41 | resp, err := http.Get("http://dnsdock.docker/services/" + os.Getenv("HOSTNAME")) 42 | if err != nil { 43 | return 44 | } 45 | 46 | if resp.StatusCode != 200 { 47 | return 48 | } 49 | defer resp.Body.Close() 50 | defer io.Copy(ioutil.Discard, resp.Body) 51 | 52 | var info struct { 53 | Name string 54 | Image string 55 | } 56 | 57 | err = json.NewDecoder(resp.Body).Decode(&info) 58 | if err != nil { 59 | return 60 | } 61 | 62 | out <- Bind{ 63 | Addr: ":80", 64 | Host: info.Name + "." + info.Image + ".docker", 65 | } 66 | }() 67 | return out 68 | } 69 | 70 | func discoverLocal() <-chan Bind { 71 | out := make(chan Bind, 1) 72 | go func() { 73 | if os.Getenv("USE_DNSDOCK") == "true" { 74 | return 75 | } 76 | if os.Getenv("DYNO") != "" { 77 | return 78 | } 79 | var port = os.Getenv("PORT") 80 | if port == "" { 81 | return 82 | } 83 | 84 | out <- Bind{ 85 | Addr: ":" + port, 86 | Host: "localhost:" + port, 87 | } 88 | }() 89 | return out 90 | } 91 | 92 | func discoverHeroku() <-chan Bind { 93 | out := make(chan Bind, 1) 94 | go func() { 95 | if os.Getenv("USE_DNSDOCK") == "true" { 96 | return 97 | } 98 | if os.Getenv("DYNO") == "" { 99 | return 100 | } 101 | var port = os.Getenv("PORT") 102 | if port == "" { 103 | return 104 | } 105 | 106 | out <- Bind{ 107 | Addr: ":" + port, 108 | Host: "0.0.0.0:" + port, 109 | } 110 | }() 111 | return out 112 | } 113 | 114 | func discoverDefault() <-chan Bind { 115 | out := make(chan Bind, 1) 116 | go func() { 117 | time.Sleep(2 * time.Second) 118 | 119 | out <- Bind{ 120 | Addr: ":3080", 121 | Host: "localhost:3080", 122 | } 123 | }() 124 | return out 125 | } 126 | -------------------------------------------------------------------------------- /vendor/golang.org/x/net/context/go17.go: -------------------------------------------------------------------------------- 1 | // Copyright 2016 The Go 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 | // +build go1.7 6 | 7 | package context 8 | 9 | import ( 10 | "context" // standard library's context, as of Go 1.7 11 | "time" 12 | ) 13 | 14 | var ( 15 | todo = context.TODO() 16 | background = context.Background() 17 | ) 18 | 19 | // Canceled is the error returned by Context.Err when the context is canceled. 20 | var Canceled = context.Canceled 21 | 22 | // DeadlineExceeded is the error returned by Context.Err when the context's 23 | // deadline passes. 24 | var DeadlineExceeded = context.DeadlineExceeded 25 | 26 | // WithCancel returns a copy of parent with a new Done channel. The returned 27 | // context's Done channel is closed when the returned cancel function is called 28 | // or when the parent context's Done channel is closed, whichever happens first. 29 | // 30 | // Canceling this context releases resources associated with it, so code should 31 | // call cancel as soon as the operations running in this Context complete. 32 | func WithCancel(parent Context) (ctx Context, cancel CancelFunc) { 33 | ctx, f := context.WithCancel(parent) 34 | return ctx, CancelFunc(f) 35 | } 36 | 37 | // WithDeadline returns a copy of the parent context with the deadline adjusted 38 | // to be no later than d. If the parent's deadline is already earlier than d, 39 | // WithDeadline(parent, d) is semantically equivalent to parent. The returned 40 | // context's Done channel is closed when the deadline expires, when the returned 41 | // cancel function is called, or when the parent context's Done channel is 42 | // closed, whichever happens first. 43 | // 44 | // Canceling this context releases resources associated with it, so code should 45 | // call cancel as soon as the operations running in this Context complete. 46 | func WithDeadline(parent Context, deadline time.Time) (Context, CancelFunc) { 47 | ctx, f := context.WithDeadline(parent, deadline) 48 | return ctx, CancelFunc(f) 49 | } 50 | 51 | // WithTimeout returns WithDeadline(parent, time.Now().Add(timeout)). 52 | // 53 | // Canceling this context releases resources associated with it, so code should 54 | // call cancel as soon as the operations running in this Context complete: 55 | // 56 | // func slowOperationWithTimeout(ctx context.Context) (Result, error) { 57 | // ctx, cancel := context.WithTimeout(ctx, 100*time.Millisecond) 58 | // defer cancel() // releases resources if slowOperation completes before timeout elapses 59 | // return slowOperation(ctx) 60 | // } 61 | func WithTimeout(parent Context, timeout time.Duration) (Context, CancelFunc) { 62 | return WithDeadline(parent, time.Now().Add(timeout)) 63 | } 64 | 65 | // WithValue returns a copy of parent in which the value associated with key is 66 | // val. 67 | // 68 | // Use context Values only for request-scoped data that transits processes and 69 | // APIs, not for passing optional parameters to functions. 70 | func WithValue(parent Context, key interface{}, val interface{}) Context { 71 | return context.WithValue(parent, key, val) 72 | } 73 | -------------------------------------------------------------------------------- /vendor/golang.org/x/net/context/context.go: -------------------------------------------------------------------------------- 1 | // Copyright 2014 The Go 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 defines the Context type, which carries deadlines, 6 | // cancelation signals, and other request-scoped values across API boundaries 7 | // and between processes. 8 | // 9 | // Incoming requests to a server should create a Context, and outgoing calls to 10 | // servers should accept a Context. The chain of function calls between must 11 | // propagate the Context, optionally replacing it with a modified copy created 12 | // using WithDeadline, WithTimeout, WithCancel, or WithValue. 13 | // 14 | // Programs that use Contexts should follow these rules to keep interfaces 15 | // consistent across packages and enable static analysis tools to check context 16 | // propagation: 17 | // 18 | // Do not store Contexts inside a struct type; instead, pass a Context 19 | // explicitly to each function that needs it. The Context should be the first 20 | // parameter, typically named ctx: 21 | // 22 | // func DoSomething(ctx context.Context, arg Arg) error { 23 | // // ... use ctx ... 24 | // } 25 | // 26 | // Do not pass a nil Context, even if a function permits it. Pass context.TODO 27 | // if you are unsure about which Context to use. 28 | // 29 | // Use context Values only for request-scoped data that transits processes and 30 | // APIs, not for passing optional parameters to functions. 31 | // 32 | // The same Context may be passed to functions running in different goroutines; 33 | // Contexts are safe for simultaneous use by multiple goroutines. 34 | // 35 | // See http://blog.golang.org/context for example code for a server that uses 36 | // Contexts. 37 | package context // import "golang.org/x/net/context" 38 | 39 | import "time" 40 | 41 | // A Context carries a deadline, a cancelation signal, and other values across 42 | // API boundaries. 43 | // 44 | // Context's methods may be called by multiple goroutines simultaneously. 45 | type Context interface { 46 | // Deadline returns the time when work done on behalf of this context 47 | // should be canceled. Deadline returns ok==false when no deadline is 48 | // set. Successive calls to Deadline return the same results. 49 | Deadline() (deadline time.Time, ok bool) 50 | 51 | // Done returns a channel that's closed when work done on behalf of this 52 | // context should be canceled. Done may return nil if this context can 53 | // never be canceled. Successive calls to Done return the same value. 54 | // 55 | // WithCancel arranges for Done to be closed when cancel is called; 56 | // WithDeadline arranges for Done to be closed when the deadline 57 | // expires; WithTimeout arranges for Done to be closed when the timeout 58 | // elapses. 59 | // 60 | // Done is provided for use in select statements: 61 | // 62 | // // Stream generates values with DoSomething and sends them to out 63 | // // until DoSomething returns an error or ctx.Done is closed. 64 | // func Stream(ctx context.Context, out <-chan Value) error { 65 | // for { 66 | // v, err := DoSomething(ctx) 67 | // if err != nil { 68 | // return err 69 | // } 70 | // select { 71 | // case <-ctx.Done(): 72 | // return ctx.Err() 73 | // case out <- v: 74 | // } 75 | // } 76 | // } 77 | // 78 | // See http://blog.golang.org/pipelines for more examples of how to use 79 | // a Done channel for cancelation. 80 | Done() <-chan struct{} 81 | 82 | // Err returns a non-nil error value after Done is closed. Err returns 83 | // Canceled if the context was canceled or DeadlineExceeded if the 84 | // context's deadline passed. No other values for Err are defined. 85 | // After Done is closed, successive calls to Err return the same value. 86 | Err() error 87 | 88 | // Value returns the value associated with this context for key, or nil 89 | // if no value is associated with key. Successive calls to Value with 90 | // the same key returns the same result. 91 | // 92 | // Use context values only for request-scoped data that transits 93 | // processes and API boundaries, not for passing optional parameters to 94 | // functions. 95 | // 96 | // A key identifies a specific value in a Context. Functions that wish 97 | // to store values in Context typically allocate a key in a global 98 | // variable then use that key as the argument to context.WithValue and 99 | // Context.Value. A key can be any type that supports equality; 100 | // packages should define keys as an unexported type to avoid 101 | // collisions. 102 | // 103 | // Packages that define a Context key should provide type-safe accessors 104 | // for the values stores using that key: 105 | // 106 | // // Package user defines a User type that's stored in Contexts. 107 | // package user 108 | // 109 | // import "golang.org/x/net/context" 110 | // 111 | // // User is the type of value stored in the Contexts. 112 | // type User struct {...} 113 | // 114 | // // key is an unexported type for keys defined in this package. 115 | // // This prevents collisions with keys defined in other packages. 116 | // type key int 117 | // 118 | // // userKey is the key for user.User values in Contexts. It is 119 | // // unexported; clients use user.NewContext and user.FromContext 120 | // // instead of using this key directly. 121 | // var userKey key = 0 122 | // 123 | // // NewContext returns a new Context that carries value u. 124 | // func NewContext(ctx context.Context, u *User) context.Context { 125 | // return context.WithValue(ctx, userKey, u) 126 | // } 127 | // 128 | // // FromContext returns the User value stored in ctx, if any. 129 | // func FromContext(ctx context.Context) (*User, bool) { 130 | // u, ok := ctx.Value(userKey).(*User) 131 | // return u, ok 132 | // } 133 | Value(key interface{}) interface{} 134 | } 135 | 136 | // Background returns a non-nil, empty Context. It is never canceled, has no 137 | // values, and has no deadline. It is typically used by the main function, 138 | // initialization, and tests, and as the top-level Context for incoming 139 | // requests. 140 | func Background() Context { 141 | return background 142 | } 143 | 144 | // TODO returns a non-nil, empty Context. Code should use context.TODO when 145 | // it's unclear which Context to use or it is not yet available (because the 146 | // surrounding function has not yet been extended to accept a Context 147 | // parameter). TODO is recognized by static analysis tools that determine 148 | // whether Contexts are propagated correctly in a program. 149 | func TODO() Context { 150 | return todo 151 | } 152 | 153 | // A CancelFunc tells an operation to abandon its work. 154 | // A CancelFunc does not wait for the work to stop. 155 | // After the first call, subsequent calls to a CancelFunc do nothing. 156 | type CancelFunc func() 157 | -------------------------------------------------------------------------------- /handler.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "crypto/rand" 5 | "encoding/hex" 6 | "encoding/json" 7 | "fmt" 8 | "io" 9 | "io/ioutil" 10 | "log" 11 | "net/http" 12 | "net/http/httputil" 13 | "net/url" 14 | "os" 15 | "path" 16 | "regexp" 17 | "sort" 18 | "strings" 19 | ) 20 | 21 | func newHandler() (http.Handler, []string) { 22 | mux := http.NewServeMux() 23 | bootHash := randHash() 24 | var watched []string 25 | 26 | mux.Handle("/asset/", assetPrefix(http.FileServer(http.Dir("./assets")))) 27 | mux.Handle("/data/", http.StripPrefix("/data", http.HandlerFunc(dataHandler))) 28 | 29 | mappers := 0 30 | for _, arg := range os.Args[1:] { 31 | if idx := strings.IndexByte(arg, '='); idx >= 0 { 32 | mappers++ 33 | prefix := arg[:idx] 34 | prefix = path.Join("/", prefix) 35 | if !strings.HasSuffix(prefix, "/") { 36 | prefix += "/" 37 | } 38 | 39 | target := arg[idx+1:] 40 | if strings.HasPrefix(target, "http://") || strings.HasPrefix(target, "https://") { 41 | fmt.Printf("forwarding %q to %q\n", prefix, target) 42 | mux.Handle(prefix, http.StripPrefix(strings.TrimRight(prefix, "/"), proxy(target))) 43 | } else { 44 | file := path.Join(".", target) 45 | watched = append(watched, file) 46 | fmt.Printf("mapped %q to %q\n", prefix, file) 47 | mux.Handle(prefix, bootloader(file, bootHash)) 48 | } 49 | } else { 50 | err := os.Chdir(arg) 51 | if err != nil { 52 | panic(err) 53 | } 54 | } 55 | } 56 | if mappers == 0 { 57 | prefix := "/" 58 | file := "index.html" 59 | watched = append(watched, file) 60 | fmt.Printf("mapped %q to %q\n", prefix, file) 61 | mux.Handle(prefix, bootloader(file, bootHash)) 62 | } 63 | 64 | return mux, watched 65 | } 66 | 67 | func bootloader(file, hash string) http.HandlerFunc { 68 | data, err := ioutil.ReadFile(file) 69 | if err != nil { 70 | panic(err) 71 | } 72 | tmpl := parseTmpl(data) 73 | etag := "\"" + hash + "\"" 74 | 75 | return func(w http.ResponseWriter, r *http.Request) { 76 | w.Header().Set("Content-Type", "text/html; charset=utf-8") 77 | w.Header().Set("Cache-Control", "must-revalidate, public") 78 | w.Header().Set("ETag", etag) 79 | 80 | if m := r.Header.Get("If-None-Match"); m != "" { 81 | m = strings.TrimPrefix(m, "W/") 82 | if m == etag { 83 | w.WriteHeader(http.StatusNotModified) 84 | return 85 | } 86 | } 87 | 88 | head := ` 89 | 100 | ` 101 | 102 | _, _, err = tmpl.writeTo(w, []byte(head), []byte("/asset/fhdevd/"+hash+"/")) 103 | if err != nil { 104 | panic(err) 105 | } 106 | } 107 | } 108 | 109 | func proxy(target string) http.Handler { 110 | t, err := url.Parse(target) 111 | if err != nil { 112 | log.Fatalf("invalid url: %q (%s)", target, err) 113 | } 114 | return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 115 | r.RequestURI = r.URL.RequestURI() 116 | r.Host = t.Host 117 | httputil.NewSingleHostReverseProxy(t).ServeHTTP(w, r) 118 | }) 119 | } 120 | 121 | type template struct { 122 | Parts [][]byte 123 | Links []string 124 | } 125 | 126 | var ( 127 | reHEAD = regexp.MustCompile("]*>") 128 | reBASE = regexp.MustCompile("<(?:[^/][^ >]*)(?:\\s(?:[^>\"']+|(?:[\"']([./]*assets/)([^\"']+))|[\"'])+)>") 129 | litHEAD = []byte{0} 130 | litBASE = []byte{1} 131 | ) 132 | 133 | func parseTmpl(data []byte) *template { 134 | var ( 135 | m0 = reHEAD.FindAllIndex(data, -1) 136 | m1 = reBASE.FindAllSubmatchIndex(data, -1) 137 | m = m1 138 | ) 139 | 140 | if len(m0) > 0 { 141 | m = append(m, m0...) 142 | } 143 | 144 | sort.Sort(sortedMatches(m)) 145 | 146 | var ( 147 | parts [][]byte 148 | links []string 149 | offset int 150 | ) 151 | 152 | for _, mi := range m { 153 | switch len(mi) { 154 | case 2: 155 | idx := mi[1] - offset 156 | if idx > 0 { 157 | part := data[:idx] 158 | data = data[idx:] 159 | offset += idx 160 | parts = append(parts, part) 161 | } 162 | parts = append(parts, litHEAD) 163 | case 6: 164 | beg := mi[2] - offset 165 | end := mi[3] - offset 166 | begLink := mi[4] - offset 167 | endLink := mi[5] - offset 168 | 169 | if begLink >= 0 && endLink >= 0 { 170 | link := string(data[begLink:endLink]) 171 | if strings.HasSuffix(link, ".css") || strings.HasSuffix(link, ".js") { 172 | links = append(links, link) 173 | } 174 | } 175 | 176 | if beg > 0 { 177 | part := data[:beg] 178 | parts = append(parts, part) 179 | } 180 | 181 | if beg >= 0 { 182 | parts = append(parts, litBASE) 183 | data = data[end:] 184 | offset += end 185 | } 186 | 187 | default: 188 | panic(fmt.Sprintf("invalid match: %v", mi)) 189 | } 190 | 191 | } 192 | if len(data) > 0 { 193 | parts = append(parts, data) 194 | } 195 | 196 | return &template{Parts: parts, Links: links} 197 | } 198 | 199 | type sortedMatches [][]int 200 | 201 | func (s sortedMatches) Len() int { return len(s) } 202 | func (s sortedMatches) Less(i, j int) bool { return s[i][0] < s[j][0] } 203 | func (s sortedMatches) Swap(i, j int) { s[i], s[j] = s[j], s[i] } 204 | 205 | func (t *template) writeTo(w io.Writer, head, base []byte) (int64, []string, error) { 206 | var n int64 207 | 208 | for _, part := range t.Parts { 209 | var ( 210 | err error 211 | ni int 212 | ) 213 | 214 | switch part[0] { 215 | case 0: 216 | ni, err = w.Write(head) 217 | n += int64(ni) 218 | case 1: 219 | ni, err = w.Write(base) 220 | n += int64(ni) 221 | default: 222 | ni, err = w.Write(part) 223 | n += int64(ni) 224 | } 225 | 226 | if err != nil { 227 | return n, nil, err 228 | } 229 | } 230 | 231 | var links = make([]string, len(t.Links)) 232 | var baseStr = string(base) 233 | for i, path := range t.Links { 234 | links[i] = "<" + baseStr + path + ">; rel=\"prefetch\"; crossorigin" 235 | } 236 | 237 | return n, links, nil 238 | } 239 | 240 | func assetPrefix(h http.Handler) http.HandlerFunc { 241 | return func(w http.ResponseWriter, r *http.Request) { 242 | fname := path.Join("/", r.URL.Path) 243 | if strings.Count(fname, "/") < 3 { 244 | http.NotFound(w, r) 245 | return 246 | } 247 | fname = strings.Join(strings.Split(fname, "/")[4:], "/") 248 | fname = path.Join("/", fname) 249 | r.URL.Path = fname 250 | h.ServeHTTP(w, r) 251 | } 252 | } 253 | 254 | func dataHandler(w http.ResponseWriter, r *http.Request) { 255 | fname := path.Join("/", r.URL.Path) 256 | fname = strings.TrimSuffix(fname, ".json") 257 | if strings.Count(fname, "/") < 2 { 258 | http.NotFound(w, r) 259 | return 260 | } 261 | fname = strings.Join(strings.Split(fname, "/")[3:], "/") 262 | fname = path.Join("./data", fname) 263 | 264 | data, err := ioutil.ReadFile(fname + ".json") 265 | if os.IsNotExist(err) { 266 | data = nil 267 | err = nil 268 | } 269 | if err != nil { 270 | panic(err) 271 | } 272 | 273 | entries, err := ioutil.ReadDir(fname) 274 | if os.IsNotExist(err) { 275 | entries = nil 276 | err = nil 277 | } 278 | if err != nil { 279 | panic(err) 280 | } 281 | 282 | if entries == nil && data == nil { 283 | http.NotFound(w, r) 284 | return 285 | } 286 | 287 | type child struct { 288 | Name string `json:"name"` 289 | } 290 | 291 | var resp struct { 292 | Data json.RawMessage `json:"data,omitempty"` 293 | Children []child `json:"children"` 294 | } 295 | 296 | resp.Data = data 297 | seen := map[string]bool{} 298 | for _, entry := range entries { 299 | ename := entry.Name() 300 | ename = strings.TrimSuffix(ename, ".json") 301 | if !seen[ename] { 302 | seen[ename] = true 303 | resp.Children = append(resp.Children, child{Name: ename}) 304 | } 305 | } 306 | 307 | w.Header().Set("Content-Type", "application/json; charset=utf-8") 308 | w.Header().Set("Cache-Control", "public,max-age=86400") 309 | 310 | err = json.NewEncoder(w).Encode(&resp) 311 | if err != nil { 312 | panic(err) 313 | } 314 | } 315 | 316 | func randHash() string { 317 | var d [20]byte 318 | _, err := io.ReadFull(rand.Reader, d[:]) 319 | if err != nil { 320 | panic(err) 321 | } 322 | return hex.EncodeToString(d[:]) 323 | } 324 | -------------------------------------------------------------------------------- /vendor/golang.org/x/net/context/pre_go17.go: -------------------------------------------------------------------------------- 1 | // Copyright 2014 The Go 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 | // +build !go1.7 6 | 7 | package context 8 | 9 | import ( 10 | "errors" 11 | "fmt" 12 | "sync" 13 | "time" 14 | ) 15 | 16 | // An emptyCtx is never canceled, has no values, and has no deadline. It is not 17 | // struct{}, since vars of this type must have distinct addresses. 18 | type emptyCtx int 19 | 20 | func (*emptyCtx) Deadline() (deadline time.Time, ok bool) { 21 | return 22 | } 23 | 24 | func (*emptyCtx) Done() <-chan struct{} { 25 | return nil 26 | } 27 | 28 | func (*emptyCtx) Err() error { 29 | return nil 30 | } 31 | 32 | func (*emptyCtx) Value(key interface{}) interface{} { 33 | return nil 34 | } 35 | 36 | func (e *emptyCtx) String() string { 37 | switch e { 38 | case background: 39 | return "context.Background" 40 | case todo: 41 | return "context.TODO" 42 | } 43 | return "unknown empty Context" 44 | } 45 | 46 | var ( 47 | background = new(emptyCtx) 48 | todo = new(emptyCtx) 49 | ) 50 | 51 | // Canceled is the error returned by Context.Err when the context is canceled. 52 | var Canceled = errors.New("context canceled") 53 | 54 | // DeadlineExceeded is the error returned by Context.Err when the context's 55 | // deadline passes. 56 | var DeadlineExceeded = errors.New("context deadline exceeded") 57 | 58 | // WithCancel returns a copy of parent with a new Done channel. The returned 59 | // context's Done channel is closed when the returned cancel function is called 60 | // or when the parent context's Done channel is closed, whichever happens first. 61 | // 62 | // Canceling this context releases resources associated with it, so code should 63 | // call cancel as soon as the operations running in this Context complete. 64 | func WithCancel(parent Context) (ctx Context, cancel CancelFunc) { 65 | c := newCancelCtx(parent) 66 | propagateCancel(parent, c) 67 | return c, func() { c.cancel(true, Canceled) } 68 | } 69 | 70 | // newCancelCtx returns an initialized cancelCtx. 71 | func newCancelCtx(parent Context) *cancelCtx { 72 | return &cancelCtx{ 73 | Context: parent, 74 | done: make(chan struct{}), 75 | } 76 | } 77 | 78 | // propagateCancel arranges for child to be canceled when parent is. 79 | func propagateCancel(parent Context, child canceler) { 80 | if parent.Done() == nil { 81 | return // parent is never canceled 82 | } 83 | if p, ok := parentCancelCtx(parent); ok { 84 | p.mu.Lock() 85 | if p.err != nil { 86 | // parent has already been canceled 87 | child.cancel(false, p.err) 88 | } else { 89 | if p.children == nil { 90 | p.children = make(map[canceler]bool) 91 | } 92 | p.children[child] = true 93 | } 94 | p.mu.Unlock() 95 | } else { 96 | go func() { 97 | select { 98 | case <-parent.Done(): 99 | child.cancel(false, parent.Err()) 100 | case <-child.Done(): 101 | } 102 | }() 103 | } 104 | } 105 | 106 | // parentCancelCtx follows a chain of parent references until it finds a 107 | // *cancelCtx. This function understands how each of the concrete types in this 108 | // package represents its parent. 109 | func parentCancelCtx(parent Context) (*cancelCtx, bool) { 110 | for { 111 | switch c := parent.(type) { 112 | case *cancelCtx: 113 | return c, true 114 | case *timerCtx: 115 | return c.cancelCtx, true 116 | case *valueCtx: 117 | parent = c.Context 118 | default: 119 | return nil, false 120 | } 121 | } 122 | } 123 | 124 | // removeChild removes a context from its parent. 125 | func removeChild(parent Context, child canceler) { 126 | p, ok := parentCancelCtx(parent) 127 | if !ok { 128 | return 129 | } 130 | p.mu.Lock() 131 | if p.children != nil { 132 | delete(p.children, child) 133 | } 134 | p.mu.Unlock() 135 | } 136 | 137 | // A canceler is a context type that can be canceled directly. The 138 | // implementations are *cancelCtx and *timerCtx. 139 | type canceler interface { 140 | cancel(removeFromParent bool, err error) 141 | Done() <-chan struct{} 142 | } 143 | 144 | // A cancelCtx can be canceled. When canceled, it also cancels any children 145 | // that implement canceler. 146 | type cancelCtx struct { 147 | Context 148 | 149 | done chan struct{} // closed by the first cancel call. 150 | 151 | mu sync.Mutex 152 | children map[canceler]bool // set to nil by the first cancel call 153 | err error // set to non-nil by the first cancel call 154 | } 155 | 156 | func (c *cancelCtx) Done() <-chan struct{} { 157 | return c.done 158 | } 159 | 160 | func (c *cancelCtx) Err() error { 161 | c.mu.Lock() 162 | defer c.mu.Unlock() 163 | return c.err 164 | } 165 | 166 | func (c *cancelCtx) String() string { 167 | return fmt.Sprintf("%v.WithCancel", c.Context) 168 | } 169 | 170 | // cancel closes c.done, cancels each of c's children, and, if 171 | // removeFromParent is true, removes c from its parent's children. 172 | func (c *cancelCtx) cancel(removeFromParent bool, err error) { 173 | if err == nil { 174 | panic("context: internal error: missing cancel error") 175 | } 176 | c.mu.Lock() 177 | if c.err != nil { 178 | c.mu.Unlock() 179 | return // already canceled 180 | } 181 | c.err = err 182 | close(c.done) 183 | for child := range c.children { 184 | // NOTE: acquiring the child's lock while holding parent's lock. 185 | child.cancel(false, err) 186 | } 187 | c.children = nil 188 | c.mu.Unlock() 189 | 190 | if removeFromParent { 191 | removeChild(c.Context, c) 192 | } 193 | } 194 | 195 | // WithDeadline returns a copy of the parent context with the deadline adjusted 196 | // to be no later than d. If the parent's deadline is already earlier than d, 197 | // WithDeadline(parent, d) is semantically equivalent to parent. The returned 198 | // context's Done channel is closed when the deadline expires, when the returned 199 | // cancel function is called, or when the parent context's Done channel is 200 | // closed, whichever happens first. 201 | // 202 | // Canceling this context releases resources associated with it, so code should 203 | // call cancel as soon as the operations running in this Context complete. 204 | func WithDeadline(parent Context, deadline time.Time) (Context, CancelFunc) { 205 | if cur, ok := parent.Deadline(); ok && cur.Before(deadline) { 206 | // The current deadline is already sooner than the new one. 207 | return WithCancel(parent) 208 | } 209 | c := &timerCtx{ 210 | cancelCtx: newCancelCtx(parent), 211 | deadline: deadline, 212 | } 213 | propagateCancel(parent, c) 214 | d := deadline.Sub(time.Now()) 215 | if d <= 0 { 216 | c.cancel(true, DeadlineExceeded) // deadline has already passed 217 | return c, func() { c.cancel(true, Canceled) } 218 | } 219 | c.mu.Lock() 220 | defer c.mu.Unlock() 221 | if c.err == nil { 222 | c.timer = time.AfterFunc(d, func() { 223 | c.cancel(true, DeadlineExceeded) 224 | }) 225 | } 226 | return c, func() { c.cancel(true, Canceled) } 227 | } 228 | 229 | // A timerCtx carries a timer and a deadline. It embeds a cancelCtx to 230 | // implement Done and Err. It implements cancel by stopping its timer then 231 | // delegating to cancelCtx.cancel. 232 | type timerCtx struct { 233 | *cancelCtx 234 | timer *time.Timer // Under cancelCtx.mu. 235 | 236 | deadline time.Time 237 | } 238 | 239 | func (c *timerCtx) Deadline() (deadline time.Time, ok bool) { 240 | return c.deadline, true 241 | } 242 | 243 | func (c *timerCtx) String() string { 244 | return fmt.Sprintf("%v.WithDeadline(%s [%s])", c.cancelCtx.Context, c.deadline, c.deadline.Sub(time.Now())) 245 | } 246 | 247 | func (c *timerCtx) cancel(removeFromParent bool, err error) { 248 | c.cancelCtx.cancel(false, err) 249 | if removeFromParent { 250 | // Remove this timerCtx from its parent cancelCtx's children. 251 | removeChild(c.cancelCtx.Context, c) 252 | } 253 | c.mu.Lock() 254 | if c.timer != nil { 255 | c.timer.Stop() 256 | c.timer = nil 257 | } 258 | c.mu.Unlock() 259 | } 260 | 261 | // WithTimeout returns WithDeadline(parent, time.Now().Add(timeout)). 262 | // 263 | // Canceling this context releases resources associated with it, so code should 264 | // call cancel as soon as the operations running in this Context complete: 265 | // 266 | // func slowOperationWithTimeout(ctx context.Context) (Result, error) { 267 | // ctx, cancel := context.WithTimeout(ctx, 100*time.Millisecond) 268 | // defer cancel() // releases resources if slowOperation completes before timeout elapses 269 | // return slowOperation(ctx) 270 | // } 271 | func WithTimeout(parent Context, timeout time.Duration) (Context, CancelFunc) { 272 | return WithDeadline(parent, time.Now().Add(timeout)) 273 | } 274 | 275 | // WithValue returns a copy of parent in which the value associated with key is 276 | // val. 277 | // 278 | // Use context Values only for request-scoped data that transits processes and 279 | // APIs, not for passing optional parameters to functions. 280 | func WithValue(parent Context, key interface{}, val interface{}) Context { 281 | return &valueCtx{parent, key, val} 282 | } 283 | 284 | // A valueCtx carries a key-value pair. It implements Value for that key and 285 | // delegates all other calls to the embedded Context. 286 | type valueCtx struct { 287 | Context 288 | key, val interface{} 289 | } 290 | 291 | func (c *valueCtx) String() string { 292 | return fmt.Sprintf("%v.WithValue(%#v, %#v)", c.Context, c.key, c.val) 293 | } 294 | 295 | func (c *valueCtx) Value(key interface{}) interface{} { 296 | if c.key == key { 297 | return c.val 298 | } 299 | return c.Context.Value(key) 300 | } 301 | --------------------------------------------------------------------------------