net/http

http 包实现了http客户端与服务端的实现

  • 1.创建http客户端
  • 2.客户端发起get,post,postForm请求演示
  • 3.自定义客户端请求
  • 4.创建服务端
  • 5.自定义服务端配置
  • 6.解析HTTP版本号
  • 7.将不同格式的时间字符串,转换成标准time结构
  • 8.获取http状态码Status 对应的文本信息
  • 9.监测服务器与客户端的连接状态
  • 10.获取请求头里面的数据
  • 11.将请求头的数据写入文件中
  • 12.文件服务器创建
  • 13.请求重定向演示
  • 14.将文件的内容返回给请求
  • 15.建立https服务
  • 16.接管服务器和客户端连接
1.创建http客户端

客户端请求连接过程

1 创建http客户端对象
2 按照指定方式请求数据
3 获取数据
4 关闭连接

type Client struct {
      
         // 传输机制
    Transport RoundTripper

    // CheckRedirect 指定了重定向策略.
    CheckRedirect func(req *Request, via []*Request) error

    // Jar 指定了缓存
    Jar CookieJar

       // 设定了超时时间
    Timeout time.Duration
}

var DefaultClient = &Client{}
DefaultClient提供了Get、Head、Post 的默认Client.

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

func main() {
  // 创建客户端发get请求
   resp,error := http.DefaultClient.Get("http://example.com/")
 // 相应结束后,请及时关闭会话
   defer resp.Body.Close()
   if error != nil {
       log.Fatal(error)
   }
   // 读取相应的数据
   data,error := ioutil.ReadAll(resp.Body)
   fmt.Println(string(data))
 }

我们看一下响应数据结构体都包含了那些数据

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 Header  // 相应头部
    Body io.ReadCloser
    ContentLength int64 // 内容长度
    TransferEncoding []string // 传输数据编码
    Close bool // 是否关闭连接
        Uncompressed bool //是否未压缩
    Trailer Header
    Request *Request // 原始请求相关
    TLS *tls.ConnectionState // https 加密相关
}

2.客户端发起get,post,postForm请求演示
resp, err := http.Get("http://example.com/")
...
resp, err := http.Post("http://example.com/upload", "image/jpeg", &buf)
...
resp, err := http.PostForm("http://example.com/form",
    url.Values{"key": {"Value"}, "id": {"123"}})

3.自定义客户端请求
  • 定义请求方式
  • 定义请求重定向策略
  • 自定义请求头
  • 自定义Transport
  • TLS配置

我们就针对上面的5点进行演示

定义请求方式:除了系统提供默认的创建客户端的方式,我们也可以手动创建客户端,比较简单,直接看下面的例子

package main

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

func main() {
   // 创建一个客户端
   client := &http.Client{}

   // 创建一个自定义请求
   req,_:= http.NewRequest("Get","http://www.baidu.com",nil)

   // 让客户端执行请求
   resp,error := client.Do(req)
  // 关闭请求
  defer resp.Body.Close()
   if error != nil {
       log.Fatal(error)
   }
   data,error := ioutil.ReadAll(resp.Body)
   fmt.Println(string(data))
 }
1594482-1d293b356352bb48.png
请求数据的截图

定义请求策略:如果我们不设置请求重定向策略,系统会使用默认的重定向策略

package main

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

func main() {
   // 创建一个客户端
   client := &http.Client{
       CheckRedirect:CheckRedirect,
   }
   // 创建一个自定义请求
   req,_:= http.NewRequest("Get","http://www.baidu.com",nil)

   // 让客户端执行请求
   resp,error := client.Do(req)
   defer resp.Body.Close()
   if error != nil {
       log.Fatal(error)
   }
   fmt.Println(resp.StatusCode)
   data,error := ioutil.ReadAll(resp.Body)
   fmt.Println(string(data))

 }

