一. ReverseProxy 基础
- 在前面我们基于原生HTTP实现正反向代理,可能存在以下问题
- 没有错误回调及错误日志等处理
- 无法更改代理后返回的内容
- 没有负载均衡
- 没有url重写
- 没有熔断限流,降级,数据统计等功能
- 解决以上问题,golang提供了ReverseProxy,在"net/http/httputil/"包下
ReverseProxy 中提供了哪些功能
- 提供了4种负载均衡的实现及接口封装,并且支持自定义负载均衡
随机
轮询
权重轮询
hash
- 通过中间件提供了: 限流, 熔断,降级, 权限,数据统计等功能
- 允许更改启动内容
- 可以设置错误信息回调
- 支持url重写
- 支持连接池功能
- 支持webSocket
- 支持https代理
ReverseProxy 结构详解
type ReverseProxy struct {
//控制器必须是一个函数,通过该函数内部可以对请求进行修改,比如请求的路径,请求的参数
Director func(*http.Request)
//连接池,如果为nil,则使用http.DefaultTransport
Transport http.RoundTripper
//刷新到客户端的刷新间隔
FlushInterval time.Duration
//错误记录器
ErrorLog *log.Logger
//定义一个缓冲池,在复制http响应的时候使用,用以提高请求效率
BufferPool BufferPool
//修改response返回内容的函数
//将函数格式定义为以下格式,就能对返回内容进行修改
ModifyResponse func(*http.Response) error
//错误回调函数,如果为nil,则默认为记录提供的错误并返回502状态错误网关响应,
//当发生异常时(包括整个流程上某一部分发生异常)可以通过该函数进行处理
ErrorHandler func(http.ResponseWriter, *http.Request, error)
}
ReverseProxy实现代理的简单示例
- 对下方示例进行解释:
- ReverseProxy在"net/http/httputil/"包下
- 通过httputil下的NewSingleHostReverseProxy()方法可以直接创建一个ReverseProxy
- ReverseProxy实现了Handler接口,所以可以直接当成路由处理器来使用
- 下方代码中,访问当前服务8080端口时,会通过ReverseProxy代理到rs1 指向的地址
import (
"log"
"net/http"
"net/http/httputil"
"net/url"
)
func main() {
//1.真实需要访问的地址
rs1 := "http://127.0.0.1:9999/base"
//通过url.Parse()解析地址
url1, err1 := url.Parse(rs1)
if err1 != nil {
log.Println(err1)
}
//2.获取到ReverseProxy
proxy := httputil.NewSingleHostReverseProxy(url1)
//3.ReverseProxy实现了Handler,可以直接当成处理器路由来使用
//通过ReverseProxy实现http代理,当访问当前服务8080端口时,
//会被ReverseProxy代理到rs1
log.Fatal(http.ListenAndServe(":8080", proxy))
}
- 上方示例的注意点: 假设访问当前服务"127.0.0.1:8080/xxx"在经过ReverseProxy代理后,实际会访问到"127.0.0.1:2003/base/xxx", 内部提供了一定的重写规则
1. NewSingleHostReverseProxy()函数源码解释
- 用来创建ReverseProxy的NewSingleHostReverseProxy()函数源码解释
//target url.URL:代理的目标服务,假设为"http://127.0.0.1:2002/base?name=123"
func NewSingleHostReverseProxy(target *url.URL) *ReverseProxy {
//1.获取路径参数,根据上面假设的路径,当前targetQuery 就是"name=123"
targetQuery := target.RawQuery
//2.创建ReverseProxy需要的Director方法
//Director:用来改写请求路径,请求参数的函数
director := func(req *http.Request) {
//2.1设置协议Scheme: http
req.URL.Scheme = target.Scheme
//2.2设置主机Host: 127.0.0.1:2002
req.URL.Host = target.Host
//2.3设置path
//设置规则:比如当前服务到此处的路径为"http://ip:端口号/dir"
//上面要代理到target指向的path为"/base"
//拼接后位"/base/dir" 也就是target.path后要拼接当前服务的path
//joinURLPath()方法中会有一些合并校验等逻辑
req.URL.Path, req.URL.RawPath = joinURLPath(target, req.URL)
//2.4 url参数的设置
if targetQuery == "" || req.URL.RawQuery == "" {
req.URL.RawQuery = targetQuery + req.URL.RawQuery
} else {
req.URL.RawQuery = targetQuery + "&" + req.URL.RawQuery
}
//2.4设置请求头
if _, ok := req.Header["User-Agent"]; !ok {
// explicitly disable User-Agent so it's not set to default value
req.Header.Set("User-Agent", "")
}
}
//3.创建ReverseProxy设置Director并返回
return &ReverseProxy{Director: director}
}
2. 模拟NewSingleHostReverseProxy()自定义创建ReverseProxy
- 在NewSingleHostReverseProxy源码中我们发现主要就是创建了用来改写请求路径,请求参数的Director(), 我们可以模拟NewSingleHostReverseProxy()实现一个自己的ReverseProxy创建方法,
- 并且在创建ReverseProxy也可以同时将用来修改response返回内容的ModifyResponse 函数与异常回调函数errorHandler定义出来
- 模拟NewSingleHostReverseProxy实现自定义创建ReverseProxy
import (
"bytes"
"errors"
"fmt"
"io/ioutil"
"log"
"net/http"
"net/http/httputil"
"net/url"
"regexp"
"strings"
)
//1.模拟NewSingleHostReverseProxy创建ReverseProxy
//target: 目标服务
func NewSingleHostReverseProxy(target *url.URL) *httputil.ReverseProxy {
//1.获取path上的请求参数
targetQuery := target.RawQuery
//2.封装用来修改请求路径,请求参数的Director函数
director := func(req *http.Request) {
//2.1请求参数
re, _ := regexp.Compile("^/dir(.*)");
req.URL.Path = re.ReplaceAllString(req.URL.Path, "$1")
//2.2设置协议
req.URL.Scheme = target.Scheme
//2.3设置主机地址
req.URL.Host = target.Host
//2.4 设置path
req.URL.Path = singleJoiningSlash(target.Path, req.URL.Path)
//2.5 设置path参数
if targetQuery == "" || req.URL.RawQuery == "" {
req.URL.RawQuery = targetQuery + req.URL.RawQuery
} else {
req.URL.RawQuery = targetQuery + "&" + req.URL.RawQuery
}
//2.5设置请求头
if _, ok := req.Header["User-Agent"]; !ok {
req.Header.Set("User-Agent", "")
}
//读取body
body, err := ioutil.ReadAll(req.Body)
if err != nil {
log.Println("Failed to read request body:", err)
return
}
fmt.Println(string(body))
defer req.Body.Close()
data := map[string]int{
"apple": 2,
"banana": 3,
"cherry": 4,
}
jsonBytes, err := json.Marshal(data)
if err != nil {
fmt.Println("Failed to serialize map to JSON:", err)
return
}
//req.Body = ioutil.NopCloser(bytes.NewReader(body))
req.Body = ioutil.NopCloser(bytes.NewReader(jsonBytes))
//注意如果修改Body内容,要同步修改req.ContentLength长度,否则会报错
req.ContentLength = int64(len(jsonBytes))
//添加请求头
req.Header.Set("token", "ssss")
}
//3.封装可用用来改写响应的modifyFunc 函数
modifyFunc := func(res *http.Response) error {
if res.StatusCode != 200 {
//3.1此处判断如果响应的http状态码为异常时,封装异常返回
return errors.New("error statusCode")
}
//3.2读取下游服务响应的body
oldPayload, err := ioutil.ReadAll(res.Body)
if err != nil {
return err
}
//3.3封装新的响应
newPayLoad := []byte("hello " + string(oldPayload))
//3.4将数据再次填充到resp中(ioutil.NopCloser()该函数直接将byte数据转换为Body中的read)
res.Body = ioutil.NopCloser(bytes.NewBuffer(newPayLoad))
//3.5重置响应数据长度
res.ContentLength = int64(len(newPayLoad))
res.Header.Set("Content-Length", fmt.Sprint(len(newPayLoad)))
return nil
}
//4.设置异常回调,在上面几个步骤如果发送异常,返回的err不为nin,
//会执行该函数,执行指定业务逻辑
errorHandler := func(res http.ResponseWriter, req *http.Request, err error) {
res.Write([]byte(err.Error()))
}
//5.创建ReverseProxy返回
return &httputil.ReverseProxy{Director: director, ModifyResponse: modifyFunc, ErrorHandler: errorHandler}
}
//2.启动服务
func main() {
//2.1代理的目标服务地址,转换为url类型
rs1 := "http://127.0.0.1:2003/base"
url1, err1 := url.Parse(rs1)
if err1 != nil {
log.Println(err1)
}
//2.2通过自定义的NewSingleHostReverseProxy创建ReverseProxy
proxy := NewSingleHostReverseProxy(url1)
//2.3ReverseProxy实现了ServeHTTP()可以作为Handle使用
log.Fatal(http.ListenAndServe(":8080", proxy))
}
//复制的源码中的
func singleJoiningSlash(a, b string) string {
aslash := strings.HasSuffix(a, "/")
bslash := strings.HasPrefix(b, "/")
switch {
case aslash && bslash:
return a + b[1:]
case !aslash && !bslash:
return a + "/" + b
}
return a + b
}
3. 去除标准逐段传输头
- 在代理时,代理服务器向下发起代理调用时,有几个请求头(如协议升级等)不需要向下转发,此时就需要去除这几个请求头
4. ReverseProxy中一些特殊的StatusCode状态
- 100: 表示正常,客户端可以继续请求
- 100-continue状态:
- 客户端发送post请求数据大于1024字节时,不会直接发送请求,而是分两步
- 先发送询问请求,包含一个"Expect:100-continue",获取到Server端是否愿意接受数据
- 接受到Server端返回的100-continue应答后,返回100状态,才会把数据post给Server
- 101: 服务端发送给客户端升级协议的请求
5. 特殊Header头解释
- “X-Forwarded-For”: 反向代理时,经过的每一个反向代理服务器的ip列表(可被伪造)
- “X-Real-IP”: 实际请求的ip(不会被伪造)
- “Connection”:
- “TE”: 希望的传输类型
- “Trailer”: 响应头
二. ReverseProxy 源码相关
1. 了解ReverseProxy 下实现的 ServeHTTP()
func (p *ReverseProxy) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
//验证结构体里面有没有设置过ReverseProxy的连接池,没有则使用默认连接池
transport := p.Transport
if transport == nil {
transport = http.DefaultTransport
}
//1、验证是否请求终止
//上下文取得信息,向下转型为CloseNotifier
//(http.CloseNotifier是一个接口,只有一个方法CloseNotify() <-chan bool,作用是检测连接是否断开)
//取出里面通知的一个channel,即cn.CloseNotify(),紧接着开启一个协程,一直监听这个channel是否有请求终止的消息,如果有,便执行cancel()方法
ctx := req.Context()
if cn, ok := rw.(http.CloseNotifier); ok {
var cancel context.CancelFunc
ctx, cancel = context.WithCancel(ctx)
defer cancel()
notifyChan := cn.CloseNotify()
go func() {
select {
case <-notifyChan:
cancel()
case <-ctx.Done():
}
}()
}
//2、设置context信息
//通过上游发送过来的req,重新拷贝新建一个outreq对外请求的request,可以理解为往下文请求的一个request
outreq := req.Clone(ctx)
//对outreq的信息做特殊处理
if req.ContentLength == 0 {
outreq.Body = nil // Issue 16036: nil Body for http.Transport retries
}
if outreq.Body != nil {
defer outreq.Body.Close()
}
//3、深拷贝Header
if outreq.Header == nil {
outreq.Header = make(http.Header) // Issue 33142: historical behavior was to always allocate
}
//4、修改request,也就是之前控制器Director那里,地址和请求信息的修改拼接
p.Director(outreq)
//outreq.Close = false的意思是表示outreq请求到下游的链接是可以被复用的
outreq.Close = false
//5、Upgrade头的特殊处理
//upgradeType(outreq.Header)取出upgrade的类型并判断是否存在
reqUpType := upgradeType(outreq.Header)
if !ascii.IsPrint(reqUpType) {
p.getErrorHandler()(rw, req, fmt.Errorf("client tried to switch to invalid protocol %q", reqUpType))
return
}
//删除connection的head头信息
removeConnectionHeaders(outreq.Header)
//逐段消息头:客户端和第一代理之间的消息头,与是否往下传递head消息头是没有关联的,往下传递的信息中不应该包含这些逐段消息头
//删除后端的逐段消息头
for _, h := range hopHeaders {
outreq.Header.Del(h)
}
//这两个特殊消息头跳过,不进行删除
if httpguts.HeaderValuesContainsToken(req.Header["Te"], "trailers") {
outreq.Header.Set("Te", "trailers")
}
if reqUpType != "" {
outreq.Header.Set("Connection", "Upgrade")
outreq.Header.Set("Upgrade", reqUpType)
}
//6、X-Forwarded-For追加ClientIP信息
//设置 X-Forwarded-For,以逗号+空格分隔
if clientIP, _, err := net.SplitHostPort(req.RemoteAddr); err == nil {
prior, ok := outreq.Header["X-Forwarded-For"]
omit := ok && prior == nil // Issue 38079: nil now means don't populate the header
if len(prior) > 0 {
clientIP = strings.Join(prior, ", ") + ", " + clientIP
}
if !omit {
outreq.Header.Set("X-Forwarded-For", clientIP)
}
}
//7、向下游请求数据,拿到响应response
res, err := transport.RoundTrip(outreq)
if err != nil {
p.getErrorHandler()(rw, outreq, err)
return
}
//8、处理升级协议请求
//验证响应状态码是否为101,是才考虑升级
// Deal with 101 Switching Protocols responses: (WebSocket, h2c, etc)
if res.StatusCode == http.StatusSwitchingProtocols {
if !p.modifyResponse(rw, res, outreq) {
return
}
//请求升级方法(具体源码步骤见补充)
p.handleUpgradeResponse(rw, outreq, res)
return
}
//9、移除逐段消息头,删除从下游返回的无用的数据
removeConnectionHeaders(res.Header)
for _, h := range hopHeaders {
res.Header.Del(h)
}
//10、修改response返回内容
if !p.modifyResponse(rw, res, outreq) {
return
}
//11、拷贝头部数据
copyHeader(rw.Header(), res.Header)
//处理Trailer头部
announcedTrailers := len(res.Trailer)
if announcedTrailers > 0 {
trailerKeys := make([]string, 0, len(res.Trailer))
for k := range res.Trailer {
trailerKeys = append(trailerKeys, k)
}
rw.Header().Add("Trailer", strings.Join(trailerKeys, ", "))
}
//12、写入状态码
rw.WriteHeader(res.StatusCode)
//13、按周期刷新内容到response
err = p.copyResponse(rw, res.Body, p.flushInterval(res))
if err != nil {
defer res.Body.Close()
if !shouldPanicOnCopyError(req) {
p.logf("suppressing panic for copyResponse error in test; copy error: %v", err)
return
}
panic(http.ErrAbortHandler)
}
//读取完body内容后,对body进行关闭
res.Body.Close()
//对Trailer逻辑处理
if len(res.Trailer) > 0 {
if fl, ok := rw.(http.Flusher); ok {
fl.Flush()
}
}
if len(res.Trailer) == announcedTrailers {
copyHeader(rw.Header(), res.Trailer)
return
}
for k, vv := range res.Trailer {
k = http.TrailerPrefix + k
for _, v := range vv {
rw.Header().Add(k, v)
}
}
}
- 通过ReverseProxy 实现代理的中断就是ServeHTTP()方法,在该方法中重点实现了一下逻辑
- 验证是否请求终止: 若请求终止,我们就不会把这个服务请求下游,例如关闭浏览器、网络断开等等,那么就会终止请求
- 设置请求context信息,如果上游传了部分context信息,那么我就会将这一部分的context信息做设置
- 深拷贝header
- 修改req: 这里的修改request信息就包含了请求到下游的特殊的head头信息的变更,比如X-Forwarded-For,X-Real-IP
- Upgrade头的特殊处理
- 追加ClientIP信息: 这里就是X-Forwarded-For,X-Real-IP这一块的设置
- 向下游请求数据: transport、roundtrip?方法
- 处理升级协议请求
- 移除逐段头部
- 修改返回数据
- 拷贝头部的数据
- 写入状态码
- 周期刷新内容到response
- 请求升级handleUpgradeResponse
func (p *ReverseProxy) handleUpgradeResponse(rw http.ResponseWriter, req *http.Request, res *http.Response) {
//比对上游和下游的协议,判断是否都升级成功
reqUpType := upgradeType(req.Header)
resUpType := upgradeType(res.Header)
if !ascii.IsPrint(resUpType) {
p.getErrorHandler()(rw, req, fmt.Errorf("backend tried to switch to invalid protocol %q", resUpType))
}
if !ascii.EqualFold(reqUpType, resUpType) {
p.getErrorHandler()(rw, req, fmt.Errorf("backend tried to switch protocol %q when %q was requested", resUpType, reqUpType))
return
}
//劫持当前http,通过向下转型的方式获得connection
hj, ok := rw.(http.Hijacker)
if !ok {
p.getErrorHandler()(rw, req, fmt.Errorf("can't switch protocols using non-Hijacker ResponseWriter type %T", rw))
return
}
backConn, ok := res.Body.(io.ReadWriteCloser)
if !ok {
p.getErrorHandler()(rw, req, fmt.Errorf("internal error: 101 switching protocols response with non-writable body"))
return
}
backConnCloseCh := make(chan bool)
go func() {
// Ensure that the cancellation of a request closes the backend.
// See issue https://golang.org/issue/35559.
select {
case <-req.Context().Done():
case <-backConnCloseCh:
}
backConn.Close()
}()
defer close(backConnCloseCh)
conn, brw, err := hj.Hijack()
if err != nil {
p.getErrorHandler()(rw, req, fmt.Errorf("Hijack failed on protocol switch: %v", err))
return
}
defer conn.Close()
copyHeader(rw.Header(), res.Header)
res.Header = rw.Header()
//将response的body赋值为空,只写入头部信息
res.Body = nil
//将下游的数据写入response里面
if err := res.Write(brw); err != nil {
p.getErrorHandler()(rw, req, fmt.Errorf("response write: %v", err))
return
}
//刷新写入状态
if err := brw.Flush(); err != nil {
p.getErrorHandler()(rw, req, fmt.Errorf("response flush: %v", err))
return
}
errc := make(chan error, 1)
//升级成功,但是还有保持一直维持的状态
//交换协议,一直维持互相拷贝,直到一方报错,返回
spc := switchProtocolCopier{user: conn, backend: backConn}
go spc.copyToBackend(errc)
go spc.copyFromBackend(errc)
<-errc
return
}
三. iris中httputil.ReverseProxy使用示例
- 可能不太符合规范,只是一个示例
- 编写中间件,在中间件中创建httputil.ReverseProxy保存到iris的context中,创建httputil.ReverseProxy时:
- 在httputil.ReverseProxy的director函数中获取目标地址,兼容请求参数,添加请求头
- 在httputil.ReverseProxy的modifyFunc函数中校验并重写响应
- 在httputil.ReverseProxy的errorHandler函数中处理异常
package middleware
import (
"bytes"
"encoding/json"
"errors"
"fmt"
"github.com/kataras/iris"
"io/ioutil"
"lmcd_adapter_police/config"
baseResp "lmcd_adapter_police/entity/response"
"lmcd_adapter_police/lmerror"
"lmcd_adapter_police/log"
encrypt "lmcd_adapter_police/util/aes"
"net/http"
"net/http/httputil"
"net/url"
"regexp"
"strconv"
"strings"
)
var UrlMap = map[string]string{
"/gw/police/site/info/update": "/site/info/update", //上报经营者信息
"/gw/police/site/room/add": "/site/room/add", //增加子经营信息 (民宿 添加房号)
"/gw/police/site/room/update": "/site/room/update", //修改子经营信息 (民宿 修改房号)
"/gw/police/site/room/delete": "/site/room/delete", //删除子经营信息 (民宿 删除房号)
"/gw/police/site/enter/report": "/site/enter/report", //流动人口进入上报
"/gw/police/site/exit/report": "/site/exit/report", //流动人口离开上报
}
//var policeReportDomain, _ = config.Conf.GetValue("police", "domain")
var policeReportDomain = "http://localHost:8080"
func GetPoliceProxyMiddleware(ctx iris.Context) {
msgid := ctx.GetHeader("msgid")
if policeReportDomain == "" {
baseResp.ResponseCode(ctx, msgid, lmerror.HTTP_DECODE_ERROR)
return
}
targetURL, _ := url.Parse(policeReportDomain)
reverseProxy := NewPoliceReverseProxy(targetURL)
if nil == reverseProxy {
baseResp.ResponseCode(ctx, msgid, lmerror.HTTP_DECODE_ERROR)
return
}
ctx.Values().Set("reverseProxy", reverseProxy)
ctx.Next()
ctx.Header("Access-Control-Allow-Headers", "origin, x-requested-with, content-type")
ctx.Header("Access-Control-Allow-Methods", "PUT, GET, POST, DELETE, OPTIONS")
ctx.Header("Access-Control-Allow-Origin", "*")
ctx.Header("Access-Control-Allow-Credentials", "true")
ctx.Header("Content-Type", "application/json")
ctx.Header("Sec-Fetch-Mode", "cors")
ctx.Header("Sec-Fetch-Site", "cross-site")
}
func NewPoliceReverseProxy(target *url.URL) *httputil.ReverseProxy {
//1.封装用来修改请求路径,请求参数的Director函数
director := func(req *http.Request) {
msgid := req.Header.Get("msgid")
path := req.URL.Path
// 1. 获取path上的请求参数
targetQuery := target.RawQuery
// 2. 封装用来修改请求路径、请求参数的Director函数
re := regexp.MustCompile("^/dir(.*)")
req.URL.Path = re.ReplaceAllString(path, "$1")
req.URL.Scheme = target.Scheme
req.URL.Host = target.Host
// 2.4 设置请求路径
targetPath, ok := UrlMap[req.URL.Path]
if !ok {
baseResp.APIResourceCode(msgid, lmerror.HTTP_DECODE_ERROR)
return
}
req.URL.Path = targetPath
// 2.5 设置请求参数
if targetQuery == "" || req.URL.RawQuery == "" {
req.URL.RawQuery = targetQuery + req.URL.RawQuery
} else {
req.URL.RawQuery = targetQuery + "&" + req.URL.RawQuery
}
// 读取并处理请求 Body
if req.Body == nil {
return
}
body, err := getRequestByBody(req)
defer req.Body.Close()
if err != nil {
baseResp.APIResourceCode(msgid, lmerror.HTTP_DECODE_ERROR)
return
}
reqMap := make(map[string]interface{})
// 将字节切片转换为 Map
if err := json.Unmarshal(body, &reqMap); err != nil {
baseResp.APIResourceCode(msgid, lmerror.HTTP_DECODE_ERROR)
return
}
reqMap = requestCompatibility(reqMap)
reqByte, _ := json.Marshal(reqMap)
// 更新请求上下文中的 Body 数据
req.Body = ioutil.NopCloser(bytes.NewReader(reqByte))
req.ContentLength = int64(len(reqByte))
// 设置请求头
// req.Header.Set("token", "ssss")
}
//2.封装可用用来改写响应的modifyFunc 函数
modifyFunc := func(res *http.Response) error {
msgid := res.Header.Get("msgid")
if res.StatusCode != 200 {
// 2.1 此处判断如果响应的 HTTP 状态码为异常时,封装异常返回
return errors.New("error statusCode")
}
// 2.2 读取下游服务响应的 body
oldPayload, err := ioutil.ReadAll(res.Body)
if err != nil {
return err
}
policResult := &baseResp.PolicResult{}
resultData := &baseResp.APIJsonReturn{}
if err := json.Unmarshal(oldPayload, policResult); err != nil {
log.ErrorLog(msgid, "请求公安接口解析响应异常 result:= %v", policResult)
resultData = baseResp.APIResourceCode(msgid, lmerror.CONF_UNKNOW_PMSTYPE_ERROR)
} else {
statusCode, err := strconv.Atoi(policResult.ErrCode)
if err != nil {
log.ErrorLog(msgid, "请求公安接口获取响应状态码异常 result:= %v", policResult)
resultData = baseResp.APIResourceCode(msgid, lmerror.POLICE_RETURN_RESULT_ERROR)
} else if statusCode != 0 {
resultData = baseResp.APIResource(msgid, lmerror.POLICE_RETURN_RESULT_ERROR, policResult.ErrMsg, nil)
} else if len(policResult.TraceId) <= 0 {
resultData = baseResp.APIResourceOk(msgid, policResult.Result)
} else {
traceMap := map[string]string{"traceId": policResult.TraceId}
resultData = baseResp.APIResourceOk(msgid, traceMap)
}
}
// 2.3 封装新的响应
newPayload, _ := json.Marshal(resultData)
// 2.4 将数据再次填充到 resp 中 (ioutil.NopCloser() 函数直接将 byte 数据转换为 Body 中的 read)
res.Body = ioutil.NopCloser(bytes.NewBuffer(newPayload))
// 2.5 重置响应数据长度和 Content-Length
res.ContentLength = int64(len(newPayload))
res.Header.Set("Content-Length", fmt.Sprint(len(newPayload)))
return nil
}
//3.设置异常回调,在上面几个步骤如果发送异常,返回的err不为nin,
//会执行该函数,执行指定业务逻辑
errorHandler := func(res http.ResponseWriter, req *http.Request, err error) {
if nil == err {
return
}
msgid := res.Header().Get("msgid")
resultData := baseResp.APIResourceCode(msgid, lmerror.CONF_UNKNOW_PMSTYPE_ERROR)
newPayLoad, _ := json.Marshal(resultData)
res.Write(newPayLoad)
}
//5.创建ReverseProxy返回
return &httputil.ReverseProxy{Director: director, ModifyResponse: modifyFunc, ErrorHandler: errorHandler}
}
func getRequestByBody(req *http.Request) ([]byte, *lmerror.LmError) {
data, err := ioutil.ReadAll(req.Body)
if err != nil {
return []byte(""), lmerror.NewLmError(lmerror.HTTP_NO_ENCODE, "Failed to read request body")
}
msgid := req.Header.Get("msgid")
lmts := req.Header.Get("lmts")
path := req.URL.Path
method := req.Method
remote := req.RemoteAddr
lmsignature := strings.ToLower(req.Header.Get("lmsignature"))
log.TraceLog(msgid, "RequestRaw %s:[%s]:%s:%s:%s:RequestRaw:%s", method, remote, path, lmsignature, lmts, string(data))
lmencode := req.Header.Get("lmencode")
if len(lmencode) <= 0 {
return data, nil
}
lmencodevalue, err := strconv.Atoi(lmencode)
if err != nil {
return []byte(""), lmerror.NewLmError(lmerror.HTTP_NO_ENCODE, "Invalid lmencode value")
}
support, _ := config.Conf.Int("AES", "support")
if lmencodevalue < support {
return []byte(""), lmerror.NewLmError(lmerror.HTTP_ENCODE_NOTSUPPORT, "Encoding not supported")
}
if lmencodevalue == 1 {
aeskey, _ := config.Conf.GetValue("AES", "aeskey")
aesiv, _ := config.Conf.GetValue("AES", "aesiv")
c := encrypt.NewAES()
c.Key = []byte(aeskey)
c.Iv = []byte(aesiv)
data, err = c.AesCBCDncrypt(data)
if err != nil {
return []byte(""), lmerror.NewLmError(lmerror.HTTP_DECODE_ERROR, "Failed to decode request body")
}
log.TraceLog(msgid, "RequestDncryptData:%s", string(data))
}
return data, nil
}
var imageKeyList = []string{"ownerPic", "licensePic", "cardPic", "pic"}
func requestCompatibility(request map[string]interface{}) map[string]interface{} {
for _, item := range imageKeyList {
if _, ok := request[item]; ok {
request[item] = joinImageBase64Prefix(request[item])
}
}
return request
}
func joinImageBase64Prefix(originalBase64 interface{}) string {
if nil == originalBase64 {
return ""
}
originalStr := originalBase64.(string)
if len(originalStr) <= 0 {
return ""
}
base64Prefix := "data:text/plain;base64,"
return base64Prefix + originalStr
}
- 在需要代理的router接口中实现中间件获取到ReverseProxy,在处理器中执行这个Proxy
package main
import (
"github.com/kataras/iris"
baseResp "lmcd_adapter_police/entity/response"
"lmcd_adapter_police/middleware"
"net/http/httputil"
)
func main() {
app := iris.New()
// 使用自定义中间件
gateway := app.Party("/gw")
{
//1.使用GetPoliceProxyMiddleware中间件获取到ReverseProxy
gateway.Post("/police/*", middleware.GetPoliceProxyMiddleware, func(ctx iris.Context) {
//处理器中通过ReverseProxy的执行调用到目标服务
reverseProxy := ctx.Values().Get("reverseProxy")
reverseProxy.(*httputil.ReverseProxy).ServeHTTP(ctx.ResponseWriter(), ctx.Request())
})
}
app.Post("/site/info/update", routeHandler)
app.Run(iris.Addr(":8080"))
}
func routeHandler(ctx iris.Context) {
// 获取解密后的参数
var params map[string]interface{}
err := ctx.ReadJSON(¶ms)
if err != nil {
ctx.StatusCode(iris.StatusBadRequest)
ctx.WriteString("Invalid JSON")
return
}
resp := &baseResp.PolicResult{
ErrCode: "0",
ErrMsg: "成功",
}
ctx.JSON(*resp)
}