首先创建一个Response结构,接着将数据存储到这个结构里面,最后将这个结构返回给客户端——如果你认为服务器是通过这种方式向客户端返回响应的,那么你就想多了:服务器在向客户端返回响应的时候,真正需要用到的是ResponseWriter接口。

ResponseWriter是一个接口,处理器可以通过这个接口创建HTTP响应。ResponseWriter在创建响应时会用到http.response结构,因为该结构是一个非导出(nonexported)的结构,所以用户只能通过ResponseWriter来使用这个结构,而不能直接使用它。

为什么要以传值的方式将ResponseWriter传递给ServeHTTP

在阅读了本章前面的内容之后,有的读者可能会感到疑惑——ServeHTTP为什么要接受ResponseWriter接口和一个指向Request结构的指针作为参数呢?

接受Request结构指针的原因很简单:为了让服务器能够察觉到处理器对Request结构的修改,我们必须以传引用(pass by reference)而不是传值(pass by value)的方式传递Request结构。

但是另一方面,为什么ServeHTTP却是以传值的方式接受ResponseWriter呢?难道服务器不需要知道处理器对ResponseWriter所做的修改吗?

对于这个问题,如果我们深入探究net/http库的源码,就会发现ResponseWriter实际上就是response这个非导出结构的接口,而ResponseWriter在使用response结构时,传递的也是指向response结构的指针,这也就是说,ResponseWriter是以传引用而不是传值的方式在使用response结构。

换句话说,实际上ServeHTTP函数的两个参数传递的都是引用而不是值——虽然ResponseWriter看上去像是一个值,但它实际上却是一个带有结构指针的接口。

ResponseWriter接口拥有以下3个方法:

  • Write
  • WriteHeader
  • Header

ResponseWriter进行写入

Write方法接受一个字节数组作为参数,并将数组中的字节写入HTTP响应的主体中。如果用户在使用Write方法执行写入操作的时候,没有为首部设置相应的内容类型,那么响应的内容类型将通过检测被写入的前512字节决定。代码清单4-8展示了Write方法的用法。

代码清单4-8 使用Write方法向客户端发送响应

package main

import (
    "net/http"
)

func writeExample(w http.ResponseWriter, r *http.Request) {    
    str := `<html><head><title>Go Web Programming</title></head>
        <body><h1>Hello World</h1></body></html>`
    w.Write([]byte(str))
}

func main() {    
    server := http.Server{
        Addr: "127.0.0.1:8080",
        }

    http.HandleFunc("/write", writeExample)
    server.ListenAndServe()
}

这段代码通过调用Write方法将一段HTML字符串写入了HTTP响应的主体中。通过向服务器发送以下命令:

curl -i 127.0.0.1:8080/write

我们可以得到以下响应:

HTTP/1.1 200 OK
Date: Tue, 13 Jan 2015 16:16:13 GMT
Content-Length: 95
Content-Type: text/html; charset=utf-8
<html>
<head><title>GoWebProgramming</title></head>
<body><h1>Hello World</h1></body>
</html>

注意,尽管我们没有亲自为响应设置内容类型,但程序还是通过检测自动设置了正确的内容类型。

WriteHeader方法的名字带有一点儿误导性质,它并不能用于设置响应的首部(Header方法才是做这件事的):WriteHeader方法接受一个代表HTTP响应状态码的整数作为参数,并将这个整数用作HTTP响应的返回状态码;在调用这个方法之后,用户可以继续对ResponseWriter进行写入,但是不能对响应的首部做任何写入操作。如果用户在调用Write方法之前没有执行过WriteHeader方法,那么程序默认会使用200 OK作为响应的状态码。

WriteHeader方法在返回错误状态码时特别有用:如果你定义了一个API,但是尚未为其编写具体的实现,那么当客户端访问这个API的时候,你可能会希望这个API返回一个501NotImplemented,状态码,代码清单4-9通过添加新的处理器实现了这一需求。顺带一提,千万别忘了使用HandleFunc方法将新处理器绑定到DefaultServeMux多路复用器里面!

代码清单4-9 通过WriteHeader方法将状态码写入到响应当中

package main

import (    
    "fmt"
    "net/http"
)

func writeExample(w http.ResponseWriter, r *http.Request) {   
    str := `<html><head><title>Go Web Programming</title></head><body><h1>Hello World</h1></body></html>`    
    w.Write([]byte(str))
}

func writeHeaderExample(w http.ResponseWriter, r *http.Request) {    
    w.WriteHeader(501)
    fmt.Fprintln(w, "No such service, try next door")
}

func main() {    
    server := http.Server{        
        Addr: "127.0.0.1:8080",
    }

    http.HandleFunc("/write", writeExample)    
    http.HandleFunc("/writeheader", writeHeaderExample)    
    server.ListenAndServe()
}

