Simple user-agents

User agents such as browsers make requests and get responses. The response type is

  1. type Response struct {
  2. Status string // e.g. "200 OK"
  3. StatusCode int // e.g. 200
  4. Proto string // e.g. "HTTP/1.0"
  5. ProtoMajor int // e.g. 1
  6. ProtoMinor int // e.g. 0
  7. RequestMethod string // e.g. "HEAD", "CONNECT", "GET", etc.
  8. Header map[string]string
  9. Body io.ReadCloser
  10. ContentLength int64
  11. TransferEncoding []string
  12. Close bool
  13. Trailer map[string]string
  14. }

We shall examine this data structure through examples. The simplest request is from a user agent is “HEAD” which asks for information about a resource and its HTTP server. The function

  1. func Head(url string) (r *Response, err os.Error)

can be used to make this query.

The status of the response is in the response field Status, while the field Header is a map of the header fields in the HTTP response. A program to make this request and display the results is

  1. /* Head
  2. */
  3. package main
  4. import (
  5. "fmt"
  6. "net/http"
  7. "os"
  8. )
  9. func main() {
  10. if len(os.Args) != 2 {
  11. fmt.Println("Usage: ", os.Args[0], "host:port")
  12. os.Exit(1)
  13. }
  14. url := os.Args[1]
  15. response, err := http.Head(url)
  16. if err != nil {
  17. fmt.Println(err.Error())
  18. os.Exit(2)
  19. }
  20. fmt.Println(response.Status)
  21. for k, v := range response.Header {
  22. fmt.Println(k+":", v)
  23. }
  24. os.Exit(0)
  25. }

When run against a resource as in Head http://www.golang.com/ it prints something like

  1. 200 OK
  2. Content-Type: text/html; charset=utf-8
  3. Date: Tue, 14 Sep 2015 05:34:29 GMT
  4. Cache-Control: public, max-age=3600
  5. Expires: Tue, 14 Sep 2015 06:34:29 GMT
  6. Server: Google Frontend

Usually, we are want to retrieve a resource rather than just get information about it. The “GET” request will do this, and this can be done using

  1. func Get(url string) (r *Response, finalURL string, err os.Error)

The content of the response is in the response field Body which is of type io.ReadCloser. We can print the content to the screen with the following program

  1. /* Get
  2. */
  3. package main
  4. import (
  5. "fmt"
  6. "net/http"
  7. "net/http/httputil"
  8. "os"
  9. "strings"
  10. )
  11. func main() {
  12. if len(os.Args) != 2 {
  13. fmt.Println("Usage: ", os.Args[0], "host:port")
  14. os.Exit(1)
  15. }
  16. url := os.Args[1]
  17. response, err := http.Get(url)
  18. if err != nil {
  19. fmt.Println(err.Error())
  20. os.Exit(2)
  21. }
  22. if response.Status != "200 OK" {
  23. fmt.Println(response.Status)
  24. os.Exit(2)
  25. }
  26. b, _ := httputil.DumpResponse(response, false)
  27. fmt.Print(string(b))
  28. contentTypes := response.Header["Content-Type"]
  29. if !acceptableCharset(contentTypes) {
  30. fmt.Println("Cannot handle", contentTypes)
  31. os.Exit(4)
  32. }
  33. var buf [512]byte
  34. reader := response.Body
  35. for {
  36. n, err := reader.Read(buf[0:])
  37. if err != nil {
  38. os.Exit(0)
  39. }
  40. fmt.Print(string(buf[0:n]))
  41. }
  42. os.Exit(0)
  43. }
  44. func acceptableCharset(contentTypes []string) bool {
  45. // each type is like [text/html; charset=UTF-8]
  46. // we want the UTF-8 only
  47. for _, cType := range contentTypes {
  48. if strings.Index(cType, "UTF-8") != -1 {
  49. return true
  50. }
  51. }
  52. return false
  53. }

Note that there are important character set issues of the type discussed in the previous chapter. The server will deliver the content using some character set encoding, and possibly some transfer encoding. Usually this is a matter of negotiation between user agent and server, but the simple Get command that we are using does not include the user agent component of the negotiation. So the server can send whatever character encoding it wishes.

At the time of first writing, I was in China. When I tried this program on www.google.com, Google’s server tried to be helpful by guessing my location and sending me the text in the Chinese character set Big5! How to tell the server what character encoding is okay for me is discussed later.