func CheckRedirect(req *http.Request, via []*http.Request) error{
    fmt.Println(req.URL.Host)
    if req.URL.Host == "www.baidu.com" {
        // 返回http.ErrUseLastResponse 可以禁止重定向
        return  http.ErrUseLastResponse
    }
    return nil
}
1594482-760283a98f4d8537.png
image.png

自定义请求头

client := &http.Client{
    CheckRedirect: redirectPolicyFunc,
}
resp, err := client.Get("http://example.com")
// ...
req, err := http.NewRequest("GET", "http://example.com", nil)
// ...
req.Header.Add("If-None-Match", `W/"wyzzy"`)

自定义Transport 要管理代理、TLS配置、keep-alive、压缩和其他设置,创建一个Transport

tr := &http.Transport{
    TLSClientConfig:    &tls.Config{RootCAs: pool},
    DisableCompression: true,
}
client := &http.Client{Transport: tr}
4.创建服务器

简单的请求

func ListenAndServe(addr string, handler Handler) error

参数 Handler 是一个接口类型,需要我们实现下面的接口

type Handler interface {
    ServeHTTP(ResponseWriter, *Request)
}

下面我们看一个完整的例子

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

// 第一步 Handle 实现接口
type  Controller struct {
}

func (c Controller)ServeHTTP(resp http.ResponseWriter, req *http.Request){
    fmt.Println(req.Host)
    resp.Write([]byte("我是服务器返回的数据"))
    req.Body.Close()
}

func main() {
   // 第二步 监听服务
   http.ListenAndServe(":8081",&Controller{})
 }

我们在http请求的时候,往往需要处理不同的请求路径,比如/login 和/register ,go为我们提供了如下的方法进行处理

func HandleFunc(pattern string, handler func(ResponseWriter, *Request))
func Handle(pattern string, handler Handler)

路由匹配

package main

import (
    "net/http"
    "fmt"
    )

func main() {
  // 第一步 注册请求匹配函数
  http.HandleFunc("/login", func(writer http.ResponseWriter, request *http.Request) {
      fmt.Println(request.Host)
      fmt.Println(request.URL)
      fmt.Println(request.URL.RawQuery)
      fmt.Println(request.Body)
      fmt.Println(request.Header)
      // 服务器相应数据
      writer.Write([]byte(request.UserAgent()))
      request.Body.Close()
  })
  // 监听服务
  http.ListenAndServe(":8081",nil)
 }

func Handle(pattern string, handler Handler) 需要自己创建变量实现 handle接口,实现的过程我们已经演示过了,就不在赘述了

5.自定义服务端配置

上面我们使用 http.ListenAndServe 启动了一个监听服务,但是我们在实际使用的过程中,有时需要配置一些信息比如

  • 读请求的最大时间
  • 写入的组大时间
  • 最大的头字节数
    ...

下面我们就演示一下如何实现自定义服务端配置的过程

package main

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

type  Controller struct {
}

func (c Controller)ServeHTTP(resp http.ResponseWriter, req *http.Request){
    fmt.Println(req.Host)
    resp.Write([]byte("我是服务器返回的数据"))
    req.Body.Close()
}

func main() {
   监听服务
  server := http.Server{
      Addr:           ":8080",
      Handler:        &Controller{},
      ReadTimeout:    10 * time.Second,
      WriteTimeout:   10 * time.Second,
      MaxHeaderBytes: 1 << 20,
  }
  server.ListenAndServe()
 }

浏览器输入 http://localhost:8080

1594482-3075b53c9eafd1dc.png
服务器日志
1594482-b8f2d39fd81666cd.png
浏览器接受数据

6.解析HTTP 版本号

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

    fmt.Println(http.ParseHTTPVersion("HTTP/1.0"))
1594482-a8510d67bbee3e8b.png
image.png

