• 设为首页
  • 点击收藏
  • 手机版
    手机扫一扫访问
    迪恩网络手机版
  • 关注官方公众号
    微信扫一扫关注
    迪恩网络公众号

go标准库的学习-net/http

原作者: [db:作者] 来自: [db:来源] 收藏 邀请

参考:https://studygolang.com/pkgdoc

概念解释:

  • request:用户请求的信息,用来解析用户的请求信息,包括post、get、cookie、url等信息
  • response:服务器返回给客户端的信息
  • conn:用户的每次请求链接
  • handler:处理请求和生成返回信息的处理逻辑

该图来自https://www.sohu.com/a/208720509_99960938

 

下面的内容来自http://www.runoob.com/http/http-messages.html

HTTP使用统一资源标识符(Uniform Resource Identifiers, URI)来传输数据和建立连接。

一旦建立连接后,数据消息就通过类似Internet邮件所使用的格式[RFC5322]和多用途Internet邮件扩展(MIME)[RFC2045]来传送。

 

客户端请求消息

客户端发送一个HTTP请求到服务器的请求消息包括以下格式:请求行(request line)、请求头部(header)、空行和请求数据四个部分组成,下图给出了请求报文的一般格式。

举例:

GET /hello.txt HTTP/1.1
User-Agent: curl/7.16.3 libcurl/7.16.3 OpenSSL/0.9.7l zlib/1.2.3
Host: www.example.com
Accept-Language: en, mi

HTTP 协议的 8 种请求方法介绍:

HTTP1.0定义了三种请求方法: GET, POST 和 HEAD方法

HTTP1.1新增了五种请求方法:OPTIONS, PUT, DELETE, TRACE 和 CONNECT 方法。

HTTP 协议中共定义了八种请求方法或者叫“动作”来表明对 Request-URI 指定的资源的不同操作方式,具体介绍如下:

  •  OPTIONS:返回服务器针对特定资源所支持的HTTP请求方法。也可以利用向Web服务器发送'*'的请求来测试服务器的功能性。 
  •  HEAD:向服务器索要与GET请求相一致的响应,只不过响应体将不会被返回。这一方法可以在不必传输整个响应内容的情况下,就可以获取包含在响应消息头中的元信息。 
  •  GET:向特定的资源发出请求。 
  •  POST:向指定资源提交数据进行处理请求(例如提交表单或者上传文件)。数据被包含在请求体中。POST请求可能会导致新的资源的创建和/或已有资源的修改。 
  •  PUT:向指定资源位置上传其最新内容。 
  •  DELETE:请求服务器删除 Request-URI 所标识的资源。 
  •  TRACE:回显服务器收到的请求,主要用于测试或诊断。 
  •  CONNECT:HTTP/1.1 协议中预留给能够将连接改为管道方式的代理服务器。 

虽然 HTTP 的请求方式有 8 种,但是我们在实际应用中常用的也就是 get 和 post,其他请求方式也都可以通过这两种方式间接的来实现。

 

服务器响应消息

HTTP响应也由四个部分组成,分别是:状态行、消息报头、空行和响应正文。

举例

服务端响应:

HTTP/1.1 200 OK
Date: Mon, 27 Jul 2009 12:28:53 GMT
Server: Apache
Last-Modified: Wed, 22 Jul 2009 19:15:56 GMT
ETag: "34aa387-d-1568eb00"
Accept-Ranges: bytes
Content-Length: 51
Vary: Accept-Encoding
Content-Type: text/plain

输出结果:

Hello World! My payload includes a trailing CRLF.

 

1>常量

const (
    StatusContinue           = 100
    StatusSwitchingProtocols = 101
    StatusOK                   = 200
    StatusCreated              = 201
    StatusAccepted             = 202
    StatusNonAuthoritativeInfo = 203
    StatusNoContent            = 204
    StatusResetContent         = 205
    StatusPartialContent       = 206
    StatusMultipleChoices   = 300
    StatusMovedPermanently  = 301
    StatusFound             = 302
    StatusSeeOther          = 303
    StatusNotModified       = 304
    StatusUseProxy          = 305
    StatusTemporaryRedirect = 307
    StatusBadRequest                   = 400
    StatusUnauthorized                 = 401
    StatusPaymentRequired              = 402
    StatusForbidden                    = 403
    StatusNotFound                     = 404
    StatusMethodNotAllowed             = 405
    StatusNotAcceptable                = 406
    StatusProxyAuthRequired            = 407
    StatusRequestTimeout               = 408
    StatusConflict                     = 409
    StatusGone                         = 410
    StatusLengthRequired               = 411
    StatusPreconditionFailed           = 412
    StatusRequestEntityTooLarge        = 413
    StatusRequestURITooLong            = 414
    StatusUnsupportedMediaType         = 415
    StatusRequestedRangeNotSatisfiable = 416
    StatusExpectationFailed            = 417
    StatusTeapot                       = 418
    StatusInternalServerError     = 500
    StatusNotImplemented          = 501
    StatusBadGateway              = 502
    StatusServiceUnavailable      = 503
    StatusGatewayTimeout          = 504
    StatusHTTPVersionNotSupported = 505
)

