Permalink
Browse files

Suppose a static system

  • Loading branch information...
1 parent e9f43d3 commit 1a871c98a1e6b5ecceb3e56d58fa4c6b227856c9 @mmcgrana committed Jan 3, 2016
Showing with 10 additions and 266 deletions.
  1. +0 −1 Procfile
  2. +10 −57 README.md
  3. +0 −208 server.go
View
1 Procfile
@@ -1 +0,0 @@
-web: gobyexample
View
67 README.md
@@ -1,18 +1,21 @@
## Go by Example
-Content, toolchain, and web server for [Go by Example](https://gobyexample.com).
+Content and build toolchain for [Go by Example](https://gobyexample.com),
+site that teaches Go via annotated example programs.
### Overview
-The Go by Example site is built by extracting code &
+The Go by Example site is built by extracting code and
comments from source files in `examples` and rendering
-that data via the site `templates`. The programs
-implementing this build process are in `tools`.
+them via the `templates` into a static `public`
+directory. The programs implementing this build process
+are in `tools`, along with some vendor'd dependencies
+in `vendor`.
-The build process produces a directory of static files -
-`public` - suitable for serving by any modern HTTP server.
-We include a lightweight Go server in `server.go`.
+The built `public` directory can be served by any
+static content system. The production site uses S3 and
+CloudFront, for example.
### Building
@@ -32,56 +35,6 @@ $ tools/build-loop
```
-### Local Deploy
-
-To run and view the site locally:
-
-```bash
-$ mkdir -p $GOPATH/src/github.com/mmcgrana
-$ cd $GOPATH/src/github.com/mmcgrana
-$ git clone git@github.com:mmcgrana/gobyexample.git
-$ cd gobyexample
-$ go get
-$ PORT=5000 CANONICAL_HOST=127.0.0.1 FORCE_HTTPS=0 gobyexample
-$ open http://127.0.0.1:5000/
-```
-
-
-### Heroku Deploy
-
-To setup the site on Heroku:
-
-```bash
-$ export DEPLOY=$USER
-$ export APP=gobyexample-$USER
-$ heroku create $APP -r $DEPLOY
-$ heroku config:add -a $APP
- BUILDPACK_URL=https://github.com/mmcgrana/buildpack-go.git
- CANONICAL_HOST=$APP.herokuapp.com \
- FORCE_HTTPS=1 \
- AUTH=go:byexample
-$ heroku labs:enable dot-profile-d -a $APP
-$ git push $DEPLOY master
-$ heroku open -a $APP
-```
-
-Add a domain + SSL:
-
-```bash
-$ heroku domains:add $DOMAIN
-$ heroku addons:add ssl -r $DEPLOY
-# order ssl cert for domain
-$ cat > /tmp/server.key
-$ cat > /tmp/server.crt.orig
-$ curl https://knowledge.rapidssl.com/library/VERISIGN/ALL_OTHER/RapidSSL%20Intermediate/RapidSSL_CA_bundle.pem > /tmp/rapidssl_bundle.pem
-$ cat /tmp/server.crt.orig /tmp/rapidssl_bundle.pem > /tmp/server.crt
-$ heroku certs:add /tmp/server.crt /tmp/server.key -r $DEPLOY
-# add ALIAS record from domain to ssl endpoint dns
-$ heroku config:add CANONICAL_HOST=$DOMAIN -r $DEPLOY
-$ heroku open -r $DEPLOY
-```
-
-
### License
This work is copyright Mark McGranaghan and licensed under a
View
208 server.go
@@ -1,208 +0,0 @@
-package main
-
-import (
- "encoding/base64"
- "fmt"
- "github.com/gorilla/mux"
- "io/ioutil"
- "net"
- "net/http"
- "os"
- "os/signal"
- "strings"
- "sync/atomic"
- "syscall"
- "time"
-)
-
-func check(err error) {
- if err != nil {
- panic(err)
- }
-}
-
-func config(k string) string {
- v := os.Getenv(k)
- if v == "" {
- panic("missing " + k)
- }
- return v
-}
-
-func runLogging(logs chan string) {
- for log := range logs {
- fmt.Println(log)
- }
-}
-
-func wrapLogging(f http.HandlerFunc, logs chan string) http.HandlerFunc {
- return func(res http.ResponseWriter, req *http.Request) {
- start := time.Now()
- f(res, req)
- method := req.Method
- path := req.URL.Path
- elapsed := float64(time.Since(start)) / 1000000.0
- logs <- fmt.Sprintf("request at=finish method=%s path=%s elapsed=%f", method, path, elapsed)
- }
-}
-
-func wrapCanonicalHost(f http.HandlerFunc, canonicalHost string, forceHttps bool) http.HandlerFunc {
- return func(res http.ResponseWriter, req *http.Request) {
- scheme := "http"
- if h, ok := req.Header["X-Forwarded-Proto"]; ok {
- if h[0] == "https" {
- scheme = "https"
- }
- }
-
- hostPort := strings.Split(req.Host, ":")
- host := hostPort[0]
-
- if (forceHttps && (scheme != "https")) || host != canonicalHost {
- if forceHttps {
- scheme = "https"
- }
- hostPort[0] = canonicalHost
- url := scheme + "://" + strings.Join(hostPort, ":") + req.URL.String()
- http.Redirect(res, req, url, 301)
- return
- }
-
- f(res, req)
- }
-}
-
-type Authenticator func(string, string) bool
-
-func testAuth(r *http.Request, auth Authenticator) bool {
- s := strings.SplitN(r.Header.Get("Authorization"), " ", 2)
- if len(s) != 2 || s[0] != "Basic" {
- return false
- }
- b, err := base64.StdEncoding.DecodeString(s[1])
- if err != nil {
- return false
- }
- pair := strings.SplitN(string(b), ":", 2)
- if len(pair) != 2 {
- return false
- }
- return auth(pair[0], pair[1])
-}
-
-func requireAuth(w http.ResponseWriter, r *http.Request) {
- w.Header().Set("WWW-Authenticate", `Basic realm="private"`)
- w.WriteHeader(401)
- w.Write([]byte("401 Unauthorized\n"))
-}
-
-func wrapAuth(h http.HandlerFunc, a Authenticator) http.HandlerFunc {
- return func(w http.ResponseWriter, r *http.Request) {
- if testAuth(r, a) {
- h(w, r)
- } else {
- requireAuth(w, r)
- }
- }
-}
-
-var reqCount int64 = 0
-
-func wrapReqCount(h http.HandlerFunc, reqCountPtr *int64) http.HandlerFunc {
- return func(w http.ResponseWriter, r *http.Request) {
- atomic.AddInt64(reqCountPtr, 1)
- h(w, r)
- atomic.AddInt64(reqCountPtr, -1)
- }
-}
-
-func static(res http.ResponseWriter, req *http.Request) {
- http.ServeFile(res, req, "public"+req.URL.Path)
-}
-
-func notFound(res http.ResponseWriter, req *http.Request) {
- http.ServeFile(res, req, "public/404.html")
-}
-
-func checkAuth(user, pass string) bool {
- auth := os.Getenv("AUTH")
- if auth == "" {
- return true
- }
- return auth == strings.Join([]string{user, pass}, ":")
-}
-
-func routerHandlerFunc(router *mux.Router) http.HandlerFunc {
- return func(res http.ResponseWriter, req *http.Request) {
- router.ServeHTTP(res, req)
- }
-}
-
-func router() *mux.Router {
- router := mux.NewRouter()
- router.HandleFunc("/", static).Methods("GET")
- router.HandleFunc("/favicon.ico", static).Methods("GET")
- router.HandleFunc("/play.png", static).Methods("GET")
- router.HandleFunc("/site.css", static).Methods("GET")
- entries, err := ioutil.ReadDir("public")
- check(err)
- for _, f := range entries {
- if !strings.Contains(f.Name(), ".") {
- router.HandleFunc("/" + f.Name(), static).Methods("GET")
- }
- }
- router.NotFoundHandler = http.HandlerFunc(notFound)
- return router
-}
-
-func main() {
- logs := make(chan string, 10000)
- go runLogging(logs)
-
- handler := routerHandlerFunc(router())
- if os.Getenv("AUTH") != "" {
- handler = wrapAuth(handler, checkAuth)
- }
- handler = wrapCanonicalHost(handler, config("CANONICAL_HOST"), config("FORCE_HTTPS") == "1")
- handler = wrapLogging(handler, logs)
- handler = wrapReqCount(handler, &reqCount)
-
- server := &http.Server{Handler: handler}
- listener, listenErr := net.Listen("tcp", ":"+config("PORT"))
- if listenErr != nil {
- panic(listenErr)
- }
-
- stop := make(chan bool, 1)
- sig := make(chan os.Signal, 1)
- go func() {
- signal.Notify(sig, syscall.SIGINT, syscall.SIGTERM)
- logs <- "trap at=start"
- <-sig
- for {
- reqCountCurrent := atomic.LoadInt64(&reqCount)
- if reqCountCurrent > 0 {
- logs <- fmt.Sprintf("trap at=draining remaining=%d", reqCountCurrent)
- time.Sleep(time.Second)
- } else {
- logs <- fmt.Sprintf("trap at=finish")
- stop <- true
- return
- }
- }
- }()
-
- go func() {
- logs <- "serve at=start"
- server.Serve(listener)
- logs <- "serve at=finish"
- }()
-
- <-stop
- logs <- "close at=start"
- closeErr := listener.Close()
- if closeErr != nil {
- panic(closeErr)
- }
- logs <- "close at=finish"
-}

0 comments on commit 1a871c9

Please sign in to comment.