Servers

The other side to building a client is a Web server handling HTTP requests. The simplest - and earliest - servers just returned copies of files. However, any URL can now trigger an arbitrary computation in current servers.

File server

We start with a basic file server. Go supplies a multi-plexer, that is, an object that will read and interpret requests. It hands out requests to handlers which run in their own thread. Thus much of the work of reading HTTP requests, decoding them and branching to suitable functions in their own thread is done for us.

For a file server, Go also gives a FileServer object which knows how to deliver files from the local file system. It takes a “root” directory which is the top of a file tree in the local system, and a pattern to match URLs against. The simplest pattern is “/“ which is the top of any URL. This will match all URLs.

An HTTP server delivering files from the local file system is almost embarrassingly trivial given these objects. It is

  1. /* File Server
  2. */
  3. package main
  4. import (
  5. "fmt"
  6. "net/http"
  7. "os"
  8. )
  9. func main() {
  10. // deliver files from the directory /var/www
  11. //fileServer := http.FileServer(http.Dir("/var/www"))
  12. fileServer := http.FileServer(http.Dir("/home/httpd/html/"))
  13. // register the handler and deliver requests to it
  14. err := http.ListenAndServe(":8000", fileServer)
  15. checkError(err)
  16. // That's it!
  17. }
  18. func checkError(err error) {
  19. if err != nil {
  20. fmt.Println("Fatal error ", err.Error())
  21. os.Exit(1)
  22. }
  23. }

This server even delivers "404 not found" messages for requests for file resources that don’t exist!

Handler functions

In this last program, the handler was given in the second argument to ListenAndServe. Any number of handlers can be registered first by calls to Handle or HandleFunc, with signatures

  1. func Handle(pattern string, handler Handler)
  2. func HandleFunc(pattern string, handler func(*Conn, *Request))

The second argument to HandleAndServe could be nil, and then calls are dispatched to all registered handlers. Each handler should have a different URL pattern. For example, the file handler might have URL pattern “/“ while a function handler might have URL pattern “/cgi-bin”. A more specific pattern takes precedence over a more general pattern.

Common CGI programs are test-cgi (written in the shell) or printenv (written in Perl) which print the values of the environment variables. A handler can be written to work in a similar manner.

  1. /* Print Env
  2. */
  3. package main
  4. import (
  5. "fmt"
  6. "net/http"
  7. "os"
  8. )
  9. func main() {
  10. // file handler for most files
  11. fileServer := http.FileServer(http.Dir("/var/www"))
  12. http.Handle("/", fileServer)
  13. // function handler for /cgi-bin/printenv
  14. http.HandleFunc("/cgi-bin/printenv", printEnv)
  15. // deliver requests to the handlers
  16. err := http.ListenAndServe(":8000", nil)
  17. checkError(err)
  18. // That's it!
  19. }
  20. func printEnv(writer http.ResponseWriter, req *http.Request) {
  21. env := os.Environ()
  22. writer.Write([]byte("<h1>Environment</h1>\n<pre>"))
  23. for _, v := range env {
  24. writer.Write([]byte(v + "\n"))
  25. }
  26. writer.Write([]byte("</pre>"))
  27. }
  28. func checkError(err error) {
  29. if err != nil {
  30. fmt.Println("Fatal error ", err.Error())
  31. os.Exit(1)
  32. }
  33. }

Note: for simplicity this program does not deliver well-formed HTML. It is missing html, head and body tags.

Using the cgi-bin directory in this program is a bit cheeky: it doesn’t call an external program like CGI scripts do. It just calls a Go function. Go does have the ability to call external programs using os.ForkExec, but does not yet have support for dynamically linkable modules like Apache’s mod_perl.

Bypassing the default multiplexer

HTTP requests received by a Go server are usually handled by a multiplexer the examines the path in the HTTP request and calls the appropriate file handler, etc. You can define your own handlers. These can either be registered with the default multiplexer by calling http.HandleFunc which takes a pattern and a function. The functions such as ListenAndServe then take a nil handler function. This was done in the last example.

If you want to take over the multiplexer role then you can give a non-zero function as the handler function. This function will then be totally responsible for managing the requests and responses.

The following example is trivial, but illustrates the use of this: the multiplexer function simply returns a "204 No content" for all requests:

  1. /* ServerHandler
  2. */
  3. package main
  4. import (
  5. "net/http"
  6. )
  7. func main() {
  8. myHandler := http.HandlerFunc(func(rw http.ResponseWriter, request *http.Request) {
  9. // Just return no content - arbitrary headers can be set, arbitrary body
  10. rw.WriteHeader(http.StatusNoContent)
  11. })
  12. http.ListenAndServe(":8080", myHandler)
  13. }

Arbitrarily complex behaviour can be built, of course.

Low-level servers

Go also supplies a lower-level interface for servers. Again, this means that as the programmer you have to do more work. You first make a TCP server, and then wrap a ServerConn around it. Then you read Request‘s and write Response‘s.

Conclusion

Go has extensive support for HTTP. This is not surprising, since Go was partly invented to fill a need by Google for their own servers.