7.将不同格式的时间字符串,转换成标准time结构

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

    t,_ := http.ParseTime("Monday, 02-Jan-06 15:04:05 MST") // time.RFC850
    fmt.Println(t)
    t,_ = http.ParseTime("Mon Jan 2 15:04:05 2006")  //time.ANSIC 不能使用字符_
    fmt.Println(t)

日志:

2006-01-02 15:04:05 +0000 MST
2006-01-02 15:04:05 +0000 UTC

8.获取http状态码Status 对应的文本信息

func StatusText(code int) string

  fmt.Println(http.StatusText(200))
  fmt.Println(http.StatusText(301))
    fmt.Println(http.StatusText(500))

OK
Moved Permanently
Internal Server Error


9.监测服务器与客户端的连接状态

type ConnState int

const (
    // StateNew代表一个新的连接,将要立刻发送请求。
    // 连接从这个状态开始,然后转变为StateAlive或StateClosed。
    StateNew ConnState = iota
    // StateActive代表一个已经读取了请求数据1到多个字节的连接。
    // 用于StateAlive的Server.ConnState回调函数在将连接交付给处理器之前被触发,
    // 等到请求被处理完后,Server.ConnState回调函数再次被触发。
    // 在请求被处理后,连接状态改变为StateClosed、StateHijacked或StateIdle。
    StateActive
    // StateIdle代表一个已经处理完了请求、处在闲置状态、等待新请求的连接。
    // 连接状态可以从StateIdle改变为StateActive或StateClosed。
    StateIdle
    // 代表一个被劫持的连接。这是一个终止状态,不会转变为StateClosed。
    StateHijacked
    // StateClosed代表一个关闭的连接。
    // 这是一个终止状态。被劫持的连接不会转变为StateClosed。
    StateClosed
)
package main

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

type  Controller struct {
}

func (c Controller)ServeHTTP(resp http.ResponseWriter, req *http.Request){
    fmt.Println(req.Host)
    resp.Write([]byte("我是服务器返回的数据"))
    req.Body.Close()
}

func main() {
   监听服务
  server := http.Server{
      Addr:           ":8080",
      Handler:        &Controller{},
      ReadTimeout:    10 * time.Second,
      WriteTimeout:   10 * time.Second,
      MaxHeaderBytes: 1 << 1,
  }

  // 监测服务器的连接状态
  server.ConnState = func(conn net.Conn, state http.ConnState) {
      fmt.Println(state)
  }

  server.ListenAndServe()

 }
1594482-e5bef62a9336ef92.png
image.png

我们发现浏览器想服务器发送请求两次请求,但是只有一个closed状态,这个是因为一旦服务器与浏览器完成数据交互后,连接状态还是激活状态,没有关闭,如果在最大连接时间未到时,客户端继续发送请求,就不会要重新建立连接了。如果我们想要每次数据结束后立即关闭连接,使用下面的方法即可

server.SetKeepAlivesEnabled(false)

1594482-c304094835727f09.png
image.png

10.获取请求头里面的数据
    for key,value := range req.Header{
        fmt.Println(key,":",value)
    }

Accept-Encoding : [gzip, deflate]
Connection : [keep-alive]
Upgrade-Insecure-Requests : [1]
Cookie : [pgv_pvi=4726842368; Webstorm-7b1b8ce4=a417cfae-a0df-4fa4-96a0-bec5a8aa3f22; _ga=GA1.1.1767641313.1490064708; Phpstorm-854b20c0=cce12517-63b4-48f0-8a05-1f6156ee2339]
User-Agent : [Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_6) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/12.0 Safari/605.1.15]
Accept-Language : [zh-cn]
Accept : [text/html,application/xhtml+xml,application/xml;q=0.9,/;q=0.8]

获取指定key的对应的值

req.Header.Get("key")

设置添加值到header中,如键已存在则会用只有新值一个元素的切片取代旧值切片

req.Header.Set("key","value")

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

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

删除请求头数据

func (h Header) Del(key string)

