惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

U
Unit 42
P
Proofpoint News Feed
The Last Watchdog
The Last Watchdog
S
Secure Thoughts
cs.AI updates on arXiv.org
cs.AI updates on arXiv.org
N
News | PayPal Newsroom
Application and Cybersecurity Blog
Application and Cybersecurity Blog
O
OpenAI News
S
Security @ Cisco Blogs
宝玉的分享
宝玉的分享
Hacker News: Ask HN
Hacker News: Ask HN
T
Troy Hunt's Blog
Google Online Security Blog
Google Online Security Blog
WordPress大学
WordPress大学
有赞技术团队
有赞技术团队
TaoSecurity Blog
TaoSecurity Blog
Help Net Security
Help Net Security
Latest news
Latest news
NISL@THU
NISL@THU
S
Security Affairs
博客园_首页
C
CXSECURITY Database RSS Feed - CXSecurity.com
博客园 - 聂微东
AI
AI
www.infosecurity-magazine.com
www.infosecurity-magazine.com
Recent Announcements
Recent Announcements
P
Privacy & Cybersecurity Law Blog
小众软件
小众软件
cs.CV updates on arXiv.org
cs.CV updates on arXiv.org
Hugging Face - Blog
Hugging Face - Blog
博客园 - 司徒正美
AWS News Blog
AWS News Blog
W
WeLiveSecurity
Google DeepMind News
Google DeepMind News
I
InfoQ
Schneier on Security
Schneier on Security
Recent Commits to openclaw:main
Recent Commits to openclaw:main
T
The Exploit Database - CXSecurity.com
IT之家
IT之家
T
Threatpost
Scott Helme
Scott Helme
L
LINUX DO - 热门话题
腾讯CDC
CTFtime.org: upcoming CTF events
CTFtime.org: upcoming CTF events
N
News and Events Feed by Topic
L
LINUX DO - 最新话题
F
Full Disclosure
大猫的无限游戏
大猫的无限游戏
H
Heimdal Security Blog
S
SegmentFault 最新的问题

文章列表 on polarisxu

