关于response
在接收和解释请求消息后,服务器会返回一个 HTTP 响应消息,与 HTTP 请求类似,HTTP 响应也是由三个部分组成,分别是:状态行、消息报头和响应正文

代码解析
Response 结构体
Response表示来自HTTP请求的响应。 一旦收到响应头,客户机和传输将从服务器返回响应。 响应主体在读取主体字段时按需流式传输

type Response struct {
	Status     string // e.g. "200 OK"
	StatusCode int    // e.g. 200
	Proto      string // e.g. "HTTP/1.0"
	ProtoMajor int    // e.g. 1
	ProtoMinor int    // e.g. 0

	// Header将头键映射到值。
	//如果响应有多个具有相同密钥的头,则可以使用逗号限制将它们串联起来。
	//当此结构中的其他字段(如ContentLength、transferncode、Trailer)复制头值时,字段值是权威的。
	Header Header

	// Body代表响应体。
	//响应主体在读取主体字段时按需流式传输。
	//如果网络连接失败或服务器终止响应,Body.Read调用返回错误,http客户机和传输保证Body始终为非nil, 关闭响应体时调用者的责任
	// 如果响应体未读到完成并关闭,则默认HTTP客户端的传输可能不会重用HTTP/1.x“keep alive”TCP连接
	// 如果服务器用“chunked”传输编码答复,则响应体将自动取消dechunked
	Body io.ReadCloser

	// ContentLength记录关联内容的长度。值>=0表示可以从Body读取给定的字节数。
	ContentLength int64

	// 包含从最外层到最内层的传输编码。值为nil,表示使用“identity”编码。
	TransferEncoding []string

	//Close记录响应头是否指示在读取Body之后关闭连接(是为客户提供建议)
	Close bool

	// 报告响应是否已压缩发送,但已由http包解压缩
	// 如果为true,则从Body读取生成未压缩的内容,而不是从服务器实际设置的压缩内容,ContentLength设置为-1,并且从responseHeader中删除“content Length”和“content Encoding”字段
	// 一般是压缩方式,利用gzip压缩文档能够显著地减少HTML文档的下载时间。
	Uncompressed bool

	// 将Trailer 键映射到与标头相同格式的值,初始是nil,服务器的Trailer头值中,指定的每个键对应一个值
	Trailer Header

	// Request 请求是为获取此响应而发送的请求。请求的主体为零(已被消耗)这仅为客户端请求填充
	Request *Request

	// TLS包含有关接收响应的TLS连接的信息
	TLS *tls.ConnectionState
}

将响应对象 写入(入参)写入器

func (r *Response) Write(w io.Writer) error {
	// 状态行
	text := r.Status
	if text == "" {
		var ok bool
		text, ok = statusText[r.StatusCode]
		if !ok {
			text = "status code " + strconv.Itoa(r.StatusCode)
		}
	} else {
		// 为了减少stutter, 如果用户设置响应码为2000 ok, 状态码为200
		text = strings.TrimPrefix(text, strconv.Itoa(r.StatusCode)+" ")
	}

	if _, err := fmt.Fprintf(w, "HTTP/%d.%d %03d %s\r\n", r.ProtoMajor, r.ProtoMinor, r.StatusCode, text); err != nil {
		return err
	}

	// 克隆r,这样我们可以根据需要修改r1
	r1 := new(Response)
	*r1 = *r
	if r1.ContentLength == 0 && r1.Body != nil {
		// Is it actually 0 length? Or just unknown?
		var buf [1]byte
		n, err := r1.Body.Read(buf[:])
		if err != nil && err != io.EOF {
			return err
		}
		if n == 0 {
			// 将其重置为已知的零读取器,以防重复读取底层读取器
			r1.Body = NoBody
		} else {
			r1.ContentLength = -1
			r1.Body = struct {
				io.Reader
				io.Closer
			}{
				io.MultiReader(bytes.NewReader(buf[:1]), r.Body),
				r.Body,
			}
		}
	}
	//如果我们发送的是一个没有内容长度的non-chunked HTTP/1.1响应,唯一的方法就是使用旧的HTTP/1.0方式,通过在连接关闭时注意EOF,所以我们需要设置close
	if r1.ContentLength == -1 && !r1.Close && r1.ProtoAtLeast(1, 1) && !chunked(r1.TransferEncoding) && !r1.Uncompressed {
		r1.Close = true
	}

	// 进程主体,内容长度,关闭,Trailer
	tw, err := newTransferWriter(r1)
	if err != nil {
		return err
	}
	err = tw.writeHeader(w, nil)
	if err != nil {
		return err
	}

	// 响应头的剩余部分
	err = r.Header.WriteSubset(w, respExcludeHeader)
	if err != nil {
		return err
	}

	// Cordon RealTrayRead可能已经发送了POST/PUT请求,即使是零长度
	contentLengthAlreadySent := tw.shouldSendContentLength()
	if r1.ContentLength == 0 && !chunked(r1.TransferEncoding) && !contentLengthAlreadySent && bodyAllowedForStatus(r.StatusCode) {
		if _, err := io.WriteString(w, "Content-Length: 0\r\n"); err != nil {
			return err
		}
	}

	// 响应头的尾部
	if _, err := io.WriteString(w, "\r\n"); err != nil {
		return err
	}

	// 写响应体和 trailer
	err = tw.writeBody(w)
	if err != nil {
		return err
	}

	// 成功
	return nil
}

func (r *Response) closeBody() {
	if r.Body != nil {
		r.Body.Close()
	}
}

