

基于Golang和WebSocket打造自已的反向代理
source link: https://studygolang.com/articles/35835
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

基于Golang和WebSocket打造自已的反向代理
onyas · 5天之前 · 696 次点击 · 预计阅读时间 5 分钟 · 大约8小时之前 开始浏览当我们在开发的时候,有时想要把自已开发的接口暴露给其他开发者或者第三方的服务,方便我们调试和排查问题,那就需要某种机制把我们本地的服务接口暴露到互联网上,本文将要介绍如何通过Golang和WebSocket来实现这一功能
为什么我们需要开发自已的代理服务
目前已经有许多可用的代理服务了,比如ngrok和localtunnel,但ngrok有个缺点就是提供的域名只能用几个小时,然后需要新生成新的域名,如果想要固定域名就要花钱,但我们自已实现的代理可以用一个固定的域名,如果给前端同学来调试的话,不用改来改去,很方便。
ggrok简介

ggrok是通过Golang和WebSocket实现的代理应用,你可以通过Github仓库上的Heroku按钮非常方便的部署,然后就可以拥有一个固定的域名了。
Step1 在服务器和客户端建立WebSocket连接
服务端基于gorilla,监听WebSocket连接
func (s *Server) Register(w http.ResponseWriter, r *http.Request) {
c, err := upgrader.Upgrade(w, r, nil)
if err != nil {
log.Print("upgrade:", err)
return
}
gconn := &Connection{
Socket: c,
mu: sync.Mutex{},
}
connections[r.Host] = gconn
log.Println("current connections: ", connections)
}
http.HandleFunc("/$$ggrok", s.Register)
客户端连接服务端
func (ggclient *GGrokClient) Proxy() {
interrupt := make(chan os.Signal, 1)
signal.Notify(interrupt, os.Interrupt)
u := url.URL{Scheme: "ws", Host: ggclient.RemoteServer, Path: "/$$ggrok"}
log.Printf("connecting to %s", u.String())
c, _, err := websocket.DefaultDialer.Dial(u.String(), nil)
if err != nil {
log.Fatal("dial:", err)
}
defer c.Close()
done := make(chan struct{})
for {
select {
case <-done:
return
}
}
}
Step2 服务端收到http请求以后转成WebSocket消息转发给客户端
http.HandleFunc("/", s.Proxy)
func (s *Server) Proxy(w http.ResponseWriter, r *http.Request) {
remoteConn := connections[r.Host]
if remoteConn == nil || remoteConn.Socket == nil {
io.WriteString(w, "client not register")
return
}
wsRequest := httpRequestToWebSocketRequest(r)
wsRes := triggerWS(remoteConn, wsRequest)
}
func triggerWS(remoteConn *Connection, reqRemote WebSocketRequest) WebSocketResponse {
remoteConn.mu.Lock()
defer remoteConn.mu.Unlock()
remoteConn.Socket.WriteJSON(reqRemote)
var wsRes WebSocketResponse
err := remoteConn.Socket.ReadJSON(&wsRes)
if err != nil {
log.Println("read remote client response error", err)
}
log.Println("remote client response: ", wsRes)
return wsRes
}
func httpRequestToWebSocketRequest(r *http.Request) (ws WebSocketRequest) {
reqStr, err := captureRequestData(r)
if err != nil {
log.Println("captureRequestData error:", err)
}
log.Println("req serialized: ", reqStr)
reqRemote := WebSocketRequest{Req: reqStr, URL: r.URL.String()}
return reqRemote
}
Step3 客户端收到WebSocket消息以后转发到LocalServer,并把LocalServer的响应返回给服务端
go func() {
defer close(done)
for {
websocketReq := readWebSocketReq(c)
localRequest := socketToLocalRequest(websocketReq, ggclient.ProxyLocalPort)
resp, err := (&http.Client{}).Do(localRequest)
if err != nil {
log.Println("local http request error:", err)
continue
}
wsRes := localResponseToWebSocketResponse(resp)
// log.Printf("client send response: %s \n", wsRes.Body)
c.WriteJSON(wsRes)
}
}()
func socketToLocalRequest(websocketReq WebSocketRequest, port int) *http.Request {
r := bufio.NewReader(bytes.NewReader([]byte(websocketReq.Req)))
localRequest, err := http.ReadRequest(r)
if err != nil {
log.Println("deserialize request error", err)
return localRequest
}
localRequest.RequestURI = ""
u, err := url.Parse(websocketReq.URL)
if err != nil {
log.Println("parse url error", err)
}
localRequest.URL = u
localRequest.URL.Scheme = "http"
localRequest.URL.Host = "localhost:" + strconv.Itoa(port)
return localRequest
}
func localResponseToWebSocketResponse(resp *http.Response) WebSocketResponse {
body, err := io.ReadAll(resp.Body)
if err != nil {
log.Println("read local response error ", err)
}
resp.Body.Close()
wsRes := WebSocketResponse{Status: resp.Status, StatusCode: resp.StatusCode,
Proto: resp.Proto, Header: resp.Header, Body: body, ContentType: resp.Header.Get("Content-Type")}
return wsRes
}
Step4 服务端收到响应以后返回给前端
func wsResToHttpResponse(w http.ResponseWriter, wsRes WebSocketResponse) {
copyHeader(w, wsRes)
io.Copy(w, bytes.NewReader(wsRes.Body))
}
至此,通过ggrok我们实现了本地服务的代理,并发布到互联网上。以上是一些主要的代码,详细的可以看github上面的代码,有问题请提issue或者pr,共同打造更健壮的开源系统。
有疑问加站长微信联系(非本文作者))

入群交流(和以上内容无关):加入Go大咖交流群,或添加微信:liuxiaoyan-s 备注:入群;或加QQ群:692541889
Recommend
-
114
Nginx反向代理WebSocket响应403的解决办法 在Nginx反向代理一个带有WebSocket功能的Spring Web程序(
-
66
新浪科技讯北京时间1月2日消息,据国外媒体报道,目前,美国宇航局一位离职研究员称,他希望通过“帮助人类改造自己的基因”,从而创造出一种“超级人类”。约西亚·赞内尔(JosiahZayner)博士今年36岁,近期他成为美国各大主流媒体的焦点人
-
46
-
33
全球工单系统 - @doubleflower - 点赞会排前面,发现杠精都给自已点赞美糍糍这是个 BUG 吧?
-
29
nginx反向代理下,golang程序获取用户真实IP 在生产环境中我主要使用了beego和gin,下面只介绍这两个框架的情况。 Nginx的配置: location /api { proxy_set_header Host $http_host; pr...
-
53
业绩能在3-5年内超过当当吗?是否会被二次踢出管理层?
-
42
前言 今天写点有趣的小东西。事情是这样的,我经常看到有些软件加了统计数据的sdk,之后就可以实现统计接口的访问量,接口负载等等数据。而这些功能不需要原有的软件做些什么,对原来的业务完全无入侵,我觉得这样的功能很有实...
-
26
1 基于reverse proxy实现的反向代理例子 package main import ( "log" "net/http" "net/http/httputil" "net/url" ) func main() { // 地址重写实例 // http://127.0.0.1:8888/test?id=1 =》 htt...
-
6
nginx反向代理websocket 发表于 2...
-
9
.NET + SignalR 的反向代理 websocket/http 数据隧道
About Joyk
Aggregate valuable and interesting links.
Joyk means Joy of geeK