Mihomo improved
This commit is contained in:
217
handlers/mihomo_proxy.go
Normal file
217
handlers/mihomo_proxy.go
Normal file
@@ -0,0 +1,217 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"io"
|
||||
"log"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
|
||||
"alpine-router/mihomo"
|
||||
)
|
||||
|
||||
func getMihomoAPIBase() string {
|
||||
cfg, err := mihomo.LoadConfig()
|
||||
if err != nil {
|
||||
return "http://127.0.0.1:9090"
|
||||
}
|
||||
ec, _ := cfg["external-controller"].(string)
|
||||
if ec == "" {
|
||||
ec = "0.0.0.0:9090"
|
||||
}
|
||||
if strings.HasPrefix(ec, "0.0.0.0:") || strings.HasPrefix(ec, ":") {
|
||||
ec = "127.0.0.1" + strings.TrimPrefix(ec, "0.0.0.0")
|
||||
}
|
||||
if !strings.HasPrefix(ec, "http") {
|
||||
ec = "http://" + ec
|
||||
}
|
||||
return ec
|
||||
}
|
||||
|
||||
func getMihomoSecret() string {
|
||||
cfg, err := mihomo.LoadConfig()
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
s, _ := cfg["secret"].(string)
|
||||
return s
|
||||
}
|
||||
|
||||
func getMihomoHostPort() (string, string) {
|
||||
u, err := url.Parse(getMihomoAPIBase())
|
||||
if err != nil {
|
||||
return "127.0.0.1", "9090"
|
||||
}
|
||||
host := u.Hostname()
|
||||
if host == "0.0.0.0" || host == "" {
|
||||
host = "127.0.0.1"
|
||||
}
|
||||
port := u.Port()
|
||||
if port == "" {
|
||||
if u.Scheme == "https" {
|
||||
port = "443"
|
||||
} else {
|
||||
port = "80"
|
||||
}
|
||||
}
|
||||
return host, port
|
||||
}
|
||||
|
||||
func HandleMihomoAPIProxy(w http.ResponseWriter, r *http.Request) {
|
||||
base := getMihomoAPIBase()
|
||||
secret := getMihomoSecret()
|
||||
|
||||
suffix := strings.TrimPrefix(r.URL.Path, "/api/mihomo/api/")
|
||||
target, err := url.Parse(base + "/" + suffix)
|
||||
if err != nil {
|
||||
fail(w, http.StatusInternalServerError, "parse mihomo url: "+err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
q := r.URL.Query()
|
||||
if r.Method == http.MethodGet {
|
||||
q.Set("nonce", "1")
|
||||
}
|
||||
target.RawQuery = q.Encode()
|
||||
|
||||
proxyReq, err := http.NewRequest(r.Method, target.String(), r.Body)
|
||||
if err != nil {
|
||||
fail(w, http.StatusInternalServerError, "create proxy request: "+err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
for k, vv := range r.Header {
|
||||
if strings.EqualFold(k, "Authorization") {
|
||||
continue
|
||||
}
|
||||
for _, v := range vv {
|
||||
proxyReq.Header.Add(k, v)
|
||||
}
|
||||
}
|
||||
if secret != "" {
|
||||
proxyReq.Header.Set("Authorization", "Bearer "+secret)
|
||||
}
|
||||
|
||||
client := &http.Client{}
|
||||
resp, err := client.Do(proxyReq)
|
||||
if err != nil {
|
||||
fail(w, http.StatusBadGateway, "mihomo api unreachable: "+err.Error())
|
||||
return
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
for k, vv := range resp.Header {
|
||||
if strings.EqualFold(k, "Content-Length") {
|
||||
continue
|
||||
}
|
||||
for _, v := range vv {
|
||||
w.Header().Add(k, v)
|
||||
}
|
||||
}
|
||||
|
||||
w.WriteHeader(resp.StatusCode)
|
||||
_, err = io.Copy(w, resp.Body)
|
||||
if err != nil {
|
||||
log.Printf("proxy copy error: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func HandleMihomoWSProxy(w http.ResponseWriter, r *http.Request) {
|
||||
secret := getMihomoSecret()
|
||||
host, port := getMihomoHostPort()
|
||||
|
||||
suffix := strings.TrimPrefix(r.URL.Path, "/api/mihomo/ws/")
|
||||
path := "/" + suffix
|
||||
if r.URL.RawQuery != "" {
|
||||
path += "?" + r.URL.RawQuery
|
||||
}
|
||||
|
||||
hj, ok := w.(http.Hijacker)
|
||||
if !ok {
|
||||
fail(w, http.StatusInternalServerError, "websocket hijack not supported")
|
||||
return
|
||||
}
|
||||
|
||||
clientConn, _, err := hj.Hijack()
|
||||
if err != nil {
|
||||
fail(w, http.StatusInternalServerError, "hijack failed: "+err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
remoteConn, err := net.Dial("tcp", net.JoinHostPort(host, port))
|
||||
if err != nil {
|
||||
fail(w, http.StatusBadGateway, "mihomo ws connect failed: "+err.Error())
|
||||
clientConn.Close()
|
||||
return
|
||||
}
|
||||
|
||||
upgradeReq := "GET " + path + " HTTP/1.1\r\n"
|
||||
upgradeReq += "Host: " + host + ":" + port + "\r\n"
|
||||
upgradeReq += "Upgrade: websocket\r\n"
|
||||
upgradeReq += "Connection: Upgrade\r\n"
|
||||
upgradeReq += "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n"
|
||||
upgradeReq += "Sec-WebSocket-Version: 13\r\n"
|
||||
if secret != "" {
|
||||
upgradeReq += "Authorization: Bearer " + secret + "\r\n"
|
||||
}
|
||||
for k, vv := range r.Header {
|
||||
if strings.EqualFold(k, "Upgrade") || strings.EqualFold(k, "Connection") ||
|
||||
strings.EqualFold(k, "Sec-Websocket-Key") || strings.EqualFold(k, "Sec-Websocket-Version") ||
|
||||
strings.EqualFold(k, "Sec-Websocket-Extensions") || strings.EqualFold(k, "Sec-Websocket-Protocol") ||
|
||||
strings.EqualFold(k, "Authorization") {
|
||||
continue
|
||||
}
|
||||
for _, v := range vv {
|
||||
upgradeReq += k + ": " + v + "\r\n"
|
||||
}
|
||||
}
|
||||
for k, v := range r.Header {
|
||||
if strings.EqualFold(k, "Sec-WebSocket-Protocol") {
|
||||
upgradeReq += "Sec-WebSocket-Protocol: " + strings.Join(v, ", ") + "\r\n"
|
||||
}
|
||||
if strings.EqualFold(k, "Sec-WebSocket-Extensions") {
|
||||
upgradeReq += "Sec-WebSocket-Extensions: " + strings.Join(v, ", ") + "\r\n"
|
||||
}
|
||||
}
|
||||
upgradeReq += "\r\n"
|
||||
|
||||
_, err = remoteConn.Write([]byte(upgradeReq))
|
||||
if err != nil {
|
||||
clientConn.Close()
|
||||
remoteConn.Close()
|
||||
return
|
||||
}
|
||||
|
||||
buf := make([]byte, 4096)
|
||||
n, err := remoteConn.Read(buf)
|
||||
if err != nil {
|
||||
clientConn.Close()
|
||||
remoteConn.Close()
|
||||
return
|
||||
}
|
||||
|
||||
respStr := string(buf[:n])
|
||||
if !strings.Contains(respStr, "101") {
|
||||
clientConn.Write(buf[:n])
|
||||
clientConn.Close()
|
||||
remoteConn.Close()
|
||||
return
|
||||
}
|
||||
|
||||
headerEnd := strings.Index(respStr, "\r\n\r\n")
|
||||
if headerEnd >= 0 {
|
||||
clientConn.Write(buf[:n])
|
||||
} else {
|
||||
clientConn.Write(buf[:n])
|
||||
}
|
||||
|
||||
go func() {
|
||||
io.Copy(remoteConn, clientConn)
|
||||
remoteConn.Close()
|
||||
}()
|
||||
go func() {
|
||||
io.Copy(clientConn, remoteConn)
|
||||
clientConn.Close()
|
||||
}()
|
||||
}
|
||||
Reference in New Issue
Block a user