获取Cookies,Location,设置无缓存

// 解析并返回Set-Cookie头中设置的Cookies(所以其实是内部方法readSetCookies的封装)
func (r *Response) Cookies() []*Cookie {
	return readSetCookies(r.Header)
}

// 当不存在位置头时,响应的Location方法将返回ErrNoLocation
var ErrNoLocation = errors.New("http: no Location header in response")

// 返回响应的“Location”头的URL(如果存在,用于重定向一个新的位置, 包含新的URL地址)
// 相对重定向是相对于响应的请求来解决的。如果不存在位置标头,则返回ErrNoLocation。
func (r *Response) Location() (*url.URL, error) {
	lv := r.Header.Get("Location")
	if lv == "" {
		return nil, ErrNoLocation
	}
	if r.Request != nil && r.Request.URL != nil {
		return r.Request.URL.Parse(lv)
	}
	return url.Parse(lv)
}

// 从r读取器 读取并返回HTTP响应,req参数可选地指定与此响应相对应的请求,如果为nil,则假定为GET请求。
// 当完成读取响应体时,客户端必须调用 resp.Body.Close,之后,客户端可检查 resp.Trailer中包含的键值对
func ReadResponse(r *bufio.Reader, req *Request) (*Response, error) {
	tp := textproto.NewReader(r)
	resp := &Response{
		Request: req,
	}

	// Parse the first line of the response.
	line, err := tp.ReadLine()
	if err != nil {
		if err == io.EOF {
			err = io.ErrUnexpectedEOF
		}
		return nil, err
	}
	if i := strings.IndexByte(line, ' '); i == -1 {
		return nil, badStringError("malformed HTTP response", line)
	} else {
		resp.Proto = line[:i]
		resp.Status = strings.TrimLeft(line[i+1:], " ")
	}
	statusCode := resp.Status
	if i := strings.IndexByte(resp.Status, ' '); i != -1 {
		statusCode = resp.Status[:i]
	}
	if len(statusCode) != 3 {
		return nil, badStringError("malformed HTTP status code", statusCode)
	}
	resp.StatusCode, err = strconv.Atoi(statusCode)
	if err != nil || resp.StatusCode < 0 {
		return nil, badStringError("malformed HTTP status code", statusCode)
	}
	var ok bool
	if resp.ProtoMajor, resp.ProtoMinor, ok = ParseHTTPVersion(resp.Proto); !ok {
		return nil, badStringError("malformed HTTP version", resp.Proto)
	}

	// Parse the response headers.
	mimeHeader, err := tp.ReadMIMEHeader()
	if err != nil {
		if err == io.EOF {
			err = io.ErrUnexpectedEOF
		}
		return nil, err
	}
	resp.Header = Header(mimeHeader)

	fixPragmaCacheControl(resp.Header)

	err = readTransfer(resp, r)
	if err != nil {
		return nil, err
	}

	return resp, nil
}

// Pragma是为了防止响应页面被缓存,和 Cache-Control:no-cache作用一样的作用。(Pargma只有一个用法, 例如: Pragma: no-cache)
func fixPragmaCacheControl(header Header) {
	if hp, ok := header["Pragma"]; ok && len(hp) > 0 && hp[0] == "no-cache" {
		// Cache-Control用来指定Response-Request遵循的缓存机制,有三个方式:Public\Private\no-cache
		if _, presentcc := header["Cache-Control"]; !presentcc {
			header["Cache-Control"] = []string{"no-cache"}
		}
	}
}

对响应体对象的检查方法

// 报告响应中使用的HTTP协议是否至少是minor(0)
func (r *Response) ProtoAtLeast(major, minor int) bool {
	return r.ProtoMajor > major ||
		r.ProtoMajor == major && r.ProtoMinor >= minor
}

// 检查响应体 是否支持写入
func (r *Response) bodyIsWritable() bool {
	// 通过将 响应体转成 写入器类型判断
	_, ok := r.Body.(io.Writer)
	return ok
}

// 检查响应 是否是对成功的协议升级的响应
func (r *Response) isProtocolSwitch() bool {
	return r.StatusCode == StatusSwitchingProtocols &&
		r.Header.Get("Upgrade") != "" &&
		httpguts.HeaderValuesContainsToken(r.Header["Connection"], "Upgrade")
}

应用
实际项目中,当服务器监听请求返回响应时,该响应结果是一个结构体,需要对结构体进行处理

func (b *HttpRequest) Bytes() ([]byte, error) {
	resp, err := b.getResponse()		// 服务器监听请求返回响应
	if err != nil {
		return nil, err
	}
	if resp.Body == nil {
		return nil, nil
	}
	defer resp.Body.Close()		// 有义务关闭响应体,避免内存泄漏
	data, err := ioutil.ReadAll(resp.Body)	// 将struct转成[]byte
	if err != nil {
		return nil, err
	}
	return data, nil
}

将处理好的[]byte类型响应结果,进行判断分析

resp, err := req.Bytes()
	if err != nil {
		logger.Info("get resp err", logger.WithError(err))
		server.WriteError(c, err)
		return
	}

	logger.Infof("indoor resp : %s", resp)

	err = json.Unmarshal(resp, &pmResp)
	if err != nil {
		logger.Info("Unmarshal DrawbackToOwner resp err", logger.WithError(err))
		server.WriteError(c, err)
		return
	}

	if !pmResp.Success {
		logger.Infof("resp:%s", pmResp.ErrorMessage)
		server.WriteError(c, err)
		return
	}