golang源码解读之sponse
关于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 Le ngth”和“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)
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
}
error parse new// 进程主体,内容长度,关闭,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
}

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。