go.talks: add talk "a simple programming environment"
R=r, campoy
CC=golang-dev
https://golang.org/cl/6768062
diff --git a/2012/simple.slide b/2012/simple.slide
new file mode 100644
index 0000000..cf6f2ca
--- /dev/null
+++ b/2012/simple.slide
@@ -0,0 +1,376 @@
+Go: a simple programming environment
+
+# Go is a general-purpose language that bridges the gap between efficient
+# statically typed languages and productive dynamic language. But it’s not just
+# the language that makes Go special – Go has broad and consistent standard
+# libraries and powerful but simple tools.
+#
+# This talk gives an introduction to Go, followed by a tour of some real
+# programs that demonstrate the power, scope, and simplicity of the Go
+# programming environment.
+
+Andrew Gerrand
+Google Inc.
+@enneff
+adg@golang.org
+http://golang.org
+
+* Background
+
+* Why a new language?
+
+Motivated by our needs at Google.
+
+We need:
+
+- Efficiency
+- Safety
+- Concurrency
+- Scalability
+- Fast development cycle
+- No surprises
+- A cute mascot
+
+* Design
+
+"Consensus drove the design. Nothing went into the language until [Ken Thompson, Robert Griesemer, and myself] all agreed that it was right. Some features didn’t get resolved until after a year or more of discussion." - Rob Pike
+
+Go is:
+
+- Lightweight, avoids unnecessary repetition
+- Object Oriented, but not in the usual way
+- Concurrent, in a way that keeps you sane
+- Designed for working programmers
+
+* Go 1
+
+Released in March 2012
+
+A specification of the language and libraries that will be supported for years.
+
+The guarantee: code written for Go 1.0 will build and run with Go 1.x.
+
+Best thing we ever did.
+
+* The gopher
+
+.image simple/gopher.jpg
+
+* Hello, go
+
+.play simple/hello.go
+
+* Standard library
+
+* Packages
+
+Go code lives in packages.
+
+Packages contain type, function, variable, and constant declarations.
+
+Packages can be very small (package `errors` has just one declaration) or very large (package `net/http` has >100 declarations). Most are somewhere in between.
+
+Case determines visiblity: `Foo` is exported, `foo` is not
+
+* io
+
+The `io` package provides fundamental I/O interfaces that are used throughout most Go code.
+
+The most ubiquitous are the `Reader` and `Writer` types, which describe streams of data.
+
+.code simple/io.go
+
+`Reader` and `Writer` implementations include files, sockets, (de)compressors, image and JSON codecs, and many more.
+
+* Chaining io.Readers
+
+.play simple/reader.go
+
+* net/http
+
+The `net/http` package implements an HTTP server and client.
+
+.play simple/hello-web.go
+
+* encoding/json
+
+The `encoding/json` package converts JSON-encoded data to and from native Go data structures.
+
+.play simple/json.go /const/,$
+
+* time
+
+The `time` package provides a representation of time and duration, and other time-related functions.
+
+.play simple/time.go /START/,/END/
+.play simple/time2.go /START/,/END/
+
+`time.Time` values also contain a `time.Location` (for display only):
+
+.play simple/time3.go /START/,/END/
+
+* flag
+
+The `flag` package provides a simple API for parsing command-line flags.
+
+.play simple/flag.go
+
+ $ flag -message 'Hold on...' -delay 5m
+
+* Tools
+
+* The go tool
+
+The `go` tool is the defacto standard for building and installing Go code.
+
+Compile and run a single file program:
+
+ $ go run hello.go
+
+Build and install the package in the current directory (and its dependencies):
+
+ $ go install
+
+Build and install the fmt package (and deps):
+
+ $ go install fmt
+
+It also acts as an interface for most of the Go tools.
+
+* Import paths
+
+The `go` tool is a "zero configuration" tool. No Makefiles or scripts. Just Go code.
+Your build schema and code are always in sync; they are one and the same.
+
+Package import paths mirror the code's location on the file system:
+
+ src/
+ github.com/nf/
+ gosynth/
+ main.go
+ note.go
+ osc.go
+ wav/
+ writer.go
+
+The `gosynth` program imports the `wav` package:
+
+ import "github.com/nf/wav"
+
+Installing `gosynth` will automatically install the `wav` package:
+
+ $ go install github.com/nf/gosynth
+
+* Remote dependencies
+
+The `go` tool also fetches Go code from remote repositories.
+
+Import paths can be URLs:
+
+ import "code.google.com/p/go.net/websocket"
+
+To fetch, build and install a package:
+
+ $ go get code.google.com/p/go.net/websocket
+
+To fetch, build, and install `gosynth` and its dependencies:
+
+ $ go get github.com/nf/gosynth
+
+This simple design leads to other cool tools:
+
+.link http://go.pkgdoc.org
+
+* Godoc
+
+Godoc extracts documentation from Go code and presents it in a variety of forms.
+
+No prescribed format, just regular comments that precede the declaration they document.
+
+ // Split slices s into all substrings separated by sep and returns a slice of
+ // the substrings between those separators.
+ // If sep is empty, Split splits after each UTF-8 sequence.
+ // It is equivalent to SplitN with a count of -1.
+ func Split(s, sep string) []string {
+
+.image simple/split.png
+
+Documentation that lives with code is easy to keep up-to-date.
+
+* Gofmt
+
+The `gofmt` tool is a pretty-printer for Go source code.
+
+All Go code in the core is gofmt'd, as is ~70% of open source Go code.
+
+Ends boring formatting discussions.
+
+Improves readability. Improves writability.
+
+Saves a _huge_ amount of time.
+
+* Tests: writing
+
+The `go` tool and the `testing` package provide a lightweight test framework.
+
+.code simple/string_test.go /func TestIndex/,/^}/
+
+* Tests: running
+
+The go tool runs tests.
+
+ $ go test
+ PASS
+
+ $ go test -v
+ === RUN TestIndex
+ --- PASS: TestIndex (0.00 seconds)
+ PASS
+
+To run the tests for all my projects:
+
+ $ go test github.com/nf/...
+
+* Tests: benchmarks
+
+The testing package also supports benchmarks.
+
+A sample benchmark function:
+
+.code simple/string_test.go /func BenchmarkIndex/,/^}/
+
+The benchmark package will vary `b.N` until the benchmark function lasts long enough to be timed reliably.
+
+ $ go test -test.bench=Index
+ PASS
+ BenchmarkIndex 50000000 37.3 ns/op
+
+* Tests: doc examples
+
+The testing package also supports testable examples.
+
+.code simple/string_test.go /func ExampleIndex/,/^}/
+
+Examples and built and run as part of the normal test suite:
+
+ $ go test -v
+ === RUN: ExampleIndex
+ --- PASS: ExampleIndex (0.00 seconds)
+ PASS
+
+The example is displayed in godoc alongside the thing it demonstrates:
+
+.link http://golang.org/pkg/strings/#Index
+
+* And there's more
+
+- vet: checks code for common programmer mistakes
+- prof: CPU and memory profiling
+- fix: automatically migrate code as APIs change
+- GDB support
+- Editor support: Vim, Emacs, Eclipse, Sublime Text
+
+* An example
+
+* webfront
+
+webfront is an HTTP server and reverse proxy.
+
+It reads a JSON-formatted rule file like this:
+
+.code simple/webfront/main.go /^\[/,/\]/
+
+For all requests to the host `example.com` (or any name ending in `".example.com"`) it serves files from the `/var/www` directory.
+
+For requests to `example.org`, it forwards the request to the HTTP server listening on localhost port 8080.
+
+* The Rule type
+
+A `Rule` value specifies what to do for a request to a specific host.
+
+.code simple/webfront/main.go /Rule represents/,/^}/
+
+It corresponds directly with the entries in the JSON configuration file.
+
+.code simple/webfront/main.go /^\[/,/\]/
+
+* Rule methods
+
+.code simple/webfront/main.go /Match returns/,/^}/
+.code simple/webfront/main.go /Handler returns/,/^}/
+
+* The Server type
+
+The `Server` type is responsible for loading (and refreshing) the rules from the rule file and serving HTTP requests with the appropriate handler.
+
+.code simple/webfront/main.go /Server implements/,/^}/
+.code simple/webfront/main.go /ServeHTTP matches/,/^}/
+
+* The handler method
+
+.code simple/webfront/main.go /handler returns/,/^}/
+
+* Parsing rules
+
+The `parseRules` function uses the `encoding/json` package to read the rule file into a Go data structure.
+
+.code simple/webfront/main.go /parseRules reads/,/^}/
+
+* The loadRules method
+
+.code simple/webfront/main.go /loadRules tests/,/^}/
+
+* Constructing the server
+
+.code simple/webfront/main.go /NewServer constructs/,/^}/
+
+This constructor function launches a goroutine running the `refreshRules` method.
+
+* Refreshing the rules
+
+.code simple/webfront/main.go /refreshRules polls/,/^}/
+
+* Bringing it all together
+
+The main function parses command-line flags, constructs a `Server`, and launches an HTTP server that serves all requests with the `Server`.
+
+.code simple/webfront/main.go /^var/,/^}/
+
+* Demo
+
+* Testing (1/3)
+
+The `Server` integration test uses the `httptest` package to construct a dummy HTTP server, synthesizes a set of rules, and constructs a `Server` instance that use those rules.
+
+.code simple/webfront/server_test.go /^func testHandler/,/STOP/
+
+* Testing (2/3)
+
+Each test cases in the table specifies a request URL and the expected response code and body.
+
+.code simple/webfront/server_test.go /TESTS START/,/STOP/
+
+* Testing (3/3)
+
+For each test case, construct an `http.Request` for the url and an `httptest.ResponseRecorder` to capture the response, and pass them to the `Server.ServeHTTP` method. Then check that the response matches the test case.
+
+.code simple/webfront/server_test.go /RANGE START/,/^}/
+
+* Demo
+
+* Conclusions
+
+* Further reading
+
+All about Go:
+
+.link http://golang.org
+
+The slides for this talk:
+
+.link http://talks.golang.org/2012/simple.slide
+
+webfront:
+
+.link https://github.com/nf/webfront
+
diff --git a/2012/simple/flag.go b/2012/simple/flag.go
new file mode 100644
index 0000000..67ac0bb
--- /dev/null
+++ b/2012/simple/flag.go
@@ -0,0 +1,18 @@
+package main
+
+import (
+ "flag"
+ "fmt"
+ "time"
+)
+
+var (
+ message = flag.String("message", "Hello!", "what to say")
+ delay = flag.Duration("delay", 2*time.Second, "how long to wait")
+)
+
+func main() {
+ flag.Parse()
+ fmt.Println(*message)
+ time.Sleep(*delay)
+}
diff --git a/2012/simple/gopher.jpg b/2012/simple/gopher.jpg
new file mode 100644
index 0000000..0e886e4
--- /dev/null
+++ b/2012/simple/gopher.jpg
Binary files differ
diff --git a/2012/simple/hello-web.go b/2012/simple/hello-web.go
new file mode 100644
index 0000000..e9c3992
--- /dev/null
+++ b/2012/simple/hello-web.go
@@ -0,0 +1,20 @@
+package main
+
+import (
+ "fmt"
+ "log"
+ "net/http"
+)
+
+type Greeting string
+
+func (g Greeting) ServeHTTP(w http.ResponseWriter, r *http.Request) {
+ fmt.Fprint(w, g)
+}
+
+func main() {
+ err := http.ListenAndServe("localhost:4000", Greeting("Hello, go"))
+ if err != nil {
+ log.Fatal(err)
+ }
+}
diff --git a/2012/simple/hello.go b/2012/simple/hello.go
new file mode 100644
index 0000000..70366f1
--- /dev/null
+++ b/2012/simple/hello.go
@@ -0,0 +1,7 @@
+package main
+
+import "fmt"
+
+func main() {
+ fmt.Println("Hello, go")
+}
diff --git a/2012/simple/io.go b/2012/simple/io.go
new file mode 100644
index 0000000..0977063
--- /dev/null
+++ b/2012/simple/io.go
@@ -0,0 +1,9 @@
+package io
+
+type Writer interface {
+ Write(p []byte) (n int, err error)
+}
+
+type Reader interface {
+ Read(p []byte) (n int, err error)
+}
diff --git a/2012/simple/json.go b/2012/simple/json.go
new file mode 100644
index 0000000..a716ef2
--- /dev/null
+++ b/2012/simple/json.go
@@ -0,0 +1,25 @@
+package main
+
+import (
+ "encoding/json"
+ "fmt"
+ "strings"
+)
+
+const blob = `[
+ {"Title":"Øredev", "URL":"http://oredev.org"},
+ {"Title":"Strange Loop", "URL":"http://thestrangeloop.com"}
+]`
+
+type Item struct {
+ Title string
+ URL string
+}
+
+func main() {
+ var items []*Item
+ json.NewDecoder(strings.NewReader(blob)).Decode(&items)
+ for _, item := range items {
+ fmt.Printf("Title: %v URL: %v\n", item.Title, item.URL)
+ }
+}
diff --git a/2012/simple/reader.go b/2012/simple/reader.go
new file mode 100644
index 0000000..cd051a2
--- /dev/null
+++ b/2012/simple/reader.go
@@ -0,0 +1,21 @@
+package main
+
+import (
+ "compress/gzip"
+ "encoding/base64"
+ "io"
+ "os"
+ "strings"
+)
+
+func main() {
+ var r io.Reader
+ r = strings.NewReader(data)
+ r = base64.NewDecoder(base64.StdEncoding, r)
+ r, _ = gzip.NewReader(r)
+ io.Copy(os.Stdout, r)
+}
+
+const data = `
+H4sIAAAJbogA/1SOO5KDQAxE8zlFZ5tQXGCjjfYIjoURoPKgcY0E57f4VZlQXf2e+r8yOYbMZJhoZWRxz3wkCVjeReETS0VHz5fBCzpxxg/PbfrT/gacCjbjeiRNOChaVkA9RAdR8eVEw4vxa0Dcs3Fe2ZqowpeqG79L995l3VaMBUV/02OS+B6kMWikwG51c8n5GnEPr11F2/QJAAD//z9IppsHAQAA
+`
diff --git a/2012/simple/split.png b/2012/simple/split.png
new file mode 100644
index 0000000..5140229
--- /dev/null
+++ b/2012/simple/split.png
Binary files differ
diff --git a/2012/simple/string_test.go b/2012/simple/string_test.go
new file mode 100644
index 0000000..688d328
--- /dev/null
+++ b/2012/simple/string_test.go
@@ -0,0 +1,43 @@
+package test
+
+import (
+ "fmt"
+ "strings"
+ "testing"
+)
+
+func TestIndex(t *testing.T) {
+ var tests = []struct {
+ s string
+ sep string
+ out int
+ }{
+ {"", "", 0},
+ {"", "a", -1},
+ {"fo", "foo", -1},
+ {"foo", "foo", 0},
+ {"oofofoofooo", "f", 2},
+ // etc
+ }
+ for _, test := range tests {
+ actual := strings.Index(test.s, test.sep)
+ if actual != test.out {
+ t.Errorf("Index(%q,%q) = %v; want %v", test.s, test.sep, actual, test.out)
+ }
+ }
+}
+
+func BenchmarkIndex(b *testing.B) {
+ const s = "some_text=some☺value"
+ for i := 0; i < b.N; i++ {
+ strings.Index(s, "v")
+ }
+}
+
+func ExampleIndex() {
+ fmt.Println(strings.Index("chicken", "ken"))
+ fmt.Println(strings.Index("chicken", "dmr"))
+ // Output:
+ // 4
+ // -1
+}
diff --git a/2012/simple/test.go b/2012/simple/test.go
new file mode 100644
index 0000000..66f2de3
--- /dev/null
+++ b/2012/simple/test.go
@@ -0,0 +1,32 @@
+package main
+
+import "strings"
+
+import "testing"
+
+func TestToUpper(t *testing.T) {
+ in := "loud noises"
+ expected := "LOUD NOISES"
+ got := strings.ToUpper(in)
+ if got != want {
+ t.Errorf("ToUpper(%v) = %v, want %v", in, got, expected)
+ }
+}
+
+func TestContains(t *testing.T) {
+ var tests = []struct {
+ str, substr string
+ expected bool
+ }{
+ {"abc", "bc", true},
+ {"abc", "bcd", false},
+ {"abc", "", true},
+ {"", "a", false},
+ }
+ for _, ct := range tests {
+ if strings.Contains(ct.str, ct.substr) != ct.expected {
+ t.Errorf("Contains(%s, %s) = %v, want %v",
+ ct.str, ct.substr, !ct.expected, ct.expected)
+ }
+ }
+}
diff --git a/2012/simple/time.go b/2012/simple/time.go
new file mode 100644
index 0000000..4e027f7
--- /dev/null
+++ b/2012/simple/time.go
@@ -0,0 +1,16 @@
+package main
+
+import (
+ "fmt"
+ "time"
+)
+
+func main() {
+ // START OMIT
+ if time.Now().Hour() < 12 {
+ fmt.Println("Good morning.")
+ } else {
+ fmt.Println("Good afternoon (or evening).")
+ }
+ // END OMIT
+}
diff --git a/2012/simple/time2.go b/2012/simple/time2.go
new file mode 100644
index 0000000..485e9da
--- /dev/null
+++ b/2012/simple/time2.go
@@ -0,0 +1,14 @@
+package main
+
+import (
+ "fmt"
+ "time"
+)
+
+func main() {
+ // START OMIT
+ birthday, _ := time.Parse("Jan 2 2006", "Nov 10 2009") // time.Time
+ age := time.Since(birthday) // time.Duration
+ fmt.Printf("Go is %d days old\n", age/(time.Hour*24))
+ // END OMIT
+}
diff --git a/2012/simple/time3.go b/2012/simple/time3.go
new file mode 100644
index 0000000..b478a2e
--- /dev/null
+++ b/2012/simple/time3.go
@@ -0,0 +1,15 @@
+package main
+
+import (
+ "fmt"
+ "time"
+)
+
+func main() {
+ // START OMIT
+ t := time.Now()
+ fmt.Println(t.In(time.UTC))
+ home, _ := time.LoadLocation("Australia/Sydney")
+ fmt.Println(t.In(home))
+ // END OMIT
+}
diff --git a/2012/simple/webfront/main.go b/2012/simple/webfront/main.go
new file mode 100644
index 0000000..e177d10
--- /dev/null
+++ b/2012/simple/webfront/main.go
@@ -0,0 +1,194 @@
+/*
+Copyright 2011 Google Inc.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+// This is a somewhat cut back version of webfront, available at
+// http://github.com/nf/webfront
+
+/*
+webfront is an HTTP server and reverse proxy.
+
+It reads a JSON-formatted rule file like this:
+
+[
+ {"Host": "example.com", "Serve": "/var/www"},
+ {"Host": "example.org", "Forward": "localhost:8080"}
+]
+
+For all requests to the host example.com (or any name ending in
+".example.com") it serves files from the /var/www directory.
+
+For requests to example.org, it forwards the request to the HTTP
+server listening on localhost port 8080.
+
+Usage of webfront:
+ -http=":80": HTTP listen address
+ -poll=10s: file poll interval
+ -rules="": rule definition file
+
+webfront was written by Andrew Gerrand <adg@golang.org>
+*/
+package main
+
+import (
+ "encoding/json"
+ "flag"
+ "fmt"
+ "log"
+ "net/http"
+ "net/http/httputil"
+ "os"
+ "strings"
+ "sync"
+ "time"
+)
+
+var (
+ httpAddr = flag.String("http", ":80", "HTTP listen address")
+ ruleFile = flag.String("rules", "", "rule definition file")
+ pollInterval = flag.Duration("poll", time.Second*10, "file poll interval")
+)
+
+func main() {
+ flag.Parse()
+
+ s, err := NewServer(*ruleFile, *pollInterval)
+ if err != nil {
+ log.Fatal(err)
+ }
+
+ err = http.ListenAndServe(*httpAddr, s)
+ if err != nil {
+ log.Fatal(err)
+ }
+}
+
+// Server implements an http.Handler that acts as either a reverse proxy or
+// a simple file server, as determined by a rule set.
+type Server struct {
+ mu sync.RWMutex // guards the fields below
+ mtime time.Time // when the rule file was last modified
+ rules []*Rule
+}
+
+// Rule represents a rule in a configuration file.
+type Rule struct {
+ Host string // to match against request Host header
+ Forward string // non-empty if reverse proxy
+ Serve string // non-empty if file server
+}
+
+// Match returns true if the Rule matches the given Request.
+func (r *Rule) Match(req *http.Request) bool {
+ return req.Host == r.Host || strings.HasSuffix(req.Host, "."+r.Host)
+}
+
+// Handler returns the appropriate Handler for the Rule.
+func (r *Rule) Handler() http.Handler {
+ if h := r.Forward; h != "" {
+ return &httputil.ReverseProxy{
+ Director: func(req *http.Request) {
+ req.URL.Scheme = "http"
+ req.URL.Host = h
+ },
+ }
+ }
+ if d := r.Serve; d != "" {
+ return http.FileServer(http.Dir(d))
+ }
+ return nil
+}
+
+// NewServer constructs a Server that reads rules from file with a period
+// specified by poll.
+func NewServer(file string, poll time.Duration) (*Server, error) {
+ s := new(Server)
+ if err := s.loadRules(file); err != nil {
+ return nil, err
+ }
+ go s.refreshRules(file, poll)
+ return s, nil
+}
+
+// ServeHTTP matches the Request with a Rule and, if found, serves the
+// request with the Rule's handler.
+func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
+ if h := s.handler(r); h != nil {
+ h.ServeHTTP(w, r)
+ return
+ }
+ http.Error(w, "Not found.", http.StatusNotFound)
+}
+
+// handler returns the appropriate Handler for the given Request,
+// or nil if none found.
+func (s *Server) handler(req *http.Request) http.Handler {
+ s.mu.RLock()
+ defer s.mu.RUnlock()
+ for _, r := range s.rules {
+ if r.Match(req) {
+ return r.Handler()
+ }
+ }
+ return nil
+}
+
+// refreshRules polls file periodically and refreshes the Server's rule
+// set if the file has been modified.
+func (s *Server) refreshRules(file string, poll time.Duration) {
+ for {
+ if err := s.loadRules(file); err != nil {
+ log.Println(err)
+ }
+ time.Sleep(poll)
+ }
+}
+
+// loadRules tests whether file has been modified
+// and, if so, loads the rule set from file.
+func (s *Server) loadRules(file string) error {
+ fi, err := os.Stat(file)
+ if err != nil {
+ return err
+ }
+ mtime := fi.ModTime()
+ if mtime.Before(s.mtime) && s.rules != nil {
+ return nil // no change
+ }
+ rules, err := parseRules(file)
+ if err != nil {
+ return fmt.Errorf("parsing %s: %v", file, err)
+ }
+ s.mu.Lock()
+ s.mtime = mtime
+ s.rules = rules
+ s.mu.Unlock()
+ return nil
+}
+
+// parseRules reads rule definitions from file returns the resultant Rules.
+func parseRules(file string) ([]*Rule, error) {
+ f, err := os.Open(file)
+ if err != nil {
+ return nil, err
+ }
+ defer f.Close()
+ var rules []*Rule
+ err = json.NewDecoder(f).Decode(&rules)
+ if err != nil {
+ return nil, err
+ }
+ return rules, nil
+}
diff --git a/2012/simple/webfront/server_test.go b/2012/simple/webfront/server_test.go
new file mode 100644
index 0000000..509ea04
--- /dev/null
+++ b/2012/simple/webfront/server_test.go
@@ -0,0 +1,97 @@
+/*
+Copyright 2011 Google Inc.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package main
+
+import (
+ "bytes"
+ "encoding/json"
+ "io/ioutil"
+ "net/http"
+ "net/http/httptest"
+ "os"
+ "testing"
+ "time"
+)
+
+func testHandler(w http.ResponseWriter, r *http.Request) {
+ w.Write([]byte("OK"))
+}
+
+func TestServer(t *testing.T) {
+ dummy := httptest.NewServer(http.HandlerFunc(testHandler))
+ defer dummy.Close()
+
+ ruleFile := writeRules([]*Rule{
+ {Host: "example.com", Forward: dummy.Listener.Addr().String()},
+ {Host: "example.org", Serve: "testdata"},
+ })
+ defer os.Remove(ruleFile)
+
+ s, err := NewServer(ruleFile, time.Hour)
+ if err != nil {
+ t.Fatal(err)
+ }
+ // continued next slide
+ // STOP OMIT
+
+ // TESTS START OMIT
+ // continued from previous slide
+
+ var tests = []struct {
+ url string
+ code int
+ body string
+ }{
+ {"http://example.com/", 200, "OK"},
+ {"http://foo.example.com/", 200, "OK"},
+ {"http://example.org/", 200, "contents of index.html\n"},
+ {"http://example.net/", 404, "Not found.\n"},
+ {"http://fooexample.com/", 404, "Not found.\n"},
+ }
+
+ // continued next slide
+ // STOP OMIT
+
+ // RANGE START OMIT
+ // continued from previous slide
+
+ for _, test := range tests {
+ req, _ := http.NewRequest("GET", test.url, nil)
+ rw := httptest.NewRecorder()
+ rw.Body = new(bytes.Buffer)
+ s.ServeHTTP(rw, req)
+ if g, w := rw.Code, test.code; g != w {
+ t.Errorf("%s: code = %d, want %d", test.url, g, w)
+ }
+ if g, w := rw.Body.String(), test.body; g != w {
+ t.Errorf("%s: body = %q, want %q", test.url, g, w)
+ }
+ }
+}
+
+func writeRules(rules []*Rule) (name string) {
+ f, err := ioutil.TempFile("", "webfront-rules")
+ if err != nil {
+ panic(err)
+ }
+ defer f.Close()
+ err = json.NewEncoder(f).Encode(rules)
+ if err != nil {
+ panic(err)
+ }
+ return f.Name()
+}
diff --git a/2012/simple/webfront/testdata/index.html b/2012/simple/webfront/testdata/index.html
new file mode 100644
index 0000000..583e54d
--- /dev/null
+++ b/2012/simple/webfront/testdata/index.html
@@ -0,0 +1 @@
+contents of index.html