Go - HTTP Server
Default HTTP server. Here is an example HTTP server that tells you what time it is. By calling the default functions of net/http
package (e.g. HandleFunc
here), we are using default values in that package e.g. DefaultServeMux
.
package main
import (
"fmt"
"net/http"
"time"
)
func main() {
http.HandleFunc("/", indexHandler)
err := http.ListenAndServe(":8080", nil)
if err != nil {
panic(err)
}
}
func indexHandler(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "Time is %s.", time.Now())
}
Custom HTTP server. Here is more verbose version of the previous minimalistic HTTP server that illustrates what is happening behind the scenes.
package main
import (
"fmt"
"net"
"net/http"
"time"
)
func main() {
// Create new ServeMux to handle routing HTTP request to the right handlers.
mux := http.NewServeMux()
mux.HandleFunc("/", indexHandler)
// Create new HTTP Server.
srv := http.Server{Addr: ":8080", Handler: mux}
// Start listening local TCP connections on the port specified in server.
listener, errListen := net.Listen("tcp", srv.Addr)
if errListen != nil {
panic(errListen)
}
// Link the listener with the server, start accepting incoming connections
// from listener and creating new handler goroutine for each connection.
errServe := srv.Serve(listener)
if errServe != nil {
panic(errServe)
}
}
func indexHandler(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "Time is %s.", time.Now())
// "Forward Print Formatted" to ResponseWriter w.
}
Make at least port configurable. Command-line arguments or a config file.
package main
import (
"flag"
"fmt"
"net/http"
"time"
)
var port int
// init() is always ran when a package is ran or imported.
// main() is only ran when when a package is ran.
func init() {
// $ go build server.go
// $ server_executable -help
// Usage of /server_executable:
// -port=8080: HTTP server port.
// $ server_executable
// Listening to :8080.
// $ server_executable -port 80
// Listening to :80.
// OR:
// go run server.go -port 8090
flag.IntVar(&port, "port", 8080, "HTTP server port.")
flag.Parse() // Look for all defined flags in command-line arguments.
}
func main() {
http.HandleFunc("/", indexHandler)
portWithColon := fmt.Sprintf(":%d", port)
fmt.Printf("Listening to %s.\n", portWithColon)
err := http.ListenAndServe(portWithColon, nil)
if err != nil {
panic(err)
}
}
func indexHandler(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "Time is %s.", time.Now())
}
Serve directories. Serve a static directory that would include CSS/JS.
package main
import (
"flag"
"fmt"
"net/http"
"time"
)
var (
staticDir string
staticUrl string
)
func init() {
// Usage:
// go run server.go -staticdir="./teststatic" -staticurl="/public/"
flag.StringVar(&staticDir, "staticdir", "./static", "Serve this directory.")
flag.StringVar(&staticUrl, "staticurl", "/static/", "Serve to this URL.")
flag.Parse()
}
func main() {
http.HandleFunc("/", indexHandler)
// Recursively serve the given directory.
fileServer := http.FileServer(http.Dir(staticDir))
fileHandler := http.StripPrefix(staticUrl, fileServer)
http.Handle(staticUrl, fileHandler)
portWithColon := fmt.Sprintf(":%d", 8080)
fmt.Printf("Listening to %s.\n", portWithColon)
err := http.ListenAndServe(portWithColon, nil)
if err != nil {
panic(err)
}
}
func indexHandler(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "Time is %s.", time.Now())
}
Redirects. Redirect handler to /redirect-me
URL.
package main
import (
"fmt"
"net/http"
"time"
)
func main() {
http.HandleFunc("/", indexHandler)
// Redirect visitors to given URL using given HTTP code.
http.Handle("/redirect-me", http.RedirectHandler("http://google.com", 302))
portWithColon := fmt.Sprintf(":%d", 8080)
fmt.Printf("Listening to %s.\n", portWithColon)
err := http.ListenAndServe(portWithColon, nil)
if err != nil {
panic(err)
}
}
func indexHandler(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "Time is %s.", time.Now())
}
Add timeout to a specific handler.
package main
import (
"flag"
"fmt"
"net/http"
"time"
)
const (
timeoutDur = time.Duration(time.Second)
timeoutMsg = "Your request has timed out."
)
var (
port int
staticDir string
staticUrl string
)
type BadIndexHandler struct{}
func (self *BadIndexHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
time.Sleep(timeoutDur)
fmt.Fprintf(w, "Time is %s.", time.Now())
}
func init() {
flag.IntVar(&port, "port", 8080, "HTTP server port.")
flag.StringVar(&staticDir, "staticdir", "./static", "Serve this directory.")
flag.StringVar(&staticUrl, "staticurl", "static", "Serve to this URL.")
flag.Parse()
staticUrl = fmt.Sprintf("/%s/", staticUrl)
}
func main() {
// http.TimeoutHandler() runs given handler (here BadIndexHandler) for
// given duration and fails if not completed in that time.
indexHandler := &BadIndexHandler{}
http.Handle("/", http.TimeoutHandler(indexHandler, timeoutDur, timeoutMsg))
fileSrv := http.FileServer(http.Dir(staticDir))
staticHandler := http.StripPrefix(staticUrl, fileSrv)
http.Handle(staticUrl, staticHandler)
http.Handle("/redirect-me", http.RedirectHandler("http://google.com", 302))
portWithColon := fmt.Sprintf(":%d", port)
fmt.Printf("Listening to %s.\n", portWithColon)
err := http.ListenAndServe(portWithColon, nil)
if err != nil {
panic(err)
}
}
Add serving JSON data and manipulating headers.
package main
import (
"encoding/json"
"flag"
"fmt"
"net/http"
"strconv"
"time"
)
var (
port int
staticDir string
staticUrl string
)
func init() {
flag.IntVar(&port, "port", 8080, "HTTP server port.")
flag.StringVar(&staticDir, "staticdir", "./static", "Serve this directory.")
flag.StringVar(&staticUrl, "staticurl", "static", "Serve to this URL.")
flag.Parse()
staticUrl = fmt.Sprintf("/%s/", staticUrl)
}
func main() {
http.HandleFunc("/", indexHandler)
http.HandleFunc("/json", jsonHandler)
http.HandleFunc("/header", headerHandler)
fileSrv := http.FileServer(http.Dir(staticDir))
staticHandler := http.StripPrefix(staticUrl, fileSrv)
http.Handle(staticUrl, staticHandler)
http.Handle("/redirect-me", http.RedirectHandler("http://google.com", 302))
portWithColon := fmt.Sprintf(":%d", port)
fmt.Printf("Listening to %s.\n", portWithColon)
err := http.ListenAndServe(portWithColon, nil)
if err != nil {
panic(err)
}
}
func indexHandler(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "Time is %s.", time.Now())
}
func jsonHandler(w http.ResponseWriter, r *http.Request) {
encoder := json.NewEncoder(w)
respData := struct {
ServerPort int
StaticDir string
StaticUrl string
Timestamp time.Time
}{}
respData.ServerPort = port
respData.StaticDir = staticDir
respData.StaticUrl = staticUrl
respData.Timestamp = time.Now()
if err := encoder.Encode(&respData); err != nil {
http.Error(w, fmt.Sprintf("Cannot encode response data: %v", err), 500)
}
}
func headerHandler(w http.ResponseWriter, r *http.Request) {
ua := r.Header.Get("User-Agent")
msg := fmt.Sprintf("Your user agent is: %v\n", ua)
w.Header().Set("Content-Type", "text/plain")
w.Header().Set("Content-Length", strconv.Itoa(len(msg)))
w.Header().Set("Go-Timestamp", fmt.Sprintf("%s.", time.Now()))
fmt.Fprint(w, msg)
}
Cookies. Adding and reading cookies.
package main
import (
"encoding/json"
"flag"
"fmt"
"net/http"
"strconv"
"time"
)
var (
port int
staticDir string
staticUrl string
)
func init() {
flag.IntVar(&port, "port", 8080, "HTTP server port.")
flag.StringVar(&staticDir, "staticdir", "./static", "Serve this directory.")
flag.StringVar(&staticUrl, "staticurl", "static", "Serve to this URL.")
flag.Parse()
staticUrl = fmt.Sprintf("/%s/", staticUrl)
}
func main() {
http.HandleFunc("/", indexHandler)
http.HandleFunc("/json", jsonHandler)
http.HandleFunc("/header", headerHandler)
http.HandleFunc("/add-cookie", addCookieHandler)
http.HandleFunc("/read-cookie", readCookieHandler)
fileSrv := http.FileServer(http.Dir(staticDir))
staticHandler := http.StripPrefix(staticUrl, fileSrv)
http.Handle(staticUrl, staticHandler)
http.Handle("/redirect-me", http.RedirectHandler("http://google.com", 302))
portWithColon := fmt.Sprintf(":%d", port)
fmt.Printf("Listening to %s.\n", portWithColon)
err := http.ListenAndServe(portWithColon, nil)
if err != nil {
panic(err)
}
}
func indexHandler(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "Time is %s.", time.Now())
}
func jsonHandler(w http.ResponseWriter, r *http.Request) {
encoder := json.NewEncoder(w)
respData := struct {
ServerPort int
StaticDir string
StaticUrl string
Timestamp time.Time
}{}
respData.ServerPort = port
respData.StaticDir = staticDir
respData.StaticUrl = staticUrl
respData.Timestamp = time.Now()
if err := encoder.Encode(&respData); err != nil {
http.Error(w, fmt.Sprintf("Cannot encode response data: %v", err), 500)
}
}
func headerHandler(w http.ResponseWriter, r *http.Request) {
ua := r.Header.Get("User-Agent")
msg := fmt.Sprintf("Your user agent is: %v\n", ua)
w.Header().Set("Content-Type", "text/plain")
w.Header().Set("Content-Length", strconv.Itoa(len(msg)))
w.Header().Set("Go-Timestamp", fmt.Sprintf("%s.", time.Now()))
fmt.Fprint(w, msg)
}
func addCookieHandler(w http.ResponseWriter, r *http.Request) {
cookie := &http.Cookie{
Name: "cookie-name",
Value: "cookie-value",
}
http.SetCookie(w, cookie)
}
func readCookieHandler(w http.ResponseWriter, r *http.Request) {
if cookie, err := r.Cookie("cookie-name"); err != nil {
http.Error(w, "Failed to read the cookie.", 500)
} else {
fmt.Fprintf(w, "Cookie value is %s.", cookie.Value)
}
}
Communication between requests. As each requests starts new goroutine, it is not safe to change any public variables, prefer using channels.
package main
import (
"fmt"
"net/http"
"time"
)
type indexHandler struct {
visitChan chan bool
}
func (self *indexHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
self.visitChan <- true
fmt.Fprintf(w, "Time is %s.", time.Now())
}
func main() {
var visitChan = make(chan bool)
go countVisits(visitChan)
http.Handle("/", &indexHandler{visitChan})
err := http.ListenAndServe(":8080", nil)
if err != nil {
panic(err)
}
}
func countVisits(visitChan chan bool) {
var visits int
for {
if <-visitChan {
visits++
fmt.Printf(" %d visits\n", visits)
}
}
}