2023-12-09 16:34:20 +08:00
|
|
|
|
package handler
|
|
|
|
|
|
|
|
|
|
import (
|
2023-12-20 17:49:02 +08:00
|
|
|
|
"fmt"
|
2023-12-09 16:34:20 +08:00
|
|
|
|
"net/http"
|
|
|
|
|
"net/http/httputil"
|
|
|
|
|
"strconv"
|
|
|
|
|
"strings"
|
2025-05-29 16:53:30 +08:00
|
|
|
|
"time"
|
2023-12-09 16:34:20 +08:00
|
|
|
|
|
2025-05-29 16:53:30 +08:00
|
|
|
|
"git.pyer.club/kingecg/gohttpd/healthcheck"
|
2023-12-09 16:34:20 +08:00
|
|
|
|
"git.pyer.club/kingecg/gohttpd/model"
|
2023-12-20 17:49:02 +08:00
|
|
|
|
"git.pyer.club/kingecg/gologger"
|
2023-12-09 16:34:20 +08:00
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
type ProxyHandler struct {
|
2023-12-20 17:49:02 +08:00
|
|
|
|
proxy []*httputil.ReverseProxy
|
|
|
|
|
Upstreams []string
|
|
|
|
|
count int
|
2025-05-29 16:53:30 +08:00
|
|
|
|
checker *healthcheck.HealthChecker // 健康检查器
|
2023-12-09 16:34:20 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (p *ProxyHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
2023-12-20 17:49:02 +08:00
|
|
|
|
l := gologger.GetLogger("Proxy")
|
|
|
|
|
originalUrl := r.Host + r.URL.String()
|
2023-12-09 16:34:20 +08:00
|
|
|
|
s, err := r.Cookie("s")
|
|
|
|
|
var proxyIndex int
|
|
|
|
|
if err != nil {
|
|
|
|
|
proxyIndex = p.count
|
|
|
|
|
p.count++
|
|
|
|
|
if p.count >= len(p.proxy) {
|
|
|
|
|
p.count = 0
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
proxyIndex, _ = strconv.Atoi(s.Value)
|
|
|
|
|
}
|
2023-12-20 17:49:02 +08:00
|
|
|
|
|
2025-05-29 16:53:30 +08:00
|
|
|
|
// 如果选中的上游服务器不健康,则进行重试
|
|
|
|
|
maxRetries := 3
|
|
|
|
|
for i := 0; i < maxRetries; i++ {
|
|
|
|
|
if p.checker == nil || p.checker.CheckHealth(p.Upstreams[proxyIndex]) {
|
|
|
|
|
l.Info(fmt.Sprintf("proxy %s to %s", originalUrl, p.Upstreams[proxyIndex]))
|
|
|
|
|
p.proxy[proxyIndex].ServeHTTP(w, r)
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
proxyIndex = (proxyIndex + 1) % len(p.proxy) // 选择下一个上游服务器
|
|
|
|
|
}
|
2023-12-14 23:47:04 +08:00
|
|
|
|
|
2025-05-29 16:53:30 +08:00
|
|
|
|
l.Error(fmt.Sprintf("All upstream servers are unhealthy"))
|
|
|
|
|
http.Error(w, "Service Unavailable", http.StatusServiceUnavailable)
|
|
|
|
|
}
|
2023-12-14 23:47:04 +08:00
|
|
|
|
|
2025-05-29 16:53:30 +08:00
|
|
|
|
// makeProxy 初始化httputil.ReverseProxy实例,并添加路径重写和会话粘滞cookie到响应
|
|
|
|
|
// 参数:
|
|
|
|
|
// upstream 上游服务器URL
|
|
|
|
|
// path HTTP路径配置
|
|
|
|
|
// index 上游服务器在列表中的索引
|
|
|
|
|
// 返回值:
|
|
|
|
|
// httputil.ReverseProxy实例
|
2023-12-14 11:25:31 +08:00
|
|
|
|
func makeProxy(upstream string, path *model.HttpPath, index int) *httputil.ReverseProxy {
|
2023-12-09 16:34:20 +08:00
|
|
|
|
p := &httputil.ReverseProxy{}
|
2023-12-14 23:47:04 +08:00
|
|
|
|
directiveHandlers := []func(r *http.Request){}
|
|
|
|
|
if len(path.Directives) > 0 {
|
|
|
|
|
for _, directive := range path.Directives {
|
2025-05-30 18:42:53 +08:00
|
|
|
|
ndirective := strings.TrimPrefix(directive, "Proxy_")
|
|
|
|
|
d := strings.Replace(string(ndirective), "$target", upstream, 1)
|
2023-12-14 23:47:04 +08:00
|
|
|
|
dh := GetUpdaterFn(d)
|
|
|
|
|
if dh != nil {
|
|
|
|
|
directiveHandlers = append(directiveHandlers, dh)
|
2023-12-09 16:34:20 +08:00
|
|
|
|
}
|
|
|
|
|
}
|
2023-12-14 23:47:04 +08:00
|
|
|
|
}
|
|
|
|
|
p.Director = func(req *http.Request) {
|
|
|
|
|
for _, handler := range directiveHandlers {
|
|
|
|
|
handler(req)
|
2023-12-14 11:25:31 +08:00
|
|
|
|
}
|
2023-12-09 16:34:20 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
p.ModifyResponse = func(resp *http.Response) error {
|
|
|
|
|
hasSticky := false
|
|
|
|
|
for _, cookie := range resp.Cookies() {
|
|
|
|
|
if cookie.Name == "s" {
|
|
|
|
|
hasSticky = true
|
|
|
|
|
break
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if !hasSticky {
|
|
|
|
|
c := http.Cookie{
|
|
|
|
|
Name: "s",
|
|
|
|
|
Value: strconv.Itoa(index),
|
|
|
|
|
}
|
|
|
|
|
resp.Header.Add("Set-Cookie", c.String())
|
|
|
|
|
}
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
return p
|
|
|
|
|
}
|
|
|
|
|
|
2025-05-29 16:53:30 +08:00
|
|
|
|
// NewProxyHandler 创建一个新的代理处理器
|
2023-12-09 16:34:20 +08:00
|
|
|
|
func NewProxyHandler(p *model.HttpPath) *ProxyHandler {
|
|
|
|
|
upstreamCount := len(p.Upstreams)
|
|
|
|
|
if upstreamCount == 0 {
|
|
|
|
|
panic("no upstream defined")
|
|
|
|
|
}
|
2023-12-20 17:49:02 +08:00
|
|
|
|
ph := &ProxyHandler{
|
|
|
|
|
Upstreams: p.Upstreams,
|
|
|
|
|
}
|
2023-12-09 16:34:20 +08:00
|
|
|
|
ph.proxy = make([]*httputil.ReverseProxy, upstreamCount)
|
|
|
|
|
|
2025-05-30 18:42:53 +08:00
|
|
|
|
for index, upstream := range p.Upstreams {
|
|
|
|
|
ph.proxy[index] = makeProxy(upstream, p, index)
|
|
|
|
|
}
|
2025-05-29 16:53:30 +08:00
|
|
|
|
|
2025-05-30 18:42:53 +08:00
|
|
|
|
if len(p.Upstreams) > 1 && p.HealthCheck != nil {
|
|
|
|
|
// 只有上游服务器数目大于1时才需要进行健康检查
|
|
|
|
|
// 从配置中获取健康检查参数,如果不存在则使用默认值
|
|
|
|
|
var interval time.Duration = 10 * time.Second
|
|
|
|
|
var timeout time.Duration = 5 * time.Second
|
|
|
|
|
var retries int = 3
|
|
|
|
|
if p.HealthCheck.Interval != "" {
|
2025-05-29 16:53:30 +08:00
|
|
|
|
var err error
|
2025-05-30 18:42:53 +08:00
|
|
|
|
interval, err = time.ParseDuration(p.HealthCheck.Interval)
|
2025-05-29 16:53:30 +08:00
|
|
|
|
if err != nil {
|
|
|
|
|
interval = 10 * time.Second // 默认值
|
|
|
|
|
}
|
|
|
|
|
}
|
2025-05-30 18:42:53 +08:00
|
|
|
|
if p.HealthCheck.Timeout != "" {
|
2025-05-29 16:53:30 +08:00
|
|
|
|
var err error
|
2025-05-30 18:42:53 +08:00
|
|
|
|
timeout, err = time.ParseDuration(p.HealthCheck.Timeout)
|
2025-05-29 16:53:30 +08:00
|
|
|
|
if err != nil {
|
|
|
|
|
timeout = 5 * time.Second // 默认值
|
|
|
|
|
}
|
|
|
|
|
}
|
2025-05-30 18:42:53 +08:00
|
|
|
|
if p.HealthCheck.Retries > 0 {
|
|
|
|
|
retries = p.HealthCheck.Retries
|
2025-05-29 16:53:30 +08:00
|
|
|
|
}
|
2025-05-30 18:42:53 +08:00
|
|
|
|
// 使用配置参数创建健康检查器
|
|
|
|
|
ph.checker = healthcheck.NewHealthChecker(interval, timeout, retries)
|
|
|
|
|
ph.checker.StartHealthCheck(ph.Upstreams, func(upstream string, healthy bool) {
|
|
|
|
|
// 当上游服务器状态变化时的回调函数
|
|
|
|
|
logger := gologger.GetLogger("Proxy")
|
|
|
|
|
if !healthy {
|
|
|
|
|
logger.Warn(fmt.Sprintf("Upstream %s is now unhealthy", upstream))
|
|
|
|
|
} else {
|
|
|
|
|
logger.Info(fmt.Sprintf("Upstream %s is now healthy", upstream))
|
|
|
|
|
}
|
|
|
|
|
})
|
2025-05-29 16:53:30 +08:00
|
|
|
|
}
|
|
|
|
|
|
2023-12-09 16:34:20 +08:00
|
|
|
|
return ph
|
2025-05-30 18:42:53 +08:00
|
|
|
|
}
|