通过一个例子让你彻底掌握 Go 工作区模式 这道题正确率竟然只有 22% Go 开源项目推荐:一个简单的 Go 练手项目 go test 如何禁用缓存? Go1.18 快讯:constraints 包被移除标准库 跟着 Go 作者学泛型 「2022 版」轻松搞定 Go 开发环境 鹅厂小哥用行动反对加班:程序员加班能被终结吗? Go 为什么选择 Gopher 作为吉祥物? 本地如何配置多个 GitHub/Gitee 账号? 为 Java 开发者准备的 Go 教程 02:Java 有而 Go 无 GitHub 发现了 studygolang 项目依赖的漏洞 Typora 宣布收费后,这款开源 Markdown 编辑器火了 为 Java 开发者准备的 Go 教程 01:漫游了 我的 2021 年终总结 泛型版 singleflight:Go 中如何防止缓存击穿? 2021 年你写了多少代码?这个 Go 工具帮你统计 Go1.18 新特性:TryLock Go 泛型入门教程 程序员瑞士军刀:各种结构的转换工具 终于有 Go 版的 Elasticsearch 了 Go泛型系列:maps 包讲解 Go 1.18 中的 any 是什么? Go1.18 快讯:新的 IP 包 Go编程模式:详解函数式选项模式 Go泛型系列:slices 包讲解 Nginx 竟然也有 playground — Go 语言构建的 Go泛型系列:Go1.18 类型约束那些事 Go1.18 快讯:Module 工作区模式 Go1.18 快讯:新增的 Cut 函数太方便了 Go1.18 快讯:废弃了这个 API Go1.18 快讯:新增字符串 Clone API Go:如何获得项目根目录? Go 如何获取和设置环境变量 重磅变动:更快找到微信公众号里的Go文章 Go 这样设置版本号:我们的项目也可以 假期结束,推荐 2 本 manning 出的 Go 图书 Go Fiber 框架系列教程 04:测试应用 Go Fiber 框架系列教程 03:中间件 Go泛型系列:提前掌握Go泛型的基本使用 推荐一个 Go GUI 实战项目 Go Fiber 框架系列教程 02:详解相关 API 的使用 Go Fiber 框架系列教程 01: 和 Express 对比学习 Go Module:私有不合规库怎么解决引用问题 Go1.17 新特性:go get 变了 Go1.17 新特性:testing 包的相关变化 StackOverflow 上关于 Go select 死锁的问题 新书推荐:用 Gin 框架构建分布式应用 Go 官网要变天。。。 Gin 这是要成为 Go 官方框架? 扬眉吐气:刚刚,Go 已经默认支持泛型了 Go 1.17 新特性:Module 有哪些变化? Go 第三方库推荐:类型转换如此简单 担心密码提交到 GitHub?建议使用这个 Go 开源工具 Go 的时间格式化为什么是 2006-01-02 15:04:05? GitHub 为 Go 社区带来安全支持 Go开源项目推荐:500行代码确认请求时间花在哪 一道关于 len 函数的诡异 Go 面试题解析 厉害了我的 Go:推荐系统都有开源实现 Go1.17 新特性:新版构建约束 这个工具真好:看看你的Go项目依赖有无漏洞 网友很强大,发现了Go并发下载的Bug Rust 劝退系列 09:函数 Go项目实战:一步步构建一个并发文件下载器 这本 Go 新书挺期待的:100 个常见错误 Go 启用新的官方问答社区 这是要干嘛?!微软招 Go 编译器全职开发人员 Go1.17 快报之标准库越来越注重易用性 Go1.17 新特性之切片变数组 回顾 Go 官网的演变史 站长8年前的Go代码竟然进入大厂的项目里了 Go图书翻译:一个好消息,一个坏消息 不怕烂尾!决定组织翻译这本 Go 图书 Rust 劝退系列 08:模式匹配 一本花了2.5年写成的Go免费在线图书 被黑惨了:一句话,说明自己会 Go,咋整? Go1.16 中的新函数 signal.NotifyContext 怎么用? Go 1.17 新特性提前学之测试执行随机化 Rust 劝退系列 07:流程控制 官方的 Go 多版本管理:使用和原理 Rust新书:给你一个劝退的理由 周刊题解:常量表达式这个规则应该了解下 盘点那些使用 Go 语言的国外公司 编写了50万行Go代码是一种什么体验 Rust 劝退系列 06:常量 Uber 使用 Go 的规模这么大?!都自己定制的 Go 编译器了 Go Team Leader — rsc 大神新开源了一个库,增强模板功能 再一次看到了 Go 的节制:int128 类型要不要支持? Rust 劝退系列 05:复合数据类型 「卷」有理论依据:海勒姆定律—Go又是怎么卷的 Go 真的也可以进行 GUI 开发:还有这样的图书呢 Rust 劝退系列 04:基本数据类型 我的 Go 语言书单 Rust 劝退系列 03:变量 我又来推荐免费 Go 新书了:一本用 Go 讲架构的书 注释竟然还有特殊用途?一文解惑 //go:linkname 指令 这个功能,公众号、微信群会不会被玩坏? 一道 Go 闭包题,面试官说原来自己答错了:面别人也涨知识 Rust 劝退系列 02:第一个 Rust 程序 Rust 劝退系列 01:打造开发环境
Echo 系列教程——定制篇6:自定义 Server 相关,替换或扩展默认的 Server
2020-03-16 · via 文章列表 on polarisxu

这是定制篇的最后一节,讲解 Server 的自定义。

通常情况下,Echo 通过如下方式启动一个 HTTP Server:

e := echo.New()
e.GET("/", func(c echo.Context) error {
  return c.String(http.StatusOK, "Hello, World!")
})
e.Logger.Fatal(e.Start(":2020"))

我们打开源码一探究竟:

// Start starts an HTTP server.
func (e *Echo) Start(address string) error {
	e.Server.Addr = address
	return e.StartServer(e.Server)
}

