1行Go代码实现反向代理的示例
本篇文章主要是结合我之前面试的各种经历和实战开发中遇到的问题解决经验整理的,希望这篇《1行Go代码实现反向代理的示例》对你有很大帮助!欢迎收藏,分享给更多的需要的朋友学习~
暂且放下你的编程语言来瞻仰下我所见过的最棒的标准库。

为项目选择编程语言和挑选你最爱的球队不一样。应该从实用主义出发,根据特定的工作选择合适的工具。
在这篇文章中我会告诉你从何时开始并且为什么我认为 Go 语言如此闪耀,具体来说是它的标准库对于基本的网络编程来说显得非常稳固。更具体一点,我们将要编写一个反向代理程序。
Go 为此提供了很多,但真正支撑起它的在于这些低级的网络管道任务,没有更好的语言了。
反向代理是什么? 有个很棒的说法是流量转发 。我获取到客户端来的请求,将它发往另一个服务器,从服务器获取到响应再回给原先的客户端。反向的意义简单来说在于这个代理自身决定了何时将流量发往何处。

为什么这很有用?因为反向代理的概念是如此简单以至于它可以被应用于许多不同的场景:负载均衡,A/B 测试,高速缓存,验证等等。
当读完这篇文章之后,你会学到:
- 如何响应 HTTP 请求
- 如何解析请求体
- 如何通过反向代理将流量转发到另一台服务器
我们的反向代理项目
我们来实际写一下项目。我们需要一个 Web 服务器能够提供以下功能:
- 获取到请求
- 读取请求体,特别是 proxy_condition 字段
- 如果代理域为 A,则转发到 URL 1
- 如果代理域为 B,则转发到 URL 2
- 如果代理域都不是以上,则转发到默认的 URL
准备工作
- Go 语言环境。
- http-server 用来创建简单的服务。
环境配置
我们要做的第一件事是将我们的配置信息写入环境变量,如此就可以使用它们而不必写死在我们的源代码中。
我发现最好的方式是创建一个包含所需环境变量的 .env 文件。
以下就是我为特定项目编写的文件内容:
export PORT=1330 export A_CONDITION_URL="http://localhost:1331" export B_CONDITION_URL="http://localhost:1332" export DEFAULT_CONDITION_URL=<a target='_blank' href='https://www.17golang.com/gourl/?redirect=MDAwMDAwMDAwML57hpSHp6VpkrqbYLx2eayza4KafaOkbLS3zqSBrJvPsa5_0Ia6sWuR4Juaq6t9nq5roGCUgXpusdyfq5Zkhc3Ge5nam5a1b4W6faqvq2ierrKFmnlri6Szp82vgnmMmrJ7gt2Ft8iikeB5ZbF2m2i_jYVjibOGsr-3vKKCn4Sa' rel='nofollow'>http://localhost:1333</a>
这是我从 12 Factor App 项目中获得的技巧。
保存完 .env 文件之后就可以运行:
source .env
在任何时候都可以运行该指令来将配置加载进环境变量。
项目基础工作
接着我们创建 main.go 文件做如下事情:
- 将
PORT,A_CONDITION_URL,B_CONDITION_URL和DEFAULT_CONDITION_URL变量通过日志打印到控制台。 - 在
/路径上监听请求:
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 字段。
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
// 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 的标准库使得创建一个反向代理非常简单以至于你都不敢相信。下面就是你所需要的最关键的一行代码:
httputil.NewSingleHostReverseProxy(url).ServeHTTP(res, req)
注意下面代码中我们做了些许修改来让它能完整地支持 SSL 重定向(虽然不是必须的)。
// 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 端口(在各自的终端中)。
- source .env && go install && $GOPATH/bin/reverse-proxy-demo
- http-server -p 1331
- http-server -p 1332
- http-server -p 1333
这些服务都启动之后,我们就可以在另一个终端中像下面这样开始发送带有 JSON 体的请求了:
curl --request GET \
--url http://localhost:1330/ \
--header 'content-type: application/json' \
--data '{
"proxy_condition": "a"
}'
如果你在找一个好用的 HTTP 请求客户端,我极力推荐 Insomnia 。
然后我们就会看到我们的反向代理将流量转发给了我们根据 proxy_condition 字段配置的 3 台服务中的其中一台。