通过curl访问刚刚添加的新处理器:

curl -i 127.0.0.1:8080/writeheader

我们将得到以下响应:

HTTP/1.1 501 Not Implemented
Date: Tue, 13 Jan 2015 16:20:29 GMT
Content-Length: 31
Content-Type: text/plain; charset=utf-8
No such service, try next door

最后,通过调用Header方法可以取得一个由首部组成的映射(关于首部的具体细节在4.1.3节曾经讲过),修改这个映射就可以俢改首部,修改后的首部将被包含在HTTP响应里面,并随着响应一同发送至客户端。

代码清单4-10 通过编写首部实现客户端重定向

package main

import (    
    "fmt"    
    "net/http"
)

func writeExample(w http.ResponseWriter, r *http.Request) {    
    str := `<html><head><title>Go Web Programming</title></head><body><h1>Hello World</h1></body></html>`    
    w.Write([]byte(str))
}

func writeHeaderExample(w http.ResponseWriter, r *http.Request) {    
    w.WriteHeader(501)    
    fmt.Fprintln(w, "No such service, try next door")
}

func headerExample(w http.ResponseWriter, r *http.Request) {    
    w.Header().Set("Location", "http://google.com")    
    w.WriteHeader(302)
}

func main() {    
    server := http.Server{        
        Addr: "127.0.0.1:8080",
    }

    http.HandleFunc("/write", writeExample)    
    http.HandleFunc("/writeheader", writeHeaderExample)    
    http.HandleFunc("/redirect", headerExample)    
    server.ListenAndServe()
}

代码清单4-10向我们展示了如何实现一次HTTP重定向:除了将状态码设置成了302之外,它还给响应添加了一个名为Location的首部,并将这个首部的值设置成了重定向的目的地。需要注意的是,因为WriteHeader方法在执行完毕之后就不允许再对首部进行写入了,所以用户必须先写入Location首部,然后再写入状态码。现在,如果我们在浏览器里面访问这个处理器,那么浏览器将被重定向到Google。

另一方面,如果我们使用curl访问这个处理器:

curl -i 127.0.0.1:8080/redirect

那么curl将获得以下响应:

HTTP/1.1 302 Found
Location: http://google.com
Date: Tue, 13 Jan 2015 16:22:16 GMT
Content-Length: 0
Content-Type: text/plain; charset=utf-8

最后,让我们来学习一下通过ResponseWriter直接向客户端返回JSON数据的方法。代码清单4-11展示了如何以JSON格式将一个名为Post的结构返回给客户端。

代码清单4-11 编写JSON输出

package main

import (    
    "fmt"    
    "encoding/json"    
    "net/http"
)

type Post struct {    
    User    string    
    Threads []string
}

func writeExample(w http.ResponseWriter, r *http.Request) {    
    str := `<html><head><title>Go Web Programming</title></head><body><h1>Hello World</h1></body></html>`    
    w.Write([]byte(str))
}

func writeHeaderExample(w http.ResponseWriter, r *http.Request) {    
    w.WriteHeader(501)    
    fmt.Fprintln(w, "No such service, try next door")
}

func headerExample(w http.ResponseWriter, r *http.Request) {    
    w.Header().Set("Location", "http://google.com")    
    w.WriteHeader(302)
}

func jsonExample(w http.ResponseWriter, r *http.Request) {    
    w.Header().Set("Content-Type", "application/json")    
    post := &Post{        
        User:    "Sau Sheong",        
        Threads: []string{"first", "second", "third"},    
    }
    json, _ := json.Marshal(post)    
    w.Write(json)
}

func main() {    
    server := http.Server{      
        Addr: "127.0.0.1:8080",    
    }

    http.HandleFunc("/write", writeExample)    
    http.HandleFunc("/writeheader", writeHeaderExample)    
    http.HandleFunc("/redirect", headerExample)    
    http.HandleFunc("/json", jsonExample)    
    server.ListenAndServe()
}

这段代码中的jsonExample处理器就是这次的主角。因为本书将在第7章进一步介绍JSON格式,所以不了解JSON格式的读者也不必过于担心,目前来说,你只需要知道变量json是一个由Post结构序列化而成的JSON字符串就可以了。

这段程序首先使用Header方法将内容类型设置成application/json,然后调用Write方法将JSON字符串写入ResponseWriter中。现在,如果我们执行curl命令:

curl -i 127.0.0.1:8080/json

那么它将返回以下响应:

HTTP/1.1 200 OK
Content-Type: application/json
Date: Tue, 13 Jan 2015 16:27:01 GMT
Content-Length: 58

{"User":"Sau Sheong","Threads":["first","second","third"]}

results matching ""

    No results matching ""