http 包實現(xiàn)了http客戶端與服務端的實現(xiàn)
- 1.創(chuàng)建http客戶端
- 2.客戶端發(fā)起get,post,postForm請求演示
- 3.自定義客戶端請求
- 4.創(chuàng)建服務端
- 5.自定義服務端配置
- 6.解析HTTP版本號
- 7.將不同格式的時間字符串,轉換成標準time結構
- 8.獲取http狀態(tài)碼Status 對應的文本信息
- 9.監(jiān)測服務器與客戶端的連接狀態(tài)
- 10.獲取請求頭里面的數(shù)據(jù)
- 11.將請求頭的數(shù)據(jù)寫入文件中
- 12.文件服務器創(chuàng)建
- 13.請求重定向演示
- 14.將文件的內容返回給請求
- 15.建立https服務
- 16.接管服務器和客戶端連接
1.創(chuàng)建http客戶端
客戶端請求連接過程
1 創(chuàng)建http客戶端對象
2 按照指定方式請求數(shù)據(jù)
3 獲取數(shù)據(jù)
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() {
// 創(chuàng)建客戶端發(fā)get請求
resp,error := http.DefaultClient.Get("http://example.com/")
// 相應結束后,請及時關閉會話
defer resp.Body.Close()
if error != nil {
log.Fatal(error)
}
// 讀取相應的數(shù)據(jù)
data,error := ioutil.ReadAll(resp.Body)
fmt.Println(string(data))
}
我們看一下響應數(shù)據(jù)結構體都包含了那些數(shù)據(jù)
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 // 傳輸數(shù)據(jù)編碼
Close bool // 是否關閉連接
Uncompressed bool //是否未壓縮
Trailer Header
Request *Request // 原始請求相關
TLS *tls.ConnectionState // https 加密相關
}
2.客戶端發(fā)起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點進行演示
定義請求方式:除了系統(tǒng)提供默認的創(chuàng)建客戶端的方式,我們也可以手動創(chuàng)建客戶端吩抓,比較簡單沈条,直接看下面的例子
package main
import (
"net/http"
"log"
"io/ioutil"
"fmt"
)
func main() {
// 創(chuàng)建一個客戶端
client := &http.Client{}
// 創(chuàng)建一個自定義請求
req,_:= http.NewRequest("Get","http://www.baidu.com",nil)
// 讓客戶端執(zhí)行請求
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))
}
定義請求策略:如果我們不設置請求重定向策略,系統(tǒng)會使用默認的重定向策略
package main
import (
"net/http"
"log"
"fmt"
"io/ioutil"
)
func main() {
// 創(chuàng)建一個客戶端
client := &http.Client{
CheckRedirect:CheckRedirect,
}
// 創(chuàng)建一個自定義請求
req,_:= http.NewRequest("Get","http://www.baidu.com",nil)
// 讓客戶端執(zhí)行請求
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
}
自定義請求頭
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岛琼、壓縮和其他設置雏逾,創(chuàng)建一個Transport
tr := &http.Transport{
TLSClientConfig: &tls.Config{RootCAs: pool},
DisableCompression: true,
}
client := &http.Client{Transport: tr}
4.創(chuàng)建服務器
簡單的請求
func ListenAndServe(addr string, handler Handler) error
參數(shù) Handler 是一個接口類型,需要我們實現(xiàn)下面的接口
type Handler interface {
ServeHTTP(ResponseWriter, *Request)
}
下面我們看一個完整的例子
package main
import (
"net/http"
"fmt"
)
// 第一步 Handle 實現(xiàn)接口
type Controller struct {
}
func (c Controller)ServeHTTP(resp http.ResponseWriter, req *http.Request){
fmt.Println(req.Host)
resp.Write([]byte("我是服務器返回的數(shù)據(jù)"))
req.Body.Close()
}
func main() {
// 第二步 監(jiān)聽服務
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() {
// 第一步 注冊請求匹配函數(shù)
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)
// 服務器相應數(shù)據(jù)
writer.Write([]byte(request.UserAgent()))
request.Body.Close()
})
// 監(jiān)聽服務
http.ListenAndServe(":8081",nil)
}
func Handle(pattern string, handler Handler)
需要自己創(chuàng)建變量實現(xiàn) handle接口,實現(xiàn)的過程我們已經(jīng)演示過了,就不在贅述了
5.自定義服務端配置
上面我們使用 http.ListenAndServe
啟動了一個監(jiān)聽服務,但是我們在實際使用的過程中,有時需要配置一些信息比如
- 讀請求的最大時間
- 寫入的組大時間
- 最大的頭字節(jié)數(shù)
...
下面我們就演示一下如何實現(xiàn)自定義服務端配置的過程
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("我是服務器返回的數(shù)據(jù)"))
req.Body.Close()
}
func main() {
//// 監(jiān)聽服務
server := http.Server{
Addr: ":8080",
Handler: &Controller{},
ReadTimeout: 10 * time.Second,
WriteTimeout: 10 * time.Second,
MaxHeaderBytes: 1 << 20,
}
server.ListenAndServe()
}
瀏覽器輸入 http://localhost:8080
6.解析HTTP 版本號
func ParseHTTPVersion(vers string) (major, minor int, ok bool)
fmt.Println(http.ParseHTTPVersion("HTTP/1.0"))
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狀態(tài)碼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.監(jiān)測服務器與客戶端的連接狀態(tài)
type ConnState int
const (
// StateNew代表一個新的連接,將要立刻發(fā)送請求廊驼。
// 連接從這個狀態(tài)開始据过,然后轉變?yōu)镾tateAlive或StateClosed。
StateNew ConnState = iota
// StateActive代表一個已經(jīng)讀取了請求數(shù)據(jù)1到多個字節(jié)的連接。
// 用于StateAlive的Server.ConnState回調函數(shù)在將連接交付給處理器之前被觸發(fā),
// 等到請求被處理完后馁菜,Server.ConnState回調函數(shù)再次被觸發(fā)鸿秆。
// 在請求被處理后幅聘,連接狀態(tài)改變?yōu)镾tateClosed、StateHijacked或StateIdle。
StateActive
// StateIdle代表一個已經(jīng)處理完了請求、處在閑置狀態(tài)原朝、等待新請求的連接。
// 連接狀態(tài)可以從StateIdle改變?yōu)镾tateActive或StateClosed镶苞。
StateIdle
// 代表一個被劫持的連接喳坠。這是一個終止狀態(tài),不會轉變?yōu)镾tateClosed茂蚓。
StateHijacked
// StateClosed代表一個關閉的連接壕鹉。
// 這是一個終止狀態(tài)。被劫持的連接不會轉變?yōu)镾tateClosed煌贴。
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("我是服務器返回的數(shù)據(jù)"))
req.Body.Close()
}
func main() {
//// 監(jiān)聽服務
server := http.Server{
Addr: ":8080",
Handler: &Controller{},
ReadTimeout: 10 * time.Second,
WriteTimeout: 10 * time.Second,
MaxHeaderBytes: 1 << 1,
}
// 監(jiān)測服務器的連接狀態(tài)
server.ConnState = func(conn net.Conn, state http.ConnState) {
fmt.Println(state)
}
server.ListenAndServe()
}
我們發(fā)現(xiàn)瀏覽器想服務器發(fā)送請求兩次請求,但是只有一個closed狀態(tài),這個是因為一旦服務器與瀏覽器完成數(shù)據(jù)交互后,連接狀態(tài)還是激活狀態(tài),沒有關閉,如果在最大連接時間未到時,客戶端繼續(xù)發(fā)送請求御板,就不會要重新建立連接了锥忿。如果我們想要每次數(shù)據(jù)結束后立即關閉連接牛郑,使用下面的方法即可
server.SetKeepAlivesEnabled(false)
10.獲取請求頭里面的數(shù)據(jù)
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)
刪除請求頭數(shù)據(jù)
func (h Header) Del(key string)
11.將請求頭的數(shù)據(jù)寫入文件中
// 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)
示例 2
file,_ := os.Create("/Users/xujie/go/src/awesomeProject/main/header.txt")
req.Header.WriteSubset(file,map[string]bool{"Accept":true})
通過方法 WriteSubset
我們將header寫入文件時,過濾了 Accept
12.文件服務器創(chuàng)建
func FileServer(root FileSystem) Handler
http.Handle("/login",&Controller{})
http.Handle("/",http.FileServer(http.Dir("/usr/local")))
http.ListenAndServe(":8080",nil)
訪問 http://localhost:8080 即可查看
這里注意一下 我們的注冊 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.請求重定向演示
如果我們訪問一個頁面出現(xiàn)了錯誤,我們讓用戶重定向到一個其他頁面上,類似于這個過程就是重定向
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() {
//// 監(jiān)聽服務
http.Handle("/login",&Controller{})
http.HandleFunc("/error", func(writer http.ResponseWriter, request *http.Request) {
writer.Write([]byte("重定向的錯誤頁面"))
})
http.ListenAndServe(":8080",nil)
}
當我們?yōu)g覽器訪問http://localhost/login ,服務器會重定向到/error 對應的服務上,這個時候瀏覽器的輸入欄的地址也會自動切換到http://localhost/error
如果想直接返回未找到資源的信息提示,可以使用
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() {
//// 監(jiān)聽服務
http.Handle("/private",&Controller{})
http.ListenAndServe(":8080",nil)
}
15.建立https服務
ListenAndServeTLS函數(shù)和ListenAndServe函數(shù)的行為基本一致础芍,除了它期望HTTPS連接之外杈抢。此外,必須提供證書文件和對應的私鑰文件仑性。如果證書是由權威機構簽發(fā)的惶楼,certFile參數(shù)必須是順序串聯(lián)的服務端證書和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 查詢狀態(tài)
接下來我們就可以使用https訪問我們的本地服務了
package main
import (
"net/http"
"log"
)
type Controller struct {
}
func (c Controller)ServeHTTP(resp http.ResponseWriter, req *http.Request){
resp.Write([]byte("返回數(shù)據(jù)"))
}
func main() {
//// 監(jiān)聽服務
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)
}
}
如果你任然使用http訪問就會發(fā)生錯誤
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讓調用者接管連接歼捐,返回連接和關聯(lián)到該連接的一個緩沖讀寫器。
// 調用本方法后晨汹,HTTP服務端將不再對連接進行任何操作豹储,
// 調用者有責任管理、關閉返回的連接淘这。
Hijack() (net.Conn, *bufio.ReadWriter, error)
}