github.com/graybobo/golang.org-package-offline-cache@v0.0.0-20200626051047-6608995c132f/x/talks/2012/simple/webfront/main.go (about) 1 // +build OMIT 2 3 // This is a somewhat cut back version of webfront, available at 4 // http://github.com/nf/webfront 5 6 /* 7 Copyright 2011 Google Inc. 8 9 Licensed under the Apache License, Version 2.0 (the "License"); 10 you may not use this file except in compliance with the License. 11 You may obtain a copy of the License at 12 13 http://www.apache.org/licenses/LICENSE-2.0 14 15 Unless required by applicable law or agreed to in writing, software 16 distributed under the License is distributed on an "AS IS" BASIS, 17 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 18 See the License for the specific language governing permissions and 19 limitations under the License. 20 */ 21 22 /* 23 webfront is an HTTP server and reverse proxy. 24 25 It reads a JSON-formatted rule file like this: 26 27 [ 28 {"Host": "example.com", "Serve": "/var/www"}, 29 {"Host": "example.org", "Forward": "localhost:8080"} 30 ] 31 32 For all requests to the host example.com (or any name ending in 33 ".example.com") it serves files from the /var/www directory. 34 35 For requests to example.org, it forwards the request to the HTTP 36 server listening on localhost port 8080. 37 38 Usage of webfront: 39 -http=":80": HTTP listen address 40 -poll=10s: file poll interval 41 -rules="": rule definition file 42 43 webfront was written by Andrew Gerrand <adg@golang.org> 44 */ 45 package main 46 47 import ( 48 "encoding/json" 49 "flag" 50 "fmt" 51 "log" 52 "net/http" 53 "net/http/httputil" 54 "os" 55 "strings" 56 "sync" 57 "time" 58 ) 59 60 var ( 61 httpAddr = flag.String("http", ":80", "HTTP listen address") 62 ruleFile = flag.String("rules", "", "rule definition file") 63 pollInterval = flag.Duration("poll", time.Second*10, "file poll interval") 64 ) 65 66 func main() { 67 flag.Parse() 68 69 s, err := NewServer(*ruleFile, *pollInterval) 70 if err != nil { 71 log.Fatal(err) 72 } 73 74 err = http.ListenAndServe(*httpAddr, s) 75 if err != nil { 76 log.Fatal(err) 77 } 78 } 79 80 // Server implements an http.Handler that acts as either a reverse proxy or 81 // a simple file server, as determined by a rule set. 82 type Server struct { 83 mu sync.RWMutex // guards the fields below 84 mtime time.Time // when the rule file was last modified 85 rules []*Rule 86 } 87 88 // Rule represents a rule in a configuration file. 89 type Rule struct { 90 Host string // to match against request Host header 91 Forward string // non-empty if reverse proxy 92 Serve string // non-empty if file server 93 } 94 95 // Match returns true if the Rule matches the given Request. 96 func (r *Rule) Match(req *http.Request) bool { 97 return req.Host == r.Host || strings.HasSuffix(req.Host, "."+r.Host) 98 } 99 100 // Handler returns the appropriate Handler for the Rule. 101 func (r *Rule) Handler() http.Handler { 102 if h := r.Forward; h != "" { 103 return &httputil.ReverseProxy{ 104 Director: func(req *http.Request) { 105 req.URL.Scheme = "http" 106 req.URL.Host = h 107 }, 108 } 109 } 110 if d := r.Serve; d != "" { 111 return http.FileServer(http.Dir(d)) 112 } 113 return nil 114 } 115 116 // NewServer constructs a Server that reads rules from file with a period 117 // specified by poll. 118 func NewServer(file string, poll time.Duration) (*Server, error) { 119 s := new(Server) 120 if err := s.loadRules(file); err != nil { 121 return nil, err 122 } 123 go s.refreshRules(file, poll) 124 return s, nil 125 } 126 127 // ServeHTTP matches the Request with a Rule and, if found, serves the 128 // request with the Rule's handler. 129 func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) { 130 if h := s.handler(r); h != nil { 131 h.ServeHTTP(w, r) 132 return 133 } 134 http.Error(w, "Not found.", http.StatusNotFound) 135 } 136 137 // handler returns the appropriate Handler for the given Request, 138 // or nil if none found. 139 func (s *Server) handler(req *http.Request) http.Handler { 140 s.mu.RLock() 141 defer s.mu.RUnlock() 142 for _, r := range s.rules { 143 if r.Match(req) { 144 return r.Handler() 145 } 146 } 147 return nil 148 } 149 150 // refreshRules polls file periodically and refreshes the Server's rule 151 // set if the file has been modified. 152 func (s *Server) refreshRules(file string, poll time.Duration) { 153 for { 154 if err := s.loadRules(file); err != nil { 155 log.Println(err) 156 } 157 time.Sleep(poll) 158 } 159 } 160 161 // loadRules tests whether file has been modified 162 // and, if so, loads the rule set from file. 163 func (s *Server) loadRules(file string) error { 164 fi, err := os.Stat(file) 165 if err != nil { 166 return err 167 } 168 mtime := fi.ModTime() 169 if mtime.Before(s.mtime) && s.rules != nil { 170 return nil // no change 171 } 172 rules, err := parseRules(file) 173 if err != nil { 174 return fmt.Errorf("parsing %s: %v", file, err) 175 } 176 s.mu.Lock() 177 s.mtime = mtime 178 s.rules = rules 179 s.mu.Unlock() 180 return nil 181 } 182 183 // parseRules reads rule definitions from file returns the resultant Rules. 184 func parseRules(file string) ([]*Rule, error) { 185 f, err := os.Open(file) 186 if err != nil { 187 return nil, err 188 } 189 defer f.Close() 190 var rules []*Rule 191 err = json.NewDecoder(f).Decode(&rules) 192 if err != nil { 193 return nil, err 194 } 195 return rules, nil 196 }