图片引用网络。
反向代理具体含义这里就不在进行讲述了,想看详细信息,可自行百度查看。
我们这里直接上代码进行描述。
package main
import (
_ "fmt"
"io"
_ "log"
"net/http"
"net/http/httputil"
"net/url"
_ "strings"
)
func main() {
/*localHost := "127.0.0.1:8080"
targetHost := "127.0.0.1:8802"
httpsServer(localHost, targetHost)
var err error = nil*/
http.HandleFunc("/", ServeHTTP)
// 注释代码也可以在main中直接写,当然封装到方法中显得更加清晰
/*
http.HandleFunc("/", func(w http.ResponseWriter, req *http.Request) {
if req.RequestURI == "/favicon.ico" {
io.WriteString(w, "Request path Error")
return
}
fmt.Println(req.RequestURI)
fmt.Fprintf(w, req.RequestURI)
})
*/
http.ListenAndServe(":8085", nil)
}
func ServeHTTP(w http.ResponseWriter, r *http.Request) {
if r.RequestURI == "/favicon.ico" {
io.WriteString(w, "Request path Error")
return
}
remote, err := url.Parse("http://" + "127.0.0.1:80")
if err != nil {
panic(err)
}
proxy := httputil.NewSingleHostReverseProxy(remote)
proxy.ServeHTTP(w, r)
}
上述就是一个简单的代理功能实现,主要是通过
httputil.NewSingleHostReverseProxy(targetUrl)
这个是httputil自带的方法,可以直接引用,但是如果我们想要做一个比较完整的反向代理功能那该怎么办呢?
需求:前端请求到后台,根据固定服务进行转发服务下的固定接口
分析:
如果已经知道是哪个具体的服务下的固定接口,那么我们可以比较简单的处理这种业务,比如我这里直接将这个接口完整请求路径作为代理转发后续path,那么只要前端请求的路径中包含这个固定path,当后端解析到有这个path,那么直接进入到这个代理转发功能上,代理功能根据请求地址后需要转发的服务域名(或者ip、端口)进行转发到这个服务上,然后根据path接口路径进行访问请求。这里需要注意:在进行访问服务下接口路径的时候,因为是转发到这个服务上,所以我们需要确定我们转发时将对应服务需要的鉴权认证信息携带过去(这里需要你明白具体服务是根据什么进行验权(token、cookie、或者直接用户信息在header中等等)),带上验证信息一起转发到服务中,这样,进行服务下接口访问的时候才能通过验权进行接口路径查询。
简单点说就是:比如这个path=/api/v1/user/ 这个路径就是user服务下的某个接口路径,我们统一路径,让前端给我们传递请求时候是:http://localhost:8080//api/v1/almsvr/ 这个路径到我们后台的时候,通过解析是否有/api/v1/almsvr/字段,如果有,就直接进入到代理转发功能,不再去走路由了,那么我们的代理转发功能直接实现具体业务就行了。
代码实现:
const (
OriginHostReqHeader = "X-Origin-Host"
ForwardedHostReqHeader = "X-Forwarded-Host" // 代理路径,记录请求到底在中间经历了哪些代理
)
func forwardHttpRequest(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
/* 1. 这种是转发到固定的服务上的固定接口,如果前端请求是前缀包含/api/v1/user/getAllUer,则认为是需要进行转发用户服务查询*/
if strings.HasPrefix(r.URL.Path, "/api/v1/user/getAllUer") {
/*
这个方法直接给路径定死了,只要前端访问到/api/v1/user/getAllUer路径,就认为到用户服务,同时访问接口/api/v1/user/getAllUer,所以这里直接将r.URL.Path直接传下去,作为//director下path的路径了
*/
forwardUser(w, r, r.URL.Path)
return
}
/* 2. 下面这个是可以转发到任意一个服务上上任意接口
可以考虑加一个固定path: serviceProxy/<serviceName>:<port>/<path>
比如:serviceProxy/ip:port/api/v1/ping
*/
if strings.HasPrefix(r.URL.Path, "/serviceProxy") {
path := r.URL.Path[len("serviceProxy/")+1:]
forwardServiceProxy(w, r, path)
return
}
next.ServeHTTP(w, r)
})
}
// 转发到固定服务的固定方法代码实现
func forwardUser(w http.ResponseWriter, r *http.Request, rawPath string) {
user:= config.Configuration.Clients["User"] // 自己在配置文件中的服务名
director := func(req *http.Request) { // 最终要的,转发定向处理
req.Header.Add(ForwardedHostReqHeader, req.Host)
req.Header.Add(OriginHostReqHeader, user.Host)
req.URL.Scheme = user.Protocol // 协议
req.URL.Host = fmt.Sprintf("%v:%v", user.Host, user.Port) // 具体转发ip和端口
req.URL.Path = rawPath // 转发的服务具体接口路径 /api/v1/almsvr
req.Method = r.Method // 接口请求方法类型get、post、put、delete
}
errHandler := func(w http.ResponseWriter, r *http.Request, err error) { // err处理
if err != nil {
fmt.Sprint("failed to forward request, path =", rawPath))
encodeErrorResponse(context.Background(), e, w) // 自己做一个错误返回处理
}
}
resHandler := func(response *http.Response) error { // 返回结果处理
log.Debug("user http forward res: ", response)
return nil
}
proxy := &httputil.ReverseProxy{Director: director, ModifyResponse: resHandler, ErrorHandler: errHandler} // go自带反向代理方法
proxy.Transport = &http.Transport{ResponseHeaderTimeout: time.Duration(user.Timeout) * time.Millisecond}
proxy.ServeHTTP(w, r) // http服务请求
}
上述代码中有一些我自己做的错误处理、配置文件读取等方法,你可以自己进行编写。
下面是任意服务下的代理转发
func forwardServiceProxy(w http.ResponseWriter, r *http.Request, rawPath string) {
// rawPath= localhost:port/api/v1/ping
index := strings.Index(rawPath, "/")
if index == -1 {
encodeErrorResponse(context.Background(), errors.ErrInternalServer, w)
return
}
// localhost:port
var host string
var port int64
var err error
serviceAddr := rawPath[:index]
split := strings.Split(serviceAddr, ":")
if len(split) > 0 {
host = split[0]
port, err = strconv.ParseInt(split[1], 10, 64)
// TODO 要加个返回具体错误信息方法 errors.ErrInternalServer
if err != nil {
encodeErrorResponse(context.Background(), errors.ErrInternalServer, w)
return
}
}
// api/v1/ping
apiUrl := rawPath[index:]
director := func(req *http.Request) {
req.Header.Add(ForwardedHostReqHeader, req.Host)
req.Header.Add(OriginHostReqHeader, host)
req.URL.Scheme = "http" // 默认为http协议
req.URL.Host = fmt.Sprintf("%v:%v", host, port) // 具体服务的ip和端口 localhost:8085
req.URL.Path = apiUrl // api/v1/ping
req.Method = r.Method // get、post、delete、put
}
errHandler := func(w http.ResponseWriter, r *http.Request, err error) {
if err != nil {
e := errors.NewErrServerError(err, fmt.Sprint("failed to forward request, path =", rawPath)) // 自定义错误返回方法NewErrServerError
encodeErrorResponse(context.Background(), e, w)
}
}
resHandler := func(response *http.Response) error {
log.Debug("serviceProxy http forward res: ", response)
return nil
}
// 验证权限信息,加入到header中,带到转发服务下,进行校验,你应该按照你都项目中需要的方式进行信息验证
/*
添加自己转发服务需要的用户信息,放到上下文中进行下穿
getUserInfo()
...
*/
// go自带的反向代理方法,主要字段Director,这里面包含了代理需要的协议、服务ip端口、路径、请求方法
proxy := &httputil.ReverseProxy{Director: director, ModifyResponse: resHandler, ErrorHandler: errHandler}
proxy.Transport = &http.Transport{ResponseHeaderTimeout: 5000 * time.Millisecond} // 这个5000按照自己需求设定
proxy.ServeHTTP(w, r)
}
以上就是具体的两种不同业务场景下的请求方式,相对还是比较完善的,大家有不懂的或者有什么改进、更好的办法的可以随时留言,希望能够同时进步。