11.将请求头的数据写入文件中
// Write以有线格式将头域写入w
func (h Header) Write(w io.Writer) error 
//WriteSubset以有线格式将头域写入w。当exclude不为nil时,如果h的键值对的键在exclude中存在且其对应值为真,该键值对就不会被写入w
func (h Header) WriteSubset(w io.Writer, exclude map[string]bool) error

示例 1

    file,_ := os.Create("/Users/xujie/go/src/awesomeProject/main/header.txt")
    req.Header.Write(file)
1594482-6fd10cdfe69091d7.png
image.png

示例 2

    file,_ := os.Create("/Users/xujie/go/src/awesomeProject/main/header.txt")
    req.Header.WriteSubset(file,map[string]bool{"Accept":true})
1594482-c454bc5e951ffe09.png
image.png

通过方法 WriteSubset我们将header写入文件时,过滤了 Accept


12.文件服务器创建

func FileServer(root FileSystem) Handler

    http.Handle("/login",&Controller{})
    http.Handle("/",http.FileServer(http.Dir("/usr/local")))
    http.ListenAndServe(":8080",nil)

访问 http://localhost:8080 即可查看

1594482-a6ee7624feac5229.png
image.png

这里注意一下 我们的注册 http.Handle("/",http.FileServer(http.Dir("/usr/local"))),如果我们想要给我们可访问目录设置一个别名,不让用户直接访问该怎么处理呢?

 http.Handle("/public",http.StripPrefix("/public",http.FileServer(http.Dir("/usr/local"))))
 http.ListenAndServe(":8080",nil)

接下来就可以通过 http://localhost:8080/public 进行访问了


13.请求重定向演示

如果我们访问一个页面出现了错误,我们让用户重定向到一个其他页面上,类似于这个过程就是重定向

func Redirect(w ResponseWriter, r *Request, urlStr string, code int)

示例

package main

import (
    "net/http"
          )

type  Controller struct {
}
func (c Controller)ServeHTTP(resp http.ResponseWriter, req *http.Request){
    // 请求重定向
    http.Redirect(resp,req,"/error",http.StatusMovedPermanently)
}
func main() {
     监听服务
    http.Handle("/login",&Controller{})
    http.HandleFunc("/error", func(writer http.ResponseWriter, request *http.Request) {
        writer.Write([]byte("重定向的错误页面"))
    })
    http.ListenAndServe(":8080",nil)
 }

当我们浏览器访问http://localhost/login ,服务器会重定向到/error 对应的服务上,这个时候浏览器的输入栏的地址也会自动切换到http://localhost/error

1594482-9ebf1b99708041ac.png
image.png

如果想直接返回未找到资源的信息提示,可以使用

func NotFound(w ResponseWriter, r *Request)

将文件的内容返回给请求
  • 全部返回
package main
import (
    "net/http"
)
type  Controller struct {
}
func (c Controller)ServeHTTP(resp http.ResponseWriter, req *http.Request){
    http.ServeFile(resp,req,"/Users/xujie/go/src/awesomeProject/main/private.pem")
}
func main() {
   监听服务
    http.Handle("/private",&Controller{})
    http.ListenAndServe(":8080",nil)
 }
1594482-4c3b7c4c464214eb.png
image.png

15.建立https服务

ListenAndServeTLS函数和ListenAndServe函数的行为基本一致,除了它期望HTTPS连接之外。此外,必须提供证书文件和对应的私钥文件。如果证书是由权威机构签发的,certFile参数必须是顺序串联的服务端证书和CA证书。如果srv.Addr为空字符串,会使用":https"

我们先生成 cert.pem和key.pem

package main

import (
    "bytes"
    cryptorand "crypto/rand"
    "crypto/rsa"
    "crypto/x509"
    "crypto/x509/pkix"
    "encoding/pem"
    "fmt"
    "math/big"
    "net"
    "time"
)