HTTP状态码

当你得到一个值的时候,如果你想要知道这个值代表的是什么状态,可以使用:

 StatusText

func StatusText(code int) string

StatusText返回HTTP状态码code对应的文本,如220对应"OK"。如果code是未知的状态码,会返回""。

举例:

package main 
import(
    "fmt"
    "net/http"
)

func main() {
    sta1 := http.StatusText(307)
    sta2 := http.StatusText(200)
    fmt.Println(sta1) //Temporary Redirect
    fmt.Println(sta2) //OK
}

 

2>变量

var DefaultClient = &Client{}

DefaultClient是用于包函数Get、Head和Post的默认Client。

var DefaultServeMux = NewServeMux()

DefaultServeMux是用于Serve的默认ServeMux。

 

3>

 CanonicalHeaderKey

func CanonicalHeaderKey(s string) string

CanonicalHeaderKey函数返回头域(表示为Header类型)的键s的规范化格式。规范化过程中让单词首字母和'-'后的第一个字母大写,其余字母小写。例如,"accept-encoding"规范化为"Accept-Encoding"。

举例:

package main 
import(
    "fmt"
    "net/http"
)

func main() {
    hea1 := http.CanonicalHeaderKey("uid-test")
    hea2 := http.CanonicalHeaderKey("accept-encoding")
    fmt.Println(hea1) //Uid-Test
    fmt.Println(hea2) //Accept-Encoding
}

 

 DetectContentType

func DetectContentType(data []byte) string

DetectContentType函数实现了http://mimesniff.spec.whatwg.org/描述的算法,用于确定数据的Content-Type。函数总是返回一个合法的MIME类型;如果它不能确定数据的类型,将返回"application/octet-stream"。它最多检查数据的前512字节。

出处:https://www.cnblogs.com/52fhy/p/5436673.html

Http Header里的Content-Type一般有这三种:

  • application/x-www-form-urlencoded:数据被编码为名称/值对。这是标准的编码格式。
  • multipart/form-data: 数据被编码为一条消息,页上的每个控件对应消息中的一个部分。
  • text/plain: 数据以纯文本形式(text/json/xml/html)进行编码,其中不含任何控件或格式字符。postman软件里标的是RAW。

网页中form的enctype属性为编码方式,常用有两种:

  • application/x-www-form-urlencoded,默认编码方式
  • multipart/form-data

1)当action为get时候,浏览器用x-www-form-urlencoded的编码方式把form数据转换成一个字串(name1=value1&name2=value2...),然后把这个字串追加到url后面,用?分割,加载这个新的url。

2)当action为post时候,浏览器把form数据封装到http body中,然后发送到server。 如果没有type=file的控件,用默认的application/x-www-form-urlencoded就可以了。 但是如果有type=file的话,就要用到multipart/form-data了。

3)当action为post且Content-Type类型是multipart/form-data,浏览器会把整个表单以控件为单位分割,并为每个部分加上Content-Disposition(form-data或者file),Content-Type(默认为text/plain),name(控件name)等信息,并加上分割符(boundary)。

4)当然还有很多其他的类型,可以查看http://www.runoob.com/http/http-content-type.html

因此可以使用DetectContentType来检测传入的[]byte类型的数据是哪种Content-Type,举例说明:\

package main 
import(
    "fmt"
    "net/http"
)

func main() {
    cont1 := http.DetectContentType([]byte{}) //text/plain; charset=utf-8
    cont2 := http.DetectContentType([]byte{1, 2, 3}) //application/octet-stream
    cont3 := http.DetectContentType([]byte(`<HtMl><bOdY>blah blah blah</body></html>`)) //text/html; charset=utf-8
    cont4 := http.DetectContentType([]byte("\n<?xml!")) //text/xml; charset=utf-8
    cont5 := http.DetectContentType([]byte(`GIF87a`)) //image/gif
    cont6 := http.DetectContentType([]byte("MThd\x00\x00\x00\x06\x00\x01")) //audio/midi
    fmt.Println(cont1)
    fmt.Println(cont2)
    fmt.Println(cont3)
    fmt.Println(cont4)
    fmt.Println(cont5)
    fmt.Println(cont6)
}

 