原来调用的是 StartServer,参数是默认的 Server 实例,它是一个 http.Server 的指针类型。

自定义 Server

知道了 Echo#Start 最终调用的是 Echo#StartServer,而且它的参数是一个 *http.Server 类型,所以自定义一个 Server 很容易。我们先看看 http.Server 这个结构。

type Server struct {
  	// 指定 TCP 的监听地址,形式:"host:port"。如果空,则使用 ":http",即 80 端口
    Addr string
	
		// 当前服务器的 Handler,如果未设置,使用 http.DefaultServeMux
    Handler Handler

  	// TLS 配置
    TLSConfig *tls.Config

  	// 读取整个 request(包括 body)的最大允许时间(超时时间)
    ReadTimeout time.Duration

  	// 从 Go1.8 开始增加。只是读取请求头的最大允许时间(超时时间)
    ReadHeaderTimeout time.Duration // Go 1.8

  	// 和 ReadTimeout 对应,这是写 response 的最大允许时间(超时时间)
    WriteTimeout time.Duration

  	// 从 Go1.8 开始增加。下个请求到来前允许的最大空闲时间(keep-alive 启用时)。
  	// 如果是 0,使用 ReadTimeout,都为 0 ,不会超时。
    IdleTimeout time.Duration // Go 1.8

  	// 允许的最大请求头(单位字节),默认值 DefaultMaxHeaderBytes
    MaxHeaderBytes int
		
  	// 其他字段忽略。。。
}

默认情况下启动的 Server,超时时间是 0,也就是不会超时。我们自定义一个 Server,设置超时时间:

s := &http.Server{
  Addr:         ":2020",
  ReadTimeout:  10 * time.Second,
  WriteTimeout: 10 * time.Second,
}
e.Logger.Fatal(e.StartServer(s))

为了方便验证,我们将超时时间设置的较短:10s,实际项目这个肯定太短了。

验证读超时是否生效

往下看之前,你想想用什么办法可以验证?

因为 HTTP 是基于 TCP 的,我们可以简单的通过建立 TCP 连接,然后等待一段时间后,再发送 HTTP 报文,具体代码如下:

func main() {
	conn, err := net.Dial("tcp", "127.0.0.1:2020")
	if err != nil {
		panic(err)
	}
	time.Sleep(11 * time.Second)
	fmt.Fprintf(conn, "GET / HTTP/1.0\r\n\r\n")
	status, err := bufio.NewReader(conn).ReadString('\n')
	fmt.Println(status, err)
}

通过修改 Sleep 的时间来控制等待时间,可以看看 10s 以内是否成功(刚好 10s 可能也会失败),10s 以上又是什么情况。

注意,这里的 HTTP/1.0 不能改为 1.1 等,否则得提供 Host 头部

Sleep 设置为 11s ,运行客户端后,如果返回 EOF,则表明超时设置生效了。

自定义 Listener

Echo 框架支持自定义 Listener,即通过 Echo#Listener 进行设置。例如:

l, err := net.Listen("tcp", ":1323")
if err != nil {
  e.Logger.Fatal(l)
}
e.Listener = l
e.Logger.Fatal(e.Start(""))

什么场景下会需要自定义?或者说为什么 Echo 要让用户可以自定义 Listener?我认为有两种考虑:

  1. Listener 支持配置,具体就是 net.ListenConfig 类型。实际上,net 包中,net.Listen() 函数就是用了这个类型获取 Listener 实例;

  2. 除了使用 net.Listen 获取一个 Listener 接口的实例外,还有其他的方式可以获取 Listener 接口的实例;

UnixListener

我们着重看看第 2 种情况。

net.Listener 是一个接口。在 net 库中,有 TCPListener 和 UnixListener,除此之外,其他的 Listener 都是基于这两种。比如 net.FileListener() 函数,最终是 TCPListener。

一般我们使用的就是 TCPListener,如果想在 echo 中使用 UnixListener,如何实现?(这个例子没有太多现实意义)

