mirror of
https://github.com/yusing/godoxy.git
synced 2026-03-18 07:24:31 +01:00
192 lines
3.9 KiB
Go
Executable File
192 lines
3.9 KiB
Go
Executable File
package main
|
|
|
|
import (
|
|
"bytes"
|
|
"fmt"
|
|
"io"
|
|
"net"
|
|
"net/http"
|
|
"path"
|
|
"path/filepath"
|
|
"regexp"
|
|
"strings"
|
|
"sync"
|
|
"time"
|
|
|
|
"github.com/golang/glog"
|
|
xhtml "golang.org/x/net/html"
|
|
)
|
|
|
|
type Utils struct {
|
|
PortsInUse map[int]bool
|
|
portsInUseMutex sync.Mutex
|
|
}
|
|
|
|
var utils = &Utils{
|
|
PortsInUse: make(map[int]bool),
|
|
portsInUseMutex: sync.Mutex{},
|
|
}
|
|
|
|
func (u *Utils) findUseFreePort(startingPort int) (int, error) {
|
|
u.portsInUseMutex.Lock()
|
|
defer u.portsInUseMutex.Unlock()
|
|
for port := startingPort; port <= startingPort+100 && port <= 65535; port++ {
|
|
if u.PortsInUse[port] {
|
|
continue
|
|
}
|
|
addr := fmt.Sprintf(":%d", port)
|
|
l, err := net.Listen("tcp", addr)
|
|
if err == nil {
|
|
u.PortsInUse[port] = true
|
|
l.Close()
|
|
return port, nil
|
|
}
|
|
}
|
|
l, err := net.Listen("tcp", ":0")
|
|
if err == nil {
|
|
// NOTE: may not be after 20000
|
|
port := l.Addr().(*net.TCPAddr).Port
|
|
u.PortsInUse[port] = true
|
|
l.Close()
|
|
return port, nil
|
|
}
|
|
return -1, fmt.Errorf("unable to find free port: %v", err)
|
|
}
|
|
|
|
func (u *Utils) resetPortsInUse() {
|
|
u.portsInUseMutex.Lock()
|
|
for port := range u.PortsInUse {
|
|
u.PortsInUse[port] = false
|
|
}
|
|
u.portsInUseMutex.Unlock()
|
|
}
|
|
|
|
func (u *Utils) markPortInUse(port int) {
|
|
u.portsInUseMutex.Lock()
|
|
u.PortsInUse[port] = true
|
|
u.portsInUseMutex.Unlock()
|
|
}
|
|
|
|
func (*Utils) healthCheckHttp(targetUrl string) error {
|
|
// try HEAD first
|
|
// if HEAD is not allowed, try GET
|
|
resp, err := healthCheckHttpClient.Head(targetUrl)
|
|
if resp != nil {
|
|
resp.Body.Close()
|
|
}
|
|
if err != nil && resp != nil && resp.StatusCode == http.StatusMethodNotAllowed {
|
|
_, err = healthCheckHttpClient.Get(targetUrl)
|
|
}
|
|
if resp != nil {
|
|
resp.Body.Close()
|
|
}
|
|
return err
|
|
}
|
|
|
|
func (*Utils) healthCheckStream(scheme, host string) error {
|
|
conn, err := net.DialTimeout(scheme, host, 5*time.Second)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
conn.Close()
|
|
return nil
|
|
}
|
|
|
|
func (*Utils) snakeToCamel(s string) string {
|
|
toHyphenCamel := http.CanonicalHeaderKey(strings.ReplaceAll(s, "_", "-"))
|
|
return strings.ReplaceAll(toHyphenCamel, "-", "")
|
|
}
|
|
|
|
func tryAppendPathPrefixImpl(pOrig, pAppend string) string {
|
|
switch {
|
|
case strings.Contains(pOrig, "://"):
|
|
return pOrig
|
|
case pOrig == "", pOrig == "#", pOrig == "/":
|
|
return pAppend
|
|
case filepath.IsLocal(pOrig) && !strings.HasPrefix(pOrig, pAppend):
|
|
return path.Join(pAppend, pOrig)
|
|
default:
|
|
return pOrig
|
|
}
|
|
}
|
|
|
|
var tryAppendPathPrefix func(string, string) string
|
|
var _ = func() int {
|
|
if glog.V(4) {
|
|
tryAppendPathPrefix = func(s1, s2 string) string {
|
|
replaced := tryAppendPathPrefixImpl(s1, s2)
|
|
glog.Infof("[Path sub] %s -> %s", s1, replaced)
|
|
return replaced
|
|
}
|
|
} else {
|
|
tryAppendPathPrefix = tryAppendPathPrefixImpl
|
|
}
|
|
return 1
|
|
}()
|
|
|
|
func htmlNodesSubPath(n *xhtml.Node, p string) {
|
|
if n.Type == xhtml.ElementNode {
|
|
for i, attr := range n.Attr {
|
|
switch attr.Key {
|
|
case "src", "href", "action": // img, script, link, form etc.
|
|
n.Attr[i].Val = tryAppendPathPrefix(attr.Val, p)
|
|
}
|
|
}
|
|
}
|
|
|
|
for c := n.FirstChild; c != nil; c = c.NextSibling {
|
|
htmlNodesSubPath(c, p)
|
|
}
|
|
}
|
|
|
|
func (*Utils) respHTMLSubPath(r *http.Response, p string) error {
|
|
// remove all path prefix from relative path in script, img, a, ...
|
|
doc, err := xhtml.Parse(r.Body)
|
|
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if p[0] == '/' {
|
|
p = p[1:]
|
|
}
|
|
htmlNodesSubPath(doc, p)
|
|
|
|
var buf bytes.Buffer
|
|
err = xhtml.Render(&buf, doc)
|
|
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
r.Body = io.NopCloser(strings.NewReader(buf.String()))
|
|
|
|
return nil
|
|
}
|
|
|
|
func (*Utils) respJSSubPath(r *http.Response, p string) error {
|
|
var buf bytes.Buffer
|
|
|
|
_, err := buf.ReadFrom(r.Body)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if p[0] == '/' {
|
|
p = p[1:]
|
|
}
|
|
|
|
js := buf.String()
|
|
|
|
re := regexp.MustCompile(`fetch\(["'].+["']\)`)
|
|
replace := func(match string) string {
|
|
match = match[7 : len(match)-2]
|
|
replaced := tryAppendPathPrefix(match, p)
|
|
return fmt.Sprintf(`fetch(%q)`, replaced)
|
|
}
|
|
js = re.ReplaceAllStringFunc(js, replace)
|
|
|
|
r.Body = io.NopCloser(strings.NewReader(js))
|
|
return nil
|
|
}
|