1. Setting the order of WriteHeader

I had a problem before, setting WriteHeader in a piece of code like this, and finally getting Name in the header was impossible.


w.WriteHeader(201)
w.Header().Set("Name", "my name is smallsoup")

When writing HTTP server with golang, it is easy to set the content of header in HTTP response through w. Header. Set (k, v). But it’s important to note that sometimes you need to modify not only the header of the response, but also the StatusCode of the response. Modifying StatusCode of response can be achieved by: w. WriteHeader (code), for example:


w.WriteHeader(404)

If these two modifications are to be done together, the w. WriteHeader must be made after all w. Header. Sets, because the set header after the w. WriteHeader is invalid.

And it must be before w. Write ([] byte (“Hello World”), otherwise it will report http: multiple response. WriteHeader calls because the WriteHeader () method will be called when w. Write is actually called, then the w. wroteHeader will be set to true, and the WriteHeader () will be adjusted again to judge the header, if it is true, it will report an error, and this call will not take effect.

You can see the following source code to show that WriteHeader must be invoked before Write.

func (w *response) WriteHeader(code int) {
 if w.conn.hijacked() {
 w.conn.server.logf("http: response.WriteHeader on hijacked connection")
 return
 }
// The second WriteHeader () comes in to satisfy the if condition and returns the error directly.
 if w.wroteHeader {
 w.conn.server.logf("http: multiple response.WriteHeader calls")
 return
 }
// The first time write () comes in, the w. wroteHeader will be set to true.
 w.wroteHeader = true
 w.status = code

 if w.calledHeader && w.cw.header == nil {
 w.cw.header = w.handlerHeader.clone()
 }

 if cl := w.handlerHeader.get("Content-Length"); cl != "" {
 v, err := strconv.ParseInt(cl, 10, 64)
 if err == nil && v >= 0 {
 w.contentLength = v
 } else {
 w.conn.server.logf("http: invalid Content-Length of %q", cl)
 w.handlerHeader.Del("Content-Length")
 }
 }
}

2. go will normalize key in header

Go normalizes keys in headers, so be careful when getting K and V values in response headers.

There is a section in reader.go’s non-export method canonical MIME Header Key that normalizes the header’s key.

1) The isTokenTable array is defined in reader. go. If the key is longer than 127 or contains characters that are not in the isTokenTable, the key will not be processed.

2) Capitalize the first letter of the key and the first letter of the word after the character.

Analyzing the following source code can explain the capitalization of key:

for i, c := range a {
 // Normalization: capital letters
 //- The first letter of the list is capitalized
 // For example: (Host, User-Agent, If-Modified-Since).
 if upper && 'a' <= c && c <= 'z' {
 // Capital to lowercase
 c -= toLower
 } else if !upper && 'A' <= c && c <= 'Z' {
 // Down-to-uppercase
 c += toLower
 }
 // Reassign the key array
 a[i] = c
 // Setting Upper and Lower Case Flags
 upper = c == '-' // for next time
}

Correct invocation method:

Server: myServer.go


package main

import (
 "net/http"
)

func main() {

 http.HandleFunc("/", func (w http.ResponseWriter, r *http.Request){


 w.Header().Set("name", "my name is smallsoup")
 w.WriteHeader(500)
 w.Write([]byte("hello world\n"))

 })

 http.ListenAndServe(":8080", nil)
}

Client:

myHttp.go:


package main

import (
 "fmt"
 "io/ioutil"
 "net/http"
)

func main() {

 myHttpGet()

}

func myHttpGet() {

 rsp, err := http.Get("http://localhost:8080")
 if err != nil {
 fmt.Println("myHttpGet error is ", err)
 return
 }

 defer rsp.Body.Close()
 body, err := ioutil.ReadAll(rsp.Body)
 if err != nil {
 fmt.Println("myHttpGet error is ", err)
 return
 }

 fmt.Println("response statuscode is ", rsp.StatusCode, 
 "\nhead[name]=", rsp.Header["Name"], 
 "\nbody is ", string(body))
}

1. Running servers


go run myServer.go

2. Running the Client


go run myHttp.go

The output is as follows: statuscode is the 500 we set, and Name gets the value.

Golang’s handling of HTTP response problems and points for attention

When dealing with HTTP response, I happened to find that when I wanted to read the body again after reading it, I could not read anything. See the following code:


response, err = ioutil.ReadAll(resp.Body)
if err != nil {
log.Println("ioutil ReadAll failed :", err.Error())
return
}

Then if you want to do it againioutil.ReadAll(resp.Body)You will find that what you read is empty. So I decided to take a look at this.resp.BodyFind it’s aio.ReadCloserInterfaces, including Reader and Loser interfaces:


type ReadCloser interface {
Reader
Closer
}

So I thought of the file. It also implements the io. Reader interface, so I tried to read the file.


func readFile(path string)string{
fi,err := os.Open(path)
if err != nil{panic(err)}
defer fi.Close()

byte1,err := ioutil.ReadAll(fi)
fmt.Println(string(byte1))

byte2,err := ioutil.ReadAll(fi)
fmt.Println(string(byte2))

return string(fd)
}

The results are consistent.fmt.Println(string(fd2))No result can be printed. I guess it should beioutil.ReadAll()There is a record offset, so there will be a second read can not read the situation. When dealing with response as client side, we will encounter this problem. When dealing with request body as server side, we will also encounter this problem. So how to solve this problem?
One way is to recreate oneio.ReadCloserAs follows:


fi2:= ioutil.NopCloser(bytes.NewBuffer(byte1))
byte3,err := ioutil.ReadAll(fi2)
fmt.Println(string(byte3))

In addition, when dealing with response as client side, it is important to note that the body must be close, otherwise, GC can not be reclaimed, resulting in memory leaks. In fact, in the official source comment of go, it is also clearly stated that the response body needs to be manually closed by the caller:It is the caller's responsibility to close Body.

As for why response body needs to be closed, this article explains: https://www.jb51.net/article/146275.htm

So the request body generated as the client side does not need to be closed manually. The answer is no, in net/http.func (c *Client) Do(req *Request) (*Response, error)Would callClose()

Similarly, the request body received as the server side also needs to be shut down, which is automatically shut down by the server.The Server will close the request body. The ServeHTTP Handler does not need to.

summary

Above is the whole content of this article. I hope that the content of this article has a certain reference value for everyone’s study or work. If you have any questions, you can leave a message and exchange it. Thank you for your support to developpaer.