脚本之家,脚本语言编程技术及教程分享平台!
分类导航

Python|VBS|Ruby|Lua|perl|VBA|Golang|PowerShell|Erlang|autoit|Dos|bat|

服务器之家 - 脚本之家 - Golang - 1行Go代码实现反向代理的示例

1行Go代码实现反向代理的示例

2020-05-18 11:08alfred-zhong Golang

这篇文章主要介绍了1行Go代码实现反向代理的示例,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧

暂且放下你的编程语言来瞻仰下我所见过的最棒的标准库。

1行Go代码实现反向代理的示例

为项目选择编程语言和挑选你最爱的球队不一样。应该从实用主义出发,根据特定的工作选择合适的工具。

在这篇文章中我会告诉你从何时开始并且为什么我认为 Go 语言如此闪耀,具体来说是它的标准库对于基本的网络编程来说显得非常稳固。更具体一点,我们将要编写一个反向代理程序。

Go 为此提供了很多,但真正支撑起它的在于这些低级的网络管道任务,没有更好的语言了。

反向代理是什么? 有个很棒的说法是流量转发 。我获取到客户端来的请求,将它发往另一个服务器,从服务器获取到响应再回给原先的客户端。反向的意义简单来说在于这个代理自身决定了何时将流量发往何处。

1行Go代码实现反向代理的示例

为什么这很有用?因为反向代理的概念是如此简单以至于它可以被应用于许多不同的场景:负载均衡,A/B 测试,高速缓存,验证等等。

当读完这篇文章之后,你会学到:

  • 如何响应 HTTP 请求
  •  如何解析请求体
  • 如何通过反向代理将流量转发到另一台服务器

 我们的反向代理项目

我们来实际写一下项目。我们需要一个 Web 服务器能够提供以下功能:

  • 获取到请求
  • 读取请求体,特别是 proxy_condition 字段
  • 如果代理域为 A,则转发到 URL 1
  • 如果代理域为 B,则转发到 URL 2
  • 如果代理域都不是以上,则转发到默认的 URL

准备工作

  •  Go 语言环境。
  • http-server 用来创建简单的服务。

环境配置

我们要做的第一件事是将我们的配置信息写入环境变量,如此就可以使用它们而不必写死在我们的源代码中。

我发现最好的方式是创建一个包含所需环境变量的 .env 文件。

以下就是我为特定项目编写的文件内容:

?
1
2
3
4
export PORT=1330
export A_CONDITION_URL="http://localhost:1331"
export B_CONDITION_URL="http://localhost:1332"
export DEFAULT_CONDITION_URL=<a rel="external nofollow" href="http://localhost:1333/">http://localhost:1333</a>

这是我从 12 Factor App 项目中获得的技巧。

保存完 .env 文件之后就可以运行:

?
1
source .env

在任何时候都可以运行该指令来将配置加载进环境变量。

项目基础工作

接着我们创建 main.go 文件做如下事情:

  1. PORTA_CONDITION_URLB_CONDITION_URLDEFAULT_CONDITION_URL 变量通过日志打印到控制台。
  2. / 路径上监听请求:
?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
package main
 
import (
 "bytes"
 "encoding/json"
 "io/ioutil"
 "log"
 "net/http"
 "net/http/httputil"
 "net/url"
 "os"
 "strings"
)
 
// Get env var or default
func getEnv(key, fallback string) string {
 if value, ok := os.LookupEnv(key); ok {
  return value
 }
 return fallback
}
 
// Get the port to listen on
func getListenAddress() string {
 port := getEnv("PORT", "1338")
 return ":" + port
}
 
// Log the env variables required for a reverse proxy
func logSetup() {
 a_condtion_url := os.Getenv("A_CONDITION_URL")
 b_condtion_url := os.Getenv("B_CONDITION_URL")
 default_condtion_url := os.Getenv("DEFAULT_CONDITION_URL")
 
 log.Printf("Server will run on: %s\n", getListenAddress())
 log.Printf("Redirecting to A url: %s\n", a_condtion_url)
 log.Printf("Redirecting to B url: %s\n", b_condtion_url)
 log.Printf("Redirecting to Default url: %s\n", default_condtion_url)
}
 
// Given a request send it to the appropriate url
func handleRequestAndRedirect(res http.ResponseWriter, req *http.Request) {
 // We will get to this...
}
 
