star/httpreverse/reverse.go
starainrt 7650951518 1.http server支持黑白路由名单和子文件夹挂载
2.http反向代理支持同一个端口按host区分不通服务,以及黑白路由名单支持
2025-06-19 23:47:39 +08:00

274 lines
7.2 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package httpreverse
import (
"b612.me/apps/b612/httpreverse/rp"
"b612.me/starlog"
"b612.me/staros/sysconf"
"bufio"
"errors"
"io"
"io/ioutil"
"net"
"net/http"
"net/url"
"os"
"path"
"strings"
"sync"
)
type ReverseConfig struct {
Addr string
Port int
httpmux http.ServeMux
httpserver http.Server
Config []*SingleReverseConfig
routes map[string]*SingleReverseConfig
autogenCert bool //是否自动生成证书
}
type SingleReverseConfig struct {
Name string
ReverseURL map[string]any
UsingSSL bool
AllowHTTPWithHttps bool
AutoGenerateCert bool
Key string
Cert string
Host string
ProxyHost string
SkipSSLVerify bool
InHeader [][2]string
OutHeader [][2]string
Cookie [][3]string //[3]string should contains path::key::value
ReplaceList [][2]string
ReplaceOnce bool
proxy map[string]*rp.ReverseProxy
httpPage map[string]string
IPFilterMode int //0=off 1=useremote 2=add 3=filter
FilterXForward bool
FilterRemoteAddr bool
FilterMustKey string
FilterSetKey string
FilterFile string
CIDR []*net.IPNet
basicAuthUser string
basicAuthPwd string
protectAuthPage []string
blackip map[string]int
whiteip map[string]int
warningpage string
warnpagedata []byte
router Router
blackpath Router
whitepath Router
rootLeaf any
}
type HttpReverseServer struct {
Config []*ReverseConfig
}
func Parse(cfgPath string) (HttpReverseServer, error) {
var res HttpReverseServer
ini := sysconf.NewIni()
err := ini.ParseFromFile(cfgPath)
if err != nil {
return res, err
}
serverMap := make(map[string]*ReverseConfig)
for _, v := range ini.Data {
var cfg *ReverseConfig
keyID := v.Get("addr") + v.Get("port")
if _, ok := serverMap[keyID]; ok {
cfg = serverMap[keyID]
} else {
cfg = &ReverseConfig{
Addr: v.Get("addr"),
Port: v.Int("port"),
routes: make(map[string]*SingleReverseConfig),
}
serverMap[keyID] = cfg
}
var ins = SingleReverseConfig{
Name: v.Name,
Host: v.Get("host"),
UsingSSL: v.Bool("enablessl"),
AllowHTTPWithHttps: v.Bool("tlsallowhttp"),
AutoGenerateCert: v.Bool("autogencert"),
Key: v.Get("key"),
Cert: v.Get("cert"),
ReplaceOnce: v.Bool("replaceonce"),
IPFilterMode: v.Int("ipfiltermode"),
FilterXForward: v.Bool("filterxforward"),
FilterRemoteAddr: v.Bool("filterremoteaddr"),
FilterMustKey: v.Get("filtermustkey"),
FilterSetKey: v.Get("filtersetkey"),
FilterFile: v.Get("filterfile"),
basicAuthUser: v.Get("authuser"),
basicAuthPwd: v.Get("authpasswd"),
warningpage: v.Get("warnpage"),
}
if ins.IPFilterMode == 3 && ins.FilterFile != "" {
starlog.Infoln("IP Filter Mode 3, Load IP Filter File", ins.FilterFile)
f, err := os.Open(ins.FilterFile)
if err != nil {
return res, err
}
buf := bufio.NewReader(f)
count := 0
for {
line, err := buf.ReadString('\n')
if err != nil {
if err == io.EOF {
f.Close()
break
}
f.Close()
return res, err
}
line = strings.TrimSpace(line)
if !strings.Contains(line, "/") {
line += "/32" //todo区分IPV6
}
_, cidr, err := net.ParseCIDR(line)
if err != nil {
return res, err
}
ins.CIDR = append(ins.CIDR, cidr)
count++
}
starlog.Infoln("Load", count, "CIDR")
}
if ins.warningpage != "" {
data, err := ioutil.ReadFile(ins.warningpage)
if err != nil {
return res, err
}
ins.warnpagedata = data
}
ins.proxy = make(map[string]*rp.ReverseProxy)
ins.ReverseURL = make(map[string]any)
for _, reverse := range v.GetAll("reverse") {
kv := strings.SplitN(reverse, "::", 2)
if len(kv) != 2 {
return res, errors.New("reverse settings not correct:" + reverse)
}
if !strings.HasPrefix(strings.TrimSpace(kv[1]), "http://") && !strings.HasPrefix(strings.TrimSpace(kv[1]), "https://") {
ins.ReverseURL[strings.TrimSpace(kv[0])] = strings.TrimSpace(kv[1])
} else {
ins.ReverseURL[strings.TrimSpace(kv[0])], err = url.Parse(strings.TrimSpace(kv[1]))
if err != nil {
return res, err
}
}
}
for _, header := range v.GetAll("inheader") {
kv := strings.SplitN(header, "::", 2)
if len(kv) != 2 {
return res, errors.New("header settings not correct:" + header)
}
ins.InHeader = append(ins.InHeader, [2]string{strings.TrimSpace(kv[0]), strings.TrimSpace(kv[1])})
}
for _, authpage := range v.GetAll("authpage") {
ins.protectAuthPage = append(ins.protectAuthPage, authpage)
}
ins.blackip = make(map[string]int)
for _, blackip := range v.GetAll("blackip") {
ip, cidr, err := IPCIDR(blackip)
if err != nil {
return res, err
}
ins.blackip[ip] = cidr
}
for _, blackpath := range v.GetAll("blackpath") {
isStar := path.Base(blackpath) == "*"
if isStar {
blackpath = path.Dir(blackpath)
}
ins.blackpath.AddLeaf(blackpath, isStar)
}
for _, whitepath := range v.GetAll("whitepath") {
isStar := path.Base(whitepath) == "*"
if isStar {
whitepath = path.Dir(whitepath)
}
ins.whitepath.AddLeaf(whitepath, isStar)
}
ins.whiteip = make(map[string]int)
for _, whiteip := range v.GetAll("whiteip") {
ip, cidr, err := IPCIDR(whiteip)
if err != nil {
return res, err
}
ins.whiteip[ip] = cidr
}
for _, header := range v.GetAll("outheader") {
kv := strings.SplitN(header, "::", 2)
if len(kv) != 2 {
return res, errors.New("header settings not correct:" + header)
}
ins.OutHeader = append(ins.OutHeader, [2]string{strings.TrimSpace(kv[0]), strings.TrimSpace(kv[1])})
}
for _, cookie := range v.GetAll("cookie") {
kv := strings.SplitN(cookie, "::", 3)
if len(kv) != 3 {
return res, errors.New("cookie settings not correct:" + cookie)
}
ins.Cookie = append(ins.Cookie, [3]string{strings.TrimSpace(kv[0]), strings.TrimSpace(kv[1]), strings.TrimSpace(kv[2])})
}
for _, replace := range v.GetAll("replace") {
kv := strings.SplitN(replace, "::", 2)
if len(kv) != 2 {
return res, errors.New("replace settings not correct:" + replace)
}
ins.ReplaceList = append(ins.ReplaceList, [2]string{strings.TrimSpace(kv[0]), strings.TrimSpace(kv[1])})
}
cfg.routes[ins.Host] = &ins
cfg.Config = append(cfg.Config, &ins)
}
for _, v := range serverMap {
res.Config = append(res.Config, v)
}
return res, nil
}
func (h *HttpReverseServer) Run() error {
var wg sync.WaitGroup
var mu sync.Mutex
var haveErr string
for _, v := range h.Config {
wg.Add(1)
go func(v *ReverseConfig) {
defer wg.Done()
err := v.Run()
if err != nil {
mu.Lock()
haveErr += err.Error() + "\n"
mu.Unlock()
}
}(v)
}
wg.Wait()
if haveErr != "" {
return errors.New(haveErr)
}
return nil
}
func (h *HttpReverseServer) Close() error {
var haveErr string
for _, v := range h.Config {
err := v.Close()
if err != nil {
haveErr += err.Error() + "\n"
}
}
if haveErr != "" {
return errors.New(haveErr)
}
return nil
}