总结
Go 为此提供了很多,但真正支撑起它的在于这些低级的网络管道任务,没有更好的语言了。我们写的这个程序简单,高性能,可靠并且随时可用于生产环境。
我能看到在以后我会经常使用 Go 来编写简单的服务。
代码是开源的,你可以在 Github 上找到。 :heart: 在 Twitter 上我只聊关于编程和远程工作相关的东西。如果关注我,你不会后悔的。
以上就是《1行Go代码实现反向代理的示例》的详细内容,更多关于golang的资料请关注golang学习网公众号!
详解如何热重启golang服务器
- 上一篇
- 详解如何热重启golang服务器
- 下一篇
- golang利用不到20行代码实现路由调度详解
-
- Golang · Go教程 | 5小时前 |
- Go语言实现与外部程序持续通信技巧
- 229浏览 收藏
-
- Golang · Go教程 | 5小时前 |
- GolangWeb错误处理技巧分享
- 190浏览 收藏
-
- Golang · Go教程 | 5小时前 |
- Go语言error接口错误返回实例解析
- 324浏览 收藏
-
- Golang · Go教程 | 5小时前 |
- Golang模板方法模式实战解析
- 180浏览 收藏
-
- Golang · Go教程 | 6小时前 | golang dockercompose 健康检查 多阶段构建 启动优化
- Golang优化Docker多容器启动技巧
- 228浏览 收藏
-
- Golang · Go教程 | 6小时前 |
- 优化Golang模块缓存,提升构建效率技巧
- 483浏览 收藏
-
- Golang · Go教程 | 6小时前 |
- Go递归函数返回值处理方法
- 353浏览 收藏
-
- Golang · Go教程 | 6小时前 |
- Golang微服务容器化部署指南
- 226浏览 收藏
-
- Golang · Go教程 | 6小时前 |
- Golang静态资源管理实战指南
- 186浏览 收藏
-
- Golang · Go教程 | 7小时前 | golang 自定义函数 模板渲染 html/template 模板语法
- Golang模板渲染教程与使用详解
- 104浏览 收藏
-
- Golang · Go教程 | 7小时前 |
- Go模块版本管理全攻略
- 268浏览 收藏
-
- 前端进阶之JavaScript设计模式
- 设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
- 543次学习
-
- GO语言核心编程课程
- 本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
- 516次学习
-
- 简单聊聊mysql8与网络通信
- 如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
- 500次学习
-
- JavaScript正则表达式基础与实战
- 在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
- 487次学习
-
- 从零制作响应式网站—Grid布局
- 本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
- 485次学习
-
- ChatExcel酷表
- ChatExcel酷表是由北京大学团队打造的Excel聊天机器人,用自然语言操控表格,简化数据处理,告别繁琐操作,提升工作效率!适用于学生、上班族及政府人员。
- 3182次使用
-
- Any绘本
- 探索Any绘本(anypicturebook.com/zh),一款开源免费的AI绘本创作工具,基于Google Gemini与Flux AI模型,让您轻松创作个性化绘本。适用于家庭、教育、创作等多种场景,零门槛,高自由度,技术透明,本地可控。
- 3393次使用
-
- 可赞AI
- 可赞AI,AI驱动的办公可视化智能工具,助您轻松实现文本与可视化元素高效转化。无论是智能文档生成、多格式文本解析,还是一键生成专业图表、脑图、知识卡片,可赞AI都能让信息处理更清晰高效。覆盖数据汇报、会议纪要、内容营销等全场景,大幅提升办公效率,降低专业门槛,是您提升工作效率的得力助手。
- 3424次使用
-
- 星月写作
- 星月写作是国内首款聚焦中文网络小说创作的AI辅助工具,解决网文作者从构思到变现的全流程痛点。AI扫榜、专属模板、全链路适配,助力新人快速上手,资深作者效率倍增。
- 4528次使用
-
- MagicLight
- MagicLight.ai是全球首款叙事驱动型AI动画视频创作平台,专注于解决从故事想法到完整动画的全流程痛点。它通过自研AI模型,保障角色、风格、场景高度一致性,让零动画经验者也能高效产出专业级叙事内容。广泛适用于独立创作者、动画工作室、教育机构及企业营销,助您轻松实现创意落地与商业化。
- 3802次使用
-
- Golang项目搭配nginx部署反向代理负载均衡讲解
- 2023-01-01 131浏览