const TimeFormat = "Mon, 02 Jan 2006 15:04:05 GMT"

TimeFormat是当解析或生产HTTP头域中的时间时,用与time.Parse或time.Format函数的时间格式。这种格式类似time.RFC1123但强制采用GMT时区。

 ParseTime

func ParseTime(text string) (t time.Time, err error)

ParseTime用3种格式TimeFormat, time.RFC850和time.ANSIC尝试解析一个时间头的值(如Date: header)。

举例:

package main 
import(
    "fmt"
    "net/http"
    "time"
)


var parseTimeTests = []struct {
    h   http.Header
    err bool
}{
    {http.Header{"Date": {""}}, true},
    {http.Header{"Date": {"invalid"}}, true},
    {http.Header{"Date": {"1994-11-06T08:49:37Z00:00"}}, true},
    {http.Header{"Date": {"Sun, 06 Nov 1994 08:49:37 GMT"}}, false},
    {http.Header{"Date": {"Sunday, 06-Nov-94 08:49:37 GMT"}}, false},
    {http.Header{"Date": {"Sun Nov  6 08:49:37 1994"}}, false},
}

func main() {
    expect := time.Date(1994, 11, 6, 8, 49, 37, 0, time.UTC)
    fmt.Println(expect) //1994-11-06 08:49:37 +0000 UTC
    for i, test := range parseTimeTests {
        d, err := http.ParseTime(test.h.Get("Date"))
        fmt.Println(d)
        if err != nil {
            fmt.Println(i, err)
            if !test.err { //test.err为false才进这里
                fmt.Errorf("#%d:\n got err: %v", i, err)
            }
            continue //有错的进入这后继续下一个循环,不往下执行
        }
        if test.err { //test.err为true,所以该例子中这里不会进入
            fmt.Errorf("#%d:\n  should err", i)
            continue
        }
        if !expect.Equal(d) { //说明后三个例子的结果和expect是相同的,所以没有报错
            fmt.Errorf("#%d:\n got: %v\nwant: %v", i, d, expect)
        }
    }
}

返回:

userdeMBP:go-learning user$ go run test.go
1994-11-06 08:49:37 +0000 UTC
0001-01-01 00:00:00 +0000 UTC //默认返回的空值
0 parsing time "" as "Mon Jan _2 15:04:05 2006": cannot parse "" as "Mon"
0001-01-01 00:00:00 +0000 UTC
1 parsing time "invalid" as "Mon Jan _2 15:04:05 2006": cannot parse "invalid" as "Mon"
0001-01-01 00:00:00 +0000 UTC
2 parsing time "1994-11-06T08:49:37Z00:00" as "Mon Jan _2 15:04:05 2006": cannot parse "1994-11-06T08:49:37Z00:00" as "Mon"
1994-11-06 08:49:37 +0000 UTC
1994-11-06 08:49:37 +0000 GMT
1994-11-06 08:49:37 +0000 UTC

额外补充,time.Date():

 Date

func Date(year int, month Month, day, hour, min, sec, nsec int, loc *Location) Time

详情看go标准库的学习-time

 

 ParseHTTPVersion

func ParseHTTPVersion(vers string) (major, minor int, ok bool)

ParseHTTPVersion解析HTTP版本字符串。如"HTTP/1.0"返回(1, 0, true)。

举例:
package main 
import(
    "fmt"
    "net/http"
)

func main() {
    m, n, ok := http.ParseHTTPVersion("HTTP/1.0")
    fmt.Println(m, n, ok) //1 0 true

 

4>

1)header-服务端和客户端的数据都有头部

 Header

type Header map[string][]string

Header代表HTTP头域的键值对。

你可以自定义自己的Header,下面的Header中只有Date字段,你还可以加入其他字段:

http.Header{"Date": {"1994-11-06T08:49:37Z00:00"}}

然后就能够调用下面的几种方法来对Header进行修改:

 Get

func (h Header) Get(key string) string

Get返回键对应的第一个值,如果键不存在会返回""。如要获取该键对应的值切片,请直接用规范格式的键访问map。

 Set

func (h Header) Set(key, value string)

Set添加键值对到h,如键已存在则会用只有新值一个元素的切片取代旧值切片。

 Add

func (h Header) Add(key, value string)

Add添加键值对到h,如键已存在则会将新的值附加到旧值切片后面。

 Del

func (h Header) Del(key string)

Del删除键值对。

举例:

package main 
import(
    "fmt"
    "net/http"
)

func main() {
    header := http.Header{"Date": {"1994-11-06T08:49:37Z00:00"}}
    fmt.Println(header.Get("Date")) //1994-11-06T08:49:37Z00:00
    fmt.Println(header.Get("Content-Type")) //因为没有该字段,返回为空

    header.Set("Content-Type", "text/plain; charset=UTF-8") //设置"Content-Type"字段
    fmt.Println(header.Get("Content-Type")) //返回text/plain; charset=UTF-8
    header.Set("Content-Type", "application/x-www-form-urlencoded;") //覆盖原先的值,返回application/x-www-form-urlencoded;
    fmt.Println(header.Get("Content-Type"))

    header.Add("Content-Type", "charset=UTF-8") //在"Content-Type"字段中追加值
    fmt.Println(header) //map[Date:[1994-11-06T08:49:37Z00:00] Content-Type:[application/x-www-form-urlencoded; charset=UTF-8]],可见添加进去
    fmt.Println(header.Get("Content-Type")) //但是这样获取是返回值仍是application/x-www-form-urlencoded;

    header.Del("Content-Type") //删除该字段
    fmt.Println(header.Get("Content-Type")) //然后返回又为空
}

 Write

func (h Header) Write(w io.Writer) error

Write以有线格式将头域写入w。

 WriteSubset

func (h Header) WriteSubset(w io.Writer, exclude map[string]bool) error

WriteSubset以有线格式将头域写入w。当exclude不为nil时,如果h的键值对的键在exclude中存在且其对应值为真,该键值对就不会被写入w。

举例:

package main 
import(
    "fmt"
    "net/http"
    "bytes"
    "os"
)

var headerWriteTests = []struct {
    h        http.Header
    exclude  map[string]bool
    expected string
}{
    {http.Header{}, nil, ""},
    {
        http.Header{
            "Content-Type":   {"text/html; charset=UTF-8"},
            "Content-Length": {"0"},
        },
        nil,
        "Content-Length: 0\r\nContent-Type: text/html; charset=UTF-8\r\n",
    },
    {
        http.Header{
            "Expires":          {"-1"},
            "Content-Length":   {"0"},
            "Content-Encoding": {"gzip"},
        },
        map[string]bool{"Content-Length": true}, //"Content-Length"字段将不会写入io.Writer
        "Content-Encoding: gzip\r\nExpires: -1\r\n",
    },
}

func main() {
    var buf bytes.Buffer //得到io.Writer
    for i, test := range headerWriteTests {
        test.h.WriteSubset(&buf, test.exclude)
        fmt.Println(i)
        buf.WriteTo(os.Stdout)
        fmt.Println()
        if buf.String() != test.expected {
            fmt.Errorf("#%d:\n got: %q\nwant: %q", i, buf.String(), test.expected)
        }
        buf.Reset()
    }
}

返回:

userdeMBP:go-learning user$ go run test.go
0

1
Content-Length: 0
Content-Type: text/html; charset=UTF-8

2
Content-Encoding: gzip
Expires: -1

 

2)Cookie

⚠️session和cookie的区别:

session是存储在服务器的文件,cookie内容保存在客户端,存在被客户篡改的情况,session保存在服务端端防止被用户篡改的情况。

1》

 Cookie

type Cookie struct {
    Name       string
    Value      string
    Path       string
    Domain     string
    Expires    time.Time
    RawExpires string
    // MaxAge=0表示未设置Max-Age属性
    // MaxAge<0表示立刻删除该cookie,等价于"Max-Age: 0"
    // MaxAge>0表示存在Max-Age属性,单位是秒
    MaxAge   int
    Secure   bool
    HttpOnly bool
    Raw      string
    Unparsed []string // 未解析的“属性-值”对的原始文本
}

Cookie代表一个出现在HTTP回复的头域中Set-Cookie头的值里或者HTTP请求的头域中Cookie头的值里的HTTP cookie。

 String

func (c *Cookie) String() string

String返回该cookie的序列化结果。如果只设置了Name和Value字段,序列化结果可用于HTTP请求的Cookie头或者HTTP回复的Set-Cookie头;如果设置了其他字段,序列化结果只能用于HTTP回复的Set-Cookie头。

1)举例:

package main 
import(
    "fmt"
    "net/http"
    "bytes"
    "os"
    "log"
    "time"
)

var writeSetCookiesTests = []struct {
    Cookie *http.Cookie
    Raw    string
}{
    {
        &http.Cookie{Name: "cookie-2", Value: "two", MaxAge: 3600},
        "cookie-2=two; Max-Age=3600",
    },
    {
        &http.Cookie{Name: "cookie-3", Value: "three", Domain: ".example.com"},
        "cookie-3=three; Domain=example.com",
    },
    {
        &http.Cookie{Name: "cookie-4", Value: "four", Path: "/restricted/"},
        "cookie-4=four; Path=/restricted/",
    },
    {
        &http.Cookie{Name: "cookie-9", Value: "expiring", Expires: time.Unix(1257894000, 0)},
        "cookie-9=expiring; Expires=Tue, 10 Nov 2009 23:00:00 GMT",
    },
    // According to IETF 6265 Section 5.1.1.5, the year cannot be less than 1601
    {//故意将这里的cookie-10写成cookie-101,然后下面就会报错
        &http.Cookie{Name: "cookie-10", Value: "expiring-1601", Expires: time.Date(1601, 1, 1, 1, 1, 1, 1, time.UTC)},
        "cookie-101=expiring-1601; Expires=Mon, 01 Jan 1601 01:01:01 GMT",
    },
    { //因此其返回值中没有Expires
        &http.Cookie{Name: "cookie-11", Value: "invalid-expiry", Expires: time.Date(1600, 1, 1, 1, 1, 1, 1, time.UTC)},
        "cookie-11=invalid-expiry",
    },
    // The "special" cookies have values containing commas or spaces which
    // are disallowed by RFC 6265 but are common in the wild.
    {
        &http.Cookie{Name: "special-1", Value: "a z"},
        `special-1="a z"`,
    },
    {
        &http.Cookie{Name: "empty-value", Value: ""},
        `empty-value=`,
    },
    {
        nil,
        ``,
    },
    {
        &http.Cookie{Name: ""},
        ``,
    },
    {
        &http.Cookie{Name: "\t"},
        ``,
    },
}


func main() {
    defer log.SetOutput(os.Stderr)
    var logbuf bytes.Buffer
    log.SetOutput(&logbuf)

    for i, tt := range writeSetCookiesTests {//没有报错则说明得到的Cookie的值与Raw字符串相等
        if g, e := tt.Cookie.String(), tt.Raw; g != e {
            fmt.Printf("Test %d, expecting:\n%s\nGot:\n%s\n", i, e, g)
            continue
        }
    }
}

返回:

userdeMBP:go-learning user$ go run test.go
Test 4, expecting:
cookie-101=expiring-1601; Expires=Mon, 01 Jan 1601 01:01:01 GMT
Got:
cookie-10=expiring-1601; Expires=Mon, 01 Jan 1601 01:01:01 GMT

2)

 SetCookie

func SetCookie(w ResponseWriter, cookie *Cookie)

SetCookie在w的头域中添加Set-Cookie头,该HTTP头的值为cookie。

常用SetCookie来给http的request请求或者http的response响应设置cookie。

然后使用request的Cookies()、Cookie(name string)函数和response的Cookies()函数来获取设置的cookie信息

 ResponseWriter

type ResponseWriter interface {
    // Header返回一个Header类型值,该值会被WriteHeader方法发送。
    // 在调用WriteHeader或Write方法后再改变该对象是没有意义的。
    Header() Header
    // WriteHeader该方法发送HTTP回复的头域和状态码。
    // 如果没有被显式调用,第一次调用Write时会触发隐式调用WriteHeader(http.StatusOK)
    // WriterHeader的显式调用主要用于发送错误码。
    WriteHeader(int)
    // Write向连接中写入作为HTTP的一部分回复的数据。
    // 如果被调用时还未调用WriteHeader,本方法会先调用WriteHeader(http.StatusOK)
    // 如果Header中没有"Content-Type"键,
    // 本方法会使用包函数DetectContentType检查数据的前512字节,将返回值作为该键的值。
    Write([]byte) (int, error)
}

ResponseWriter接口被HTTP处理器用于构造HTTP回复。

举例:

package main 
import(
    "fmt"
    "net/http"
)

type headerOnlyResponseWriter http.Header
// 
                       
                    
                    

鲜花

握手

雷人

路过

鸡蛋
该文章已有0人参与评论

请发表评论

全部评论

专题导读
上一篇:
安装go环境发布时间:2022-07-10
下一篇:
go可以开发桌面应用发布时间:2022-07-10
热门推荐
热门话题
阅读排行榜

扫描微信二维码

查看手机版网站

随时了解更新最新资讯

139-2527-9053

在线客服(服务时间 9:00~18:00)

在线QQ客服
地址:深圳市南山区西丽大学城创智工业园
电邮:jeky_zhao#qq.com
移动电话:139-2527-9053

Powered by 互联科技 X3.4© 2001-2213 极客世界.|Sitemap