An Elaborate Web Server
This lesson covers all the primary services provided by a server by implementing a detailed web server, and showing how each service is delivered independently.
To further deepen your understanding of the Http
package and how to build web server functionality, study and experiment with the following example. First, the code is listed, then, the usage of the different functionalities of the program and its output are shown.
The example below demonstrates various ways in which you can write web server handlers.
The web server itself is started at line 43 in main()
with error-handling in the following lines.
Package expvar
provides a standardized interface to public variables, such as operation counters in servers. A variable containing the number of hello requests is defined at line 15, and a struct Counter
at line 22 as well as a channel of integers at line 27. Also, some command-line flags are defined (from line 17 to line 19, which are then parsed at line 30.
Then, from line 31 to line 42, we define all of the handlers, which are used by this webserver. At line 34, we define a new Counter
variable named ctr
. Then, at line 36, we publish ctr
, which is global to the webserver.
More detailed explanations of each of the web server handlers can be found below, which also shows you which request URL corresponds to which handler function.
package main import ( "bytes" "expvar" "flag" "fmt" "net/http" "io" "log" "os" "strconv" ) // hello world, the web server var helloRequests = expvar.NewInt("hello-requests") // flags: var webroot = flag.String("root", "/home/user", "web root directory") // simple flag server var booleanflag = flag.Bool("boolean", true, "another flag for testing") // Simple counter server. POSTing to it will set the value. type Counter struct { n int } // a channel type Chan chan int func main() { flag.Parse() http.Handle("/", http.HandlerFunc(Logger)) http.Handle("/go/hello", http.HandlerFunc(HelloServer)) // The counter is published as a variable directly. ctr := new(Counter) expvar.Publish("counter", ctr) http.Handle("/counter", ctr) // http.Handle("/go/", http.FileServer(http.Dir("/tmp"))) // uses the OS filesystem http.Handle("/go/", http.StripPrefix("/go/", http.FileServer(http.Dir(*webroot)))) http.Handle("/flags", http.HandlerFunc(FlagServer)) http.Handle("/args", http.HandlerFunc(ArgServer)) http.Handle("/chan", ChanCreate()) http.Handle("/date", http.HandlerFunc(DateServer)) err := http.ListenAndServe("0.0.0.0:3000", nil) if err != nil { log.Panicln("ListenAndServe:", err) } } func Logger(w http.ResponseWriter, req *http.Request) { log.Print(req.URL.String()) w.WriteHeader(404) w.Write([]byte("oops")) } func HelloServer(w http.ResponseWriter, req *http.Request) { helloRequests.Add(1) io.WriteString(w, "hello, world!\n") } // This makes Counter satisfy the expvar.Var interface, so we can export // it directly. func (ctr *Counter) String() string { return fmt.Sprintf("%d", ctr.n) } func (ctr *Counter) ServeHTTP(w http.ResponseWriter, req *http.Request) { switch req.Method { case "GET": // increment n ctr.n++ case "POST": // set n to posted value buf := new(bytes.Buffer) io.Copy(buf, req.Body) body := buf.String() if n, err := strconv.Atoi(body); err != nil { fmt.Fprintf(w, "bad POST: %v\nbody: [%v]\n", err, body) } else { ctr.n = n fmt.Fprint(w, "counter reset\n") } } fmt.Fprintf(w, "counter = %d\n", ctr.n) } func FlagServer(w http.ResponseWriter, req *http.Request) { w.Header().Set("Content-Type", "text/plain; charset=utf-8") fmt.Fprint(w, "Flags:\n") flag.VisitAll(func(f *flag.Flag) { if f.Value.String() != f.DefValue { fmt.Fprintf(w, "%s = %s [default = %s]\n", f.Name, f.Value.String(), f.DefValue) } else { fmt.Fprintf(w, "%s = %s\n", f.Name, f.Value.String()) } }) } // simple argument server func ArgServer(w http.ResponseWriter, req *http.Request) { for _, s := range os.Args { fmt.Fprint(w, s, " ") } } func ChanCreate() Chan { c := make(Chan) go func(c Chan) { for x := 0; ; x++ { c <- x } }(c) return c } func (ch Chan) ServeHTTP(w http.ResponseWriter, req *http.Request) { io.WriteString(w, fmt.Sprintf("channel send #%d\n", <-ch)) } // exec a program, redirecting output func DateServer(rw http.ResponseWriter, req *http.Request) { rw.Header().Set("Content-Type", "text/plain; charset=utf-8") r, w, err := os.Pipe() if err != nil { fmt.Fprintf(rw, "pipe: %s\n", err) return } p, err := os.StartProcess("/bin/date", []string{"date"}, &os.ProcAttr{Files: []*os.File{nil, w, w}}) defer r.Close() w.Close() if err != nil { fmt.Fprintf(rw, "fork/exec: %s\n", err) return } defer p.Release() io.Copy(rw, r) wait, err := p.Wait() if err != nil { fmt.Fprintf(rw, "wait: %s\n", err) return } if !wait.Exited() { fmt.Fprintf(rw, "date: %v\n", wait) return } }
Remark: Change line 43 to
err := http.ListenAndServe(":12345", nil)
, if you’re running it locally. ...