数码控科技猎奇Iphone动漫星座游戏电竞lolcosplay王者荣耀攻略allcnewsBLOGNEWSBLOGASKBLOGBLOGZSK全部技术问答问答技术问答it问答代码软件新闻开发博客电脑/网络手机/数码笔记本电脑互联网操作系统软件硬件编程开发360产品资源分享电脑知识文档中心IT全部全部分类全部分类技术牛文全部分类教程最新网页制作cms教程平面设计媒体动画操作系统网站运营网络安全服务器教程数据库工具网络安全软件教学vbscript正则表达式javascript批处理更多»编程更新教程更新游戏更新allitnewsJava新闻网络医疗信息化安全创业站长电商科技访谈域名会议专栏创业动态融资创投创业学院 / 产品经理创业公司人物访谈营销开发数据库服务器系统虚拟化云计算嵌入式移动开发作业作业1常见软件all电脑网络手机数码生活游戏体育运动明星影音休闲爱好文化艺术社会民生教育科学医疗健康金融管理情感社交地区其他电脑互联网软件硬件编程开发360相关产品手机平板其他电子产品摄影器材360硬件通讯智能设备购物时尚生活常识美容塑身服装服饰出行旅游交通汽车购房置业家居装修美食烹饪单机电脑游戏网页游戏电视游戏桌游棋牌游戏手机游戏小游戏掌机游戏客户端游戏集体游戏其他游戏体育赛事篮球足球其他运动球类运动赛车健身运动运动用品影视娱乐人物音乐动漫摄影摄像收藏宠物幽默搞笑起名花鸟鱼虫茶艺彩票星座占卜书画美术舞蹈小说图书器乐声乐小品相声戏剧戏曲手工艺品历史话题时事政治就业职场军事国防节日风俗法律法规宗教礼仪礼节自然灾害360维权社会人物升学入学人文社科外语资格考试公务员留学出国家庭教育学习方法语文物理生物工程学农业数学化学健康知识心理健康孕育早教内科外科妇产科儿科皮肤科五官科男科整形中医药品传染科其他疾病医院两性肿瘤科创业投资企业管理财务税务银行股票金融理财基金债券保险贸易商务文书国民经济爱情婚姻家庭烦恼北京上海重庆天津黑龙江吉林辽宁河北内蒙古山西陕西宁夏甘肃青海新疆西藏四川贵州云南河南湖北湖南山东江苏浙江安徽江西福建广东广西海南香港澳门台湾海外地区

Go语言的http/2服务器功能及客户端使用

来源:脚本之家  责任编辑:小易  

前言

大家都知道,Go的标准库HTTP服务器默认支持HTTP/2。那么,在这篇文章中,我们将首先展示Go的http/2服务器功能,并解释如何将它们作为客户端使用。

在这篇文章中,我们将首先展示Go的http/2服务器功能,并解释如何将它们作为客户端使用。Go的标准库HTTP服务器默认支持HTTP/2。

下面话不多说了,来一起看看详细的介绍吧

HTTP/2 服务器

首先,让我们在Go中创建一个http/2服务器!根据http/2文档,所有东西都是为我们自动配置的,我们甚至不需要导入Go的标准库http2包:

HTTP/2强制使用TLS。为了实现这一点,我们首先需要一个私钥和一个证书。在Linux上,下面的命令执行这个任务。

openssl req -newkey rsa:2048 -nodes -keyout server.key -x509 -days 365 -out server.crt

该命令将生成两个文件:server.key 以及 server.crt

现在,对于服务器代码,以最简单的形式,我们将使用Go的标准库HTTP服务器,并启用TLS与生成的SSL文件。

package main

import (
 "log"
 "net/http"
)
 
func main() {
 // 在 8000 端口启动服务器
 // 确切地说,如何运行HTTP/1.1服务器。

 srv := &http.Server{Addr:":8000", Handler: http.HandlerFunc(handle)}
 // 用TLS启动服务器,因为我们运行的是http/2,它必须是与TLS一起运行。
 // 确切地说,如何使用TLS连接运行HTTP/1.1服务器。
 log.Printf("Serving on https://0.0.0.0:8000")
 log.Fatal(srv.ListenAndServeTLS("server.crt", "server.key"))
}

func handle(w http.ResponseWriter, r *http.Request) {
 // 记录请求协议
 log.Printf("Got connection: %s", r.Proto)
 // 向客户发送一条消息
 w.Write([]byte("Hello"))
}

HTTP/2 客户端

在go中,标准 http.Client 也用于http/2请求。惟一的区别是在客户端的Transport字段,使用 http2.Transport 代替 http.Transport。

我们生成的服务器证书是“自签名”的,这意味着它不是由一个已知的证书颁发机构(CA)签署的。这将导致我们的客户端不相信它:

package main

import (
 "fmt"
 "net/http"
)

const url = "https://localhost:8000"

func main() {
 _, err := http.Get(url)
 fmt.Println(err)
}

让我们试着运行它:

$ go run h2-client.go 
Get https://localhost:8000: x509: certificate signed by unknown authority

在服务器日志中,我们还将看到客户端(远程)有一个错误:

http: TLS handshake error from [::1]:58228: remote error: tls: bad certificate

为了解决这个问题,我们可以用定制的TLS配置去配置我们的客户端。我们将把服务器证书文件添加到客户端“证书池”中,因为我们信任它,即使它不是由已知CA签名的。

我们还将添加一个选项,根据命令行标志在HTTP/1.1和HTTP/2传输之间进行选择。

package main

import (
 "crypto/tls"
 "crypto/x509"
 "flag"
 "fmt"
 "io/ioutil"
 "log"
 "net/http"
 "golang.org/x/net/http2"
)
 
const url = "https://localhost:8000"

var httpVersion = flag.Int("version", 2, "HTTP version")

func main() {
 flag.Parse()
 client := &http.Client{}
 // Create a pool with the server certificate since it is not signed
 // by a known CA
 caCert, err := ioutil.ReadFile("server.crt")
 if err != nil {
 log.Fatalf("Reading server certificate: %s", err)
 }
 caCertPool := x509.NewCertPool()
 caCertPool.AppendCertsFromPEM(caCert)
 // Create TLS configuration with the certificate of the server
 tlsConfig := &tls.Config{
 RootCAs: caCertPool,
 }

 // Use the proper transport in the client
 switch *httpVersion {
 case 1:
 client.Transport = &http.Transport{
 TLSClientConfig: tlsConfig,
 }
 case 2:
 client.Transport = &http2.Transport{
 TLSClientConfig: tlsConfig,
 }
 }
 // Perform the request
 resp, err := client.Get(url)

 if err != nil {
 log.Fatalf("Failed get: %s", err)
 }
 defer resp.Body.Close()
 body, err := ioutil.ReadAll(resp.Body)
 if err != nil {
 log.Fatalf("Failed reading response body: %s", err)
 }
 fmt.Printf(
 "Got response %d: %s %s\n",
 resp.StatusCode, resp.Proto, string(body)
 )
}

这一次我们得到了正确的回应:

$ go run h2-client.go 
Got response 200: HTTP/2.0 Hello

在服务器日志中,我们将看到正确的日志线:获得连接:Got connection: HTTP/2.0!!
但是当我们尝试使用HTTP/1.1传输时,会发生什么呢?

$ go run h2-client.go -version 1
Got response 200: HTTP/1.1 Hello

我们的服务器对HTTP/2没有任何特定的东西,所以它支持HTTP/1.1连接。这对于向后兼容性很重要。此外,服务器日志表明连接是HTTP/1.1:Got connection: HTTP/1.1。

HTTP/2 高级特性

我们创建了一个HTTP/2客户机-服务器连接,并且我们正在享受安全有效的连接带来的好处。但是HTTP/2提供了更多的特性,让我们来研究它们!

服务器推送

HTTP/2允许服务器推送“使用给定的目标构造一个合成请求”。
这可以很容易地在服务器处理程序中实现(在github上的视图):

func handle(w http.ResponseWriter, r *http.Request) {
 // Log the request protocol
 log.Printf("Got connection: %s", r.Proto)
 // Handle 2nd request, must be before push to prevent recursive calls.
 // Don't worry - Go protect us from recursive push by panicking.
 if r.URL.Path == "/2nd" {
 log.Println("Handling 2nd")
 w.Write([]byte("Hello Again!"))
 return
 }

 // Handle 1st request
 log.Println("Handling 1st")
 // Server push must be before response body is being written.
 // In order to check if the connection supports push, we should use
 // a type-assertion on the response writer.
 // If the connection does not support server push, or that the push
 // fails we just ignore it - server pushes are only here to improve
 // the performance for HTTP/2 clients.
 pusher, ok := w.(http.Pusher)

 if !ok {
 log.Println("Can't push to client")
 } else {
 err := pusher.Push("/2nd", nil)
  if err != nil {
 log.Printf("Failed push: %v", err)
 }
 }
 // Send response body
 w.Write([]byte("Hello"))
}

使用服务器推送

让我们重新运行服务器,并测试客户机。

对于HTTP / 1.1客户端:

$ go run ./h2-client.go -version 1
Got response 200: HTTP/1.1 Hello

服务器日志将显示:

Got connection: HTTP/1.1Handling 1st
Can't push to client

HTTP/1.1客户端传输连接产生一个 http.ResponseWriter 没有实现http.Pusher,这是有道理的。在我们的服务器代码中,我们可以选择在这种客户机的情况下该做什么。

对于HTTP/2客户:

go run ./h2-client.go -version 2
Got response 200: HTTP/2.0 Hello

服务器日志将显示:

Got connection: HTTP/2.0Handling 1st
Failed push: feature not supported

这很奇怪。我们的HTTP/2传输的客户端只得到了第一个“Hello”响应。日志表明连接实现了 http.Pusher 接口——但是一旦我们实际调用 Push() 函数——它就失败了。

排查发现,HTTP/2客户端传输设置了一个HTTP/2设置标志,表明推送是禁用的。

因此,目前没有选择使用Go客户机来使用服务器推送。

作为一个附带说明,google chrome作为一个客户端可以处理服务器推送。

服务器日志将显示我们所期望的,处理程序被调用两次,路径 / 和 /2nd,即使客户实际上只对路径 /:

Got connection: HTTP/2.0Handling 1st
Got connection: HTTP/2.0Handling 2nd

全双工通信

Go HTTP/2演示页面有一个echo示例,它演示了服务器和客户机之间的全双工通信。

让我们先用CURL来测试一下:

$ curl -i -XPUT --http2 https://http2.golang.org/ECHO -d hello
HTTP/2 200 
content-type: text/plain; charset=utf-8
date: Tue, 24 Jul 2018 12:20:56 GMT

HELLO 

我们把curl配置为使用HTTP/2,并将一个PUT/ECHO发送给“hello”作为主体。服务器以“HELLO”作为主体返回一个HTTP/2 200响应。但我们在这里没有做任何复杂的事情,它看起来像是一个老式的HTTP/1.1半双工通信,有不同的头部。让我们深入研究这个问题,并研究如何使用HTTP/2全双工功能。

服务器实现

下面是HTTP echo处理程序的简化版本(不使用响应)。它使用 http.Flusher 接口,HTTP/2添加到http.ResponseWriter。

type flushWriter struct {
 w io.Writer
}

func (fw flushWriter) Write(p []byte) (n int, err error) {
 n, err = fw.w.Write(p)
 // Flush - send the buffered written data to the client
 if f, ok := fw.w.(http.Flusher); ok {
 f.Flush()
 }
  return
}

func echoCapitalHandler(w http.ResponseWriter, r *http.Request) {
 // First flash response headers
 if f, ok := w.(http.Flusher); ok {
 f.Flush()
 }
 // Copy from the request body to the response writer and flush
 // (send to client)
 io.Copy(flushWriter{w: w}, r.Body)
}

服务器将从请求正文读取器复制到写入ResponseWriter和 Flush() 的“冲洗写入器”。同样,我们看到了笨拙的类型断言样式实现,冲洗操作将缓冲的数据发送给客户机。

请注意,这是全双工,服务器读取一行,并在一个HTTP处理程序调用中重复写入一行。

GO客户端实现

我试图弄清楚一个启用了HTTP/2的go客户端如何使用这个端点,并发现了这个Github问题。提出了类似于下面的代码。

const url = "https://http2.golang.org/ECHO"

func main() {
  // Create a pipe - an object that implements `io.Reader` and `io.Writer`. 
  // Whatever is written to the writer part will be read by the reader part.

  pr, pw := io.Pipe() 
  // Create an `http.Request` and set its body as the reader part of the
  // pipe - after sending the request, whatever will be written to the pipe,
  // will be sent as the request body.  // This makes the request content dynamic, so we don't need to define it
  // before sending the request.
 req, err := http.NewRequest(http.MethodPut, url, ioutil.NopCloser(pr))

 if err != nil {
 log.Fatal(err)
 }
 
  // Send the request
 resp, err := http.DefaultClient.Do(req) if err != nil {
   log.Fatal(err)
   }
 log.Printf("Got: %d", resp.StatusCode) 
 // Run a loop which writes every second to the writer part of the pipe
 // the current time.
 go func() { for { 
 time.Sleep(1 * time.Second)
 fmt.Fprintf(pw, "It is now %v\n", time.Now())
 }
 }() 

  // Copy the server's response to stdout.
 _, err = io.Copy(os.Stdout, res.Body)

 log.Fatal(err)
}

总结

Go支持与服务器推送和全双工通信的HTTP/2连接,这也支持HTTP/1.1与标准库的标准TLS服务器的连接——这太不可思议了。对于标准的库HTTP客户端,它不支持服务器推送,但是支持标准库的标准HTTP的全双工通信。以上就是本篇的内容,大家有什么疑问可以在文章下面留言沟通。

好了,以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,如果有疑问大家可以留言交流,谢谢大家对脚本之家的支持。

您可能感兴趣的文章:


  • 本文相关:
  • golang实现http服务器处理静态文件示例
  • golang的http基本认证机制实例详解
  • 浅谈golang的http cookie用法
  • golang编程入门之http请求天气实例
  • golang设置http response响应头与填坑记录
  • go语言通过http抓取网页的方法
  • go语言实现一个最简单的http文件服务器实例
  • go语言中利用http发起get和post请求的方法示例
  • go语言实现简单http服务的方法
  • 浅谈go语言的空标示符
  • golang sql连接池的实现方法详解
  • go语言中的switch用法实例分析
  • go实现redigo的简单操作
  • golang判断chan channel是否关闭的方法
  • go语言hello world实例
  • go语言base64加密解密的方法
  • go语言实现的端口扫描器分享
  • 关于golang中平行赋值浅析
  • go语言的变量、函数、socks5代理服务器示例详解
  • 免责声明 - 关于我们 - 联系我们 - 广告联系 - 友情链接 - 帮助中心 - 频道导航
    Copyright © 2017 www.zgxue.com All Rights Reserved