func main() {
 // Log setup values
 logSetup()
 
 // start server
 http.HandleFunc("/", handleRequestAndRedirect)
 if err := http.ListenAndServe(getListenAddress(), nil); err != nil {
  panic(err)
 }
}

现在你就可以运行代码了。

解析请求体

有了项目的基本骨架之后,我们需要添加逻辑来处理解析请求的请求体部分。更新 handleRequestAndRedirect 函数来从请求体中解析出 proxy_condition 字段。

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
type requestPayloadStruct struct {
 ProxyCondition string `json:"proxy_condition"`
}
 
// Get a json decoder for a given requests body
func requestBodyDecoder(request *http.Request) *json.Decoder {
 // Read body to buffer
 body, err := ioutil.ReadAll(request.Body)
 if err != nil {
  log.Printf("Error reading body: %v", err)
  panic(err)
 }
 
 // Because go lang is a pain in the ass if you read the body then any susequent calls
 // are unable to read the body again....
 request.Body = ioutil.NopCloser(bytes.NewBuffer(body))
 
 return json.NewDecoder(ioutil.NopCloser(bytes.NewBuffer(body)))
}
 
// Parse the requests body
func parseRequestBody(request *http.Request) requestPayloadStruct {
 decoder := requestBodyDecoder(request)
 
 var requestPayload requestPayloadStruct
 err := decoder.Decode(&requestPayload)
 
 if err != nil {
  panic(err)
 }
 
 return requestPayload
}
 
// Given a request send it to the appropriate url
func handleRequestAndRedirect(res http.ResponseWriter, req *http.Request) {
 requestPayload := parseRequestBody(req)
  // ... more to come
}

通过 proxy_condition 判断将流量发往何处

现在我们从请求中取得了 proxy_condition 的值,可以根据它来判断我们要反向代理到何处。记住上文我们提到的三种情形:

  • 如果 proxy_condition 值为 A ,我们将流量发送到 A_CONDITION_URL
  • 如果 proxy_condition 值为 B ,我们将流量发送到 B_CONDITION_URL
  • 其他情况将流量发送到 DEFAULT_CONDITION_URL
?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
// Log the typeform payload and redirect url
func logRequestPayload(requestionPayload requestPayloadStruct, proxyUrl string) {
 log.Printf("proxy_condition: %s, proxy_url: %s\n", requestionPayload.ProxyCondition, proxyUrl)
}
 
// Get the url for a given proxy condition
func getProxyUrl(proxyConditionRaw string) string {
 proxyCondition := strings.ToUpper(proxyConditionRaw)
 
 a_condtion_url := os.Getenv("A_CONDITION_URL")
 b_condtion_url := os.Getenv("B_CONDITION_URL")
 default_condtion_url := os.Getenv("DEFAULT_CONDITION_URL")
 
 if proxyCondition == "A" {
  return a_condtion_url
 }
 
 if proxyCondition == "B" {
  return b_condtion_url
 }
 
 return default_condtion_url
}
 
// Given a request send it to the appropriate url
func handleRequestAndRedirect(res http.ResponseWriter, req *http.Request) {
 requestPayload := parseRequestBody(req)
 url := getProxyUrl(requestPayload.ProxyCondition)
 logRequestPayload(requestPayload, url)
 // more still to come...
}

反向代理到 URL

最终我们来到了实际的反向代理部分。在如此多的语言中要编写一个反向代理需要考虑很多东西,写大段的代码。或者至少引入一个复杂的外部库。

然而 Go 的标准库使得创建一个反向代理非常简单以至于你都不敢相信。下面就是你所需要的最关键的一行代码:

?
1
httputil.NewSingleHostReverseProxy(url).ServeHTTP(res, req)

注意下面代码中我们做了些许修改来让它能完整地支持 SSL 重定向(虽然不是必须的)。

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
// Serve a reverse proxy for a given url
func serveReverseProxy(target string, res http.ResponseWriter, req *http.Request) {
 // parse the url
 url, _ := url.Parse(target)
 
 // create the reverse proxy
 proxy := httputil.NewSingleHostReverseProxy(url)
 
 // Update the headers to allow for SSL redirection
 req.URL.Host = url.Host
 req.URL.Scheme = url.Scheme
 req.Header.Set("X-Forwarded-Host", req.Header.Get("Host"))
 req.Host = url.Host
 
 // Note that ServeHttp is non blocking and uses a go routine under the hood
 proxy.ServeHTTP(res, req)
}
 
