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  }