An Elaborate Web Server
Explore how to build a sophisticated web server in Go by implementing various handler functions, managing errors effectively, and using Go's expvar package to expose server variables. This lesson helps you understand server setup, request handling, logging, and how to serve files and dynamic content with concurrency.
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.
- Lines 31-42: We define all of the handlers, which are used by this webserver.
- Line 34: We define a new
Countervariable namedctr. - Line 36: Then, we publish
ctr, which is global to the webserver.
- Line 34: We define a new
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. ...