func main() {
	unixAddr, err := net.ResolveUnixAddr("unix", "/tmp/echo.sock")
	if err != nil {
		panic(err)
	}
	unixListener, err := net.ListenUnix("unix", unixAddr)
	if err != nil {
		panic(err)
	}

	e := echo.New()

	e.GET("/", func(ctx echo.Context) error {
		return ctx.String(http.StatusOK, "Hello Unix!")
	})

	e.Listener = unixListener
	e.Logger.Fatal(e.Start(""))
}

因为基于 Unix Domain Socket,所以,没法直接浏览器访问。写一个测试程序:

func main() {
	unixAddr, err := net.ResolveUnixAddr("unix", "/tmp/echo.sock")
	if err != nil {
		panic(err)
	}
	conn, err := net.DialUnix("unix", nil, unixAddr)
	if err != nil {
		panic(err)
	}
	fmt.Fprintf(conn, "GET / HTTP/1.0\r\n\r\n")
	body, err := ioutil.ReadAll(conn)
	if err != nil {
		panic(err)
	}
	defer conn.Close()
	fmt.Println(string(body))
}

客户端能够输出:

HTTP/1.0 200 OK
Content-Type: text/plain; charset=UTF-8
Date: Wed, 25 Mar 2020 06:35:12 GMT
Content-Length: 13

Hello Unix!

表示成功!

再提醒下,一般不会定义 Listener,你知晓能定义即可。

TLS 相关

这里顺便介绍下 Echo 对 TLS 的支持。一般来说是用不到的,因为实际中,我们会使用 Web Server 进行反向代理,由 Web Server 来处理 TLS。但了解相关内容还是有利的。

HTTPS Server

看下相应的方法签名:

func (e *Echo) StartTLS(address string, certFile, keyFile interface{}) (err error)

相比 Echo#Start 方法,StartTLS 多了两个参数:certFile 和 keyFile。这两个参数是什么意思?这里简单介绍下,知晓什么意思即可。

  • certFile:证书
  • keyFile:私钥

net/http 包,这两个参数是字符串,代表两个文件的路径。Echo 对此进行了扩展,除了可以代表文件的路径,还可以是字节数组,表示证书和私钥的具体内容。

CA 证书可以购买,也可以申请免费的(阿里云、七牛云都有),也可以使用 let’s encrypt 免费证书。这里我们先自己生成证书,本地验证。(注意,自己生成的证书,浏览器是不认的)

证书生成

证书的生成可以用 Linux/Mac 的 OpenSSL 工具链。对于一个网站,首先必须有自己的私钥,私钥的生成方式为:

$ cd go-cache-example # 切换到我们的项目根目录
$ openssl genrsa -out server.key 2048

会在当前目录下生成一个 server.key 文件,这就是私钥。

利用私钥就可以生成证书了。OpenSSL 使用 x509 命令生成证书。这里需要区分两个概念:证书(certificate)和证书请求(certificate sign request)

  • 证书是自签名或 CA 签名过的凭据,用来进行身份认证
  • 证书请求是对签名的请求,需要使用私钥进行签名

x509 命令可以将证书和证书请求相互转换。

从私钥可以生成自签名证书:(跳过了证书请求,一步到位)

openssl req -new -x509 -key server.key -out server.crt -days 365

req 命令会通过命令行要求用户输入国家、地区、组织等信息,这些信息会附加在证书中展示给连接方。

You are about to be asked to enter information that will be incorporated
into your certificate request.
What you are about to enter is what is called a Distinguished Name or a DN.
There are quite a few fields but you can leave some blank
For some fields there will be a default value,
If you enter '.', the field will be left blank.
-----
Country Name (2 letter code) []:CN
State or Province Name (full name) []:Beijing
Locality Name (eg, city) []:Beijing
Organization Name (eg, company) []:studygolang
Organizational Unit Name (eg, section) []:
Common Name (eg, fully qualified host name) []:localhost
Email Address []:polaris@studygolang.com

以上命令会在当前目录生成 server.crt 文件。

这样我们有了上面需要的 cert 和 key。

验证本地的 HTTPS Server