// Given a request send it to the appropriate url
func handleRequestAndRedirect(res http.ResponseWriter, req *http.Request) {
 requestPayload := parseRequestBody(req)
 url := getProxyUrl(requestPayload.ProxyCondition)
 
 logRequestPayload(requestPayload, url)
 
 serveReverseProxy(url, res, req)
}

全部启动

好了,现在启动我们的反向代理程序让其监听 1330 端口。让其他的 3 个简单的服务分别监听 1331–1333 端口(在各自的终端中)。

  1. source .env && go install && $GOPATH/bin/reverse-proxy-demo
  2. http-server -p 1331
  3. http-server -p 1332
  4. http-server -p 1333

这些服务都启动之后,我们就可以在另一个终端中像下面这样开始发送带有 JSON 体的请求了:

?
1
2
3
4
5
6
curl --request GET \
 --url http://localhost:1330/ \
 --header 'content-type: application/json' \
 --data '{
 "proxy_condition": "a"
 }'

如果你在找一个好用的 HTTP 请求客户端,我极力推荐 Insomnia 。

然后我们就会看到我们的反向代理将流量转发给了我们根据 proxy_condition 字段配置的 3 台服务中的其中一台。

1行Go代码实现反向代理的示例

总结

Go 为此提供了很多,但真正支撑起它的在于这些低级的网络管道任务,没有更好的语言了。我们写的这个程序简单,高性能,可靠并且随时可用于生产环境。

我能看到在以后我会经常使用 Go 来编写简单的服务。

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持服务器之家。

‍ 代码是开源的,你可以在 Github 上找到。 :heart: 在 Twitter 上我只聊关于编程和远程工作相关的东西。如果关注我,你不会后悔的。

原文链接:https://studygolang.com/articles/14246

延伸 · 阅读

精彩推荐
  • Golang深入浅析Go中三个点(...)用法

    深入浅析Go中三个点(...)用法

    这篇文章主要介绍了深入浅析Go中三个点(...)用法,需要的朋友可以参考下...

    踏雪无痕SS6472021-11-17
  • GolangGo语言基础单元测试与性能测试示例详解

    Go语言基础单元测试与性能测试示例详解

    这篇文章主要为大家介绍了Go语言基础单元测试与性能测试示例详解,有需要的朋友可以借鉴参考下,希望能够有所帮助祝大家多多进步...

    枫少文7812021-12-05
  • GolangGO语言字符串处理Strings包的函数使用示例讲解

    GO语言字符串处理Strings包的函数使用示例讲解

    这篇文章主要为大家介绍了GO语言字符串处理Strings包的函数使用示例讲解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步早日升职加...

    Jeff的技术栈6882022-04-14
  • Golanggo语言获取系统盘符的方法

    go语言获取系统盘符的方法

    这篇文章主要介绍了go语言获取系统盘符的方法,涉及Go语言调用winapi获取系统硬件信息的技巧,具有一定参考借鉴价值,需要的朋友可以参考下 ...

    无尽海3862020-04-24
  • GolangGo语言range关键字循环时的坑

    Go语言range关键字循环时的坑

    今天小编就为大家分享一篇关于Go语言range关键字循环时的坑,小编觉得内容挺不错的,现在分享给大家,具有很好的参考价值,需要的朋友一起跟随小编来...

    benben_20154202020-05-23
  • GolangGolang 语言极简类型转换库cast的使用详解

    Golang 语言极简类型转换库cast的使用详解

    本文我们通过 cast.ToString() 函数的使用,简单介绍了cast 的使用方法,除此之外,它还支持很多其他类型,在这没有多多介绍,对Golang 类型转换库 cast相关知...

    Golang语言开发栈6112021-12-02
  • GolangGolang实现四种负载均衡的算法(随机,轮询等)

    Golang实现四种负载均衡的算法(随机,轮询等)

    本文介绍了示例介绍了Golang 负载均衡的四种实现,主要包括了随机,轮询,加权轮询负载,一致性hash,感兴趣的小伙伴们可以参考一下...

    Gundy_8442021-08-09
  • GolangGo语言实现自动填写古诗词实例代码

    Go语言实现自动填写古诗词实例代码

    这篇文章主要给大家介绍了关于Go语言实现自动填写古诗词的相关资料,这是最近在项目中遇到的一个需求,文中通过示例代码介绍的非常详细,需要的朋...

    FengY5862020-05-14