func main() {
    ip := []byte("192.168.110.66")
    alternateDNS := []string{"kubernetes.default.svc", "kubernetes.default", "kubernetes", "localhost"}
    cert, key, _ := GenerateSelfSignedCertKey("10.10.10.10", []net.IP{ip}, alternateDNS)
    fmt.Println(string(cert), string(key))

}

func GenerateSelfSignedCertKey(host string, alternateIPs []net.IP, alternateDNS []string) ([]byte, []byte, error) {
    priv, err := rsa.GenerateKey(cryptorand.Reader, 2048)
    if err != nil {
        return nil, nil, err
    }

    template := x509.Certificate{
        SerialNumber: big.NewInt(1),
        Subject: pkix.Name{
            CommonName: fmt.Sprintf("%s@%d", host, time.Now().Unix()),
        },
        NotBefore: time.Now(),
        NotAfter:  time.Now().Add(time.Hour * 24 * 365),

        KeyUsage:              x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature | x509.KeyUsageCertSign,
        ExtKeyUsage:           []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},
        BasicConstraintsValid: true,
        IsCA: true,
    }

    if ip := net.ParseIP(host); ip != nil {
        template.IPAddresses = append(template.IPAddresses, ip)
    } else {
        template.DNSNames = append(template.DNSNames, host)
    }

    template.IPAddresses = append(template.IPAddresses, alternateIPs...)
    template.DNSNames = append(template.DNSNames, alternateDNS...)

    derBytes, err := x509.CreateCertificate(cryptorand.Reader, &template, &template, &priv.PublicKey, priv)
    if err != nil {
        return nil, nil, err
    }

    // Generate cert
    certBuffer := bytes.Buffer{}
    if err := pem.Encode(&certBuffer, &pem.Block{Type: "CERTIFICATE", Bytes: derBytes}); err != nil {
        return nil, nil, err
    }

    // Generate key
    keyBuffer := bytes.Buffer{}
    if err := pem.Encode(&keyBuffer, &pem.Block{Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(priv)}); err != nil {
        return nil, nil, err
    }

    return certBuffer.Bytes(), keyBuffer.Bytes(), nil
}

命令行生成

openssl genrsa -out ca.key 2048 生成root密钥
openssl req -new -x509 -sha256 -key ca.key -days 3650 -out ca.crt -subj "/CN=dotcoo.com" 生成root签名
openssl genrsa -out server.key 2048 生成server密钥
openssl req -new -sha256 -key server.key -subj "/CN=server.com" -out server.csr 生成server签名请求
openssl x509 -req -sha256 -in server.csr -CA ca.crt -CAkey ca.key -CAcreateserial -out server.crt -days 3650 给server签名
openssl x509 -in ./server.crt -noout -text 查询状态
1594482-3175f7cb50ae6bf0.png
image.png

接下来我们就可以使用https访问我们的本地服务了

package main

import (
    "net/http"
    "log"
)

type  Controller struct {

}

func (c Controller)ServeHTTP(resp http.ResponseWriter, req *http.Request){
   resp.Write([]byte("返回数据"))
}

func main() {
   监听服务
    http.Handle("/private",&Controller{})
    err := http.ListenAndServeTLS(":10443","/Users/xujie/go/src/awesomeProject/server.crt","/Users/xujie/go/src/awesomeProject/server.key",nil)
    if err != nil {
        log.Fatal(err)
    }
 }
1594482-e4b8263349d5251f.png
image.png

如果你任然使用http访问就会发生错误

2018/09/27 17:28:30 http: TLS handshake error from [::1]:59181: tls: first record does not look like a TLS handshake

16.接管服务器和客户端连接
type Hijacker interface {
    // Hijack让调用者接管连接,返回连接和关联到该连接的一个缓冲读写器。
    // 调用本方法后,HTTP服务端将不再对连接进行任何操作,
    // 调用者有责任管理、关闭返回的连接。
    Hijack() (net.Conn, *bufio.ReadWriter, error)
}
  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值