在 go-cache-example 项目中的 cmd/custom/main.go 包含如下代码:

func main() {
	e := echo.New()

	e.GET("/", func(ctx echo.Context) error {
		return ctx.String(http.StatusOK, "Hello TLS!")
	})

	e.Logger.Fatal(e.StartTLS(":2020", "server.crt", "server.key"))
}

启动服务:go run cmd/custom/main.go

   ____    __
  / __/___/ /  ___
 / _// __/ _ \/ _ \
/___/\__/_//_/\___/ v4.1.15
High performance, minimalist Go web framework
https://echo.labstack.com
____________________________________O/_______
                                    O\
⇨ https server started on [::]:2020

打开 Chrome 浏览器,请求:https://localhost:2020 (注意是 HTTPS),出现如下错误:

在浏览器输出:chrome://flags/#allow-insecure-localhost ,高亮的置为 Enabled,Reload Chrome。

再次请求 https://localhost:2020 ,能正常输出:Hello TLS!表明成功了。

通过 FireFox 浏览器,可以方便看到,我们这个服务支持 HTTP/2:

Echo 还支持自动 TLS,即:Echo#StartAutoTLS 方法,它从 https://letsencrypt.org 获取证书。这涉及到正式域名等,在此不演示!

自定义 HTTP/2 Cleartext Server

默认情况下,HTTP/2 是基于 TLS 的。上面也看到,当是 HTTPS Server 时,浏览器支持 HTTP/2,默认使用了它。如果我们想使用 HTTP/2,但是又不想使用 TLS,可以吗?这就是 H2C,即:HTTP/2 Cleartext Server。

从 Echo4.1.15 开始支持启动 H2C 服务器:

func (e *Echo) StartH2CServer(address string, h2s *http2.Server) (err error)

Go 从 1.6 开始,标准库 net/http 支持 HTTP/2,Go1.8 支持 HTTP/2 的 Push。Go 对 HTTP/2 的支持,最初通过 golang.org/x/net/http2 包实现。Echo 中,上面方法的参数 http2.Server 就是 golang.org/x/net/http2 包中的。

实现一个支持 H2C 的 HTTP Server:

func main() {
	e := echo.New()

	e.GET("/", func(ctx echo.Context) error {
		fmt.Println(ctx.Request().Proto)
		return ctx.String(http.StatusOK, "Hello HTTP/2!")
	})

	server := &http2.Server{
		MaxConcurrentStreams: 250,
		MaxReadFrameSize:     1048576,
		IdleTimeout:          10 * time.Second,
	}
	e.Logger.Fatal(e.StartH2CServer(":2020", server))
}

然而,因为现代的浏览器都不支持 H2C,所以测试我们通过 curl 来进行。

$ curl -v --http2 -k http://localhost:2020

*   Trying ::1...
* TCP_NODELAY set
* Connected to localhost (::1) port 2020 (#0)
> GET / HTTP/1.1
> Host: localhost:2020
> User-Agent: curl/7.64.1
> Accept: */*
> Connection: Upgrade, HTTP2-Settings
> Upgrade: h2c
> HTTP2-Settings: AAMAAABkAARAAAAAAAIAAAAA
>
< HTTP/1.1 101 Switching Protocols
< Connection: Upgrade
< Upgrade: h2c
* Received 101
* Using HTTP2, server supports multi-use
* Connection state changed (HTTP/2 confirmed)
* Copying HTTP/2 data in stream buffer to connection buffer after upgrade: len=0
* Connection state changed (MAX_CONCURRENT_STREAMS == 250)!
< HTTP/2 200
< content-type: text/plain; charset=UTF-8
< content-length: 15
< date: Thu, 26 Mar 2020 08:20:58 GMT
<
* Connection #0 to host localhost left intact
Hello HTTP/2!* Closing connection 0

而且服务端的输出也确实证明是 HTTP/2。

另外可以通过 Echo#DisableHTTP2 禁用 HTTP/2。

至此,关于 Server 的定制就介绍完了。Echo 的定制也全部讲解完。