refactor: split utils.go into ws.go, http.go, and url.go for SRP
sinwoojin committed
Dec 16, 2025 at 10:22 UTC
151333c480ce5fd0e21a6ee06c7f4fbc58c1d5c4
3 files changed
+123
-113
utils/http.go
new
+81
@@ -0,0 +1,81 @@
1
+package utils
2
+
3
+import (
4
+ "mime"
5
+ "net"
6
+ "net/http"
7
+ "strings"
8
+)
9
+
10
+// IsHTMLContentType checks if the Content-Type header indicates HTML content
11
+// It properly handles media type parsing with parameters like charset
12
+func IsHTMLContentType(contentType string) bool {
13
+ if contentType == "" {
14
+ return false
15
+ }
16
+ mediaType, _, err := mime.ParseMediaType(contentType)
17
+ if err != nil {
18
+ return strings.HasPrefix(strings.ToLower(contentType), "text/html")
19
+ }
20
+ return mediaType == "text/html"
21
+}
22
+
23
+// GetContentType returns the MIME type for a file extension
24
+func GetContentType(ext string) string {
25
+ switch ext {
26
+ case ".html":
27
+ return "text/html; charset=utf-8"
28
+ case ".js":
29
+ return "application/javascript"
30
+ case ".json":
31
+ return "application/json"
32
+ case ".wasm":
33
+ return "application/wasm"
34
+ case ".css":
35
+ return "text/css"
36
+ case ".mp4":
37
+ return "video/mp4"
38
+ case ".svg":
39
+ return "image/svg+xml"
40
+ case ".png":
41
+ return "image/png"
42
+ case ".ico":
43
+ return "image/x-icon"
44
+ default:
45
+ return ""
46
+ }
47
+}
48
+
49
+// SetCORSHeaders sets permissive CORS headers for GET/OPTIONS and common headers
50
+func SetCORSHeaders(w http.ResponseWriter) {
51
+ w.Header().Set("Access-Control-Allow-Origin", "*")
52
+ w.Header().Set("Access-Control-Allow-Methods", "GET, OPTIONS")
53
+ w.Header().Set("Access-Control-Allow-Headers", "Content-Type, Accept, Accept-Encoding")
54
+}
55
+
56
+func IsLocalhost(r *http.Request) bool {
57
+ host := r.RemoteAddr
58
+ if h, _, err := net.SplitHostPort(r.RemoteAddr); err == nil {
59
+ host = h
60
+ }
61
+
62
+ // If a proxy/adapter reports a hostname, allow Docker Desktop host alias.
63
+ if strings.EqualFold(host, "host.docker.internal") {
64
+ return true
65
+ }
66
+
67
+ ip := net.ParseIP(host)
68
+ if ip == nil {
69
+ // Try resolving hostnames to IPs (best-effort).
70
+ if addrs, err := net.LookupIP(host); err == nil {
71
+ for _, a := range addrs {
72
+ if a.IsLoopback() || a.IsPrivate() {
73
+ return true
74
+ }
75
+ }
76
+ }
77
+ return false
78
+ }
79
+
80
+ return ip.IsLoopback() || ip.IsPrivate()
81
+}
utils/url.go
renamed
-113
@@ -1,52 +1,12 @@
1
package utils
2
3
import (
4
- "context"
4
"fmt"
6
- "io"
7
- "mime"
8
- "net"
9
- "net/http"
5
"net/url"
6
"regexp"
7
"strings"
13
-
14
- "github.com/gorilla/websocket"
15
-
16
- "gosuda.org/portal/portal/utils/wsstream"
8
)
9
19
-// NewWebSocketDialer returns a dialer that establishes WebSocket connections
20
-// and wraps them as io.ReadWriteCloser.
21
-func NewWebSocketDialer() func(context.Context, string) (io.ReadWriteCloser, error) {
22
- return func(ctx context.Context, url string) (io.ReadWriteCloser, error) {
23
- wsConn, _, err := websocket.DefaultDialer.Dial(url, nil)
24
- if err != nil {
25
- return nil, err
26
- }
27
- return &wsstream.WsStream{Conn: wsConn}, nil
28
- }
29
-}
30
-
31
-// defaultWebSocketUpgrader provides a permissive upgrader used across cmd binaries
32
-var defaultWebSocketUpgrader = websocket.Upgrader{
33
- CheckOrigin: func(r *http.Request) bool { return true },
34
-}
35
-
36
-// UpgradeWebSocket upgrades the request/response to a WebSocket connection using DefaultWebSocketUpgrader
37
-func UpgradeWebSocket(w http.ResponseWriter, r *http.Request, responseHeader http.Header) (*websocket.Conn, error) {
38
- return defaultWebSocketUpgrader.Upgrade(w, r, responseHeader)
39
-}
40
-
41
-// UpgradeToWSStream upgrades HTTP to WebSocket and wraps it as io.ReadWriteCloser
42
-func UpgradeToWSStream(w http.ResponseWriter, r *http.Request, responseHeader http.Header) (io.ReadWriteCloser, *websocket.Conn, error) {
43
- wsConn, err := UpgradeWebSocket(w, r, responseHeader)
44
- if err != nil {
45
- return nil, nil, err
46
- }
47
- return &wsstream.WsStream{Conn: wsConn}, wsConn, nil
48
-}
49
-
10
// URL-safe name validation regex
11
var urlSafeNameRegex = regexp.MustCompile(`^[\p{L}\p{N}_-]+$`)
12
@@ -129,45 +89,6 @@ func ParseURLs(raw string) []string {
89
return out
90
}
91
132
-// IsHTMLContentType checks if the Content-Type header indicates HTML content
133
-// It properly handles media type parsing with parameters like charset
134
-func IsHTMLContentType(contentType string) bool {
135
- if contentType == "" {
136
- return false
137
- }
138
- mediaType, _, err := mime.ParseMediaType(contentType)
139
- if err != nil {
140
- return strings.HasPrefix(strings.ToLower(contentType), "text/html")
141
- }
142
- return mediaType == "text/html"
143
-}
144
-
145
-// GetContentType returns the MIME type for a file extension
146
-func GetContentType(ext string) string {
147
- switch ext {
148
- case ".html":
149
- return "text/html; charset=utf-8"
150
- case ".js":
151
- return "application/javascript"
152
- case ".json":
153
- return "application/json"
154
- case ".wasm":
155
- return "application/wasm"
156
- case ".css":
157
- return "text/css"
158
- case ".mp4":
159
- return "video/mp4"
160
- case ".svg":
161
- return "image/svg+xml"
162
- case ".png":
163
- return "image/png"
164
- case ".ico":
165
- return "image/x-icon"
166
- default:
167
- return ""
168
- }
169
-}
170
-
92
// IsHexString reports whether s contains only hexadecimal characters
93
func IsHexString(s string) bool {
94
for _, c := range s {
@@ -178,13 +99,6 @@ func IsHexString(s string) bool {
99
return true
100
}
101
181
-// SetCORSHeaders sets permissive CORS headers for GET/OPTIONS and common headers
182
-func SetCORSHeaders(w http.ResponseWriter) {
183
- w.Header().Set("Access-Control-Allow-Origin", "*")
184
- w.Header().Set("Access-Control-Allow-Methods", "GET, OPTIONS")
185
- w.Header().Set("Access-Control-Allow-Headers", "Content-Type, Accept, Accept-Encoding")
186
-}
187
-
102
// IsSubdomain reports whether host matches the given domain pattern.
103
// Supports patterns like:
104
// - "*.example.com" (wildcard for any subdomain of example.com)
@@ -290,30 +204,3 @@ func DefaultBootstrapFrom(base string) string {
204
}
205
return "ws://" + host + "/relay"
206
}
293
-
294
-func IsLocalhost(r *http.Request) bool {
295
- host := r.RemoteAddr
296
- if h, _, err := net.SplitHostPort(r.RemoteAddr); err == nil {
297
- host = h
298
- }
299
-
300
- // If a proxy/adapter reports a hostname, allow Docker Desktop host alias.
301
- if strings.EqualFold(host, "host.docker.internal") {
302
- return true
303
- }
304
-
305
- ip := net.ParseIP(host)
306
- if ip == nil {
307
- // Try resolving hostnames to IPs (best-effort).
308
- if addrs, err := net.LookupIP(host); err == nil {
309
- for _, a := range addrs {
310
- if a.IsLoopback() || a.IsPrivate() {
311
- return true
312
- }
313
- }
314
- }
315
- return false
316
- }
317
-
318
- return ip.IsLoopback() || ip.IsPrivate()
319
-}
utils/ws.go
new
+42
@@ -0,0 +1,42 @@
1
+package utils
2
+
3
+import (
4
+ "context"
5
+ "io"
6
+ "net/http"
7
+
8
+ "github.com/gorilla/websocket"
9
+
10
+ "gosuda.org/portal/portal/utils/wsstream"
11
+)
12
+
13
+// NewWebSocketDialer returns a dialer that establishes WebSocket connections
14
+// and wraps them as io.ReadWriteCloser.
15
+func NewWebSocketDialer() func(context.Context, string) (io.ReadWriteCloser, error) {
16
+ return func(ctx context.Context, url string) (io.ReadWriteCloser, error) {
17
+ wsConn, _, err := websocket.DefaultDialer.Dial(url, nil)
18
+ if err != nil {
19
+ return nil, err
20
+ }
21
+ return &wsstream.WsStream{Conn: wsConn}, nil
22
+ }
23
+}
24
+
25
+// defaultWebSocketUpgrader provides a permissive upgrader used across cmd binaries
26
+var defaultWebSocketUpgrader = websocket.Upgrader{
27
+ CheckOrigin: func(r *http.Request) bool { return true },
28
+}
29
+
30
+// UpgradeWebSocket upgrades the request/response to a WebSocket connection using DefaultWebSocketUpgrader
31
+func UpgradeWebSocket(w http.ResponseWriter, r *http.Request, responseHeader http.Header) (*websocket.Conn, error) {
32
+ return defaultWebSocketUpgrader.Upgrade(w, r, responseHeader)
33
+}
34
+
35
+// UpgradeToWSStream upgrades HTTP to WebSocket and wraps it as io.ReadWriteCloser
36
+func UpgradeToWSStream(w http.ResponseWriter, r *http.Request, responseHeader http.Header) (io.ReadWriteCloser, *websocket.Conn, error) {
37
+ wsConn, err := UpgradeWebSocket(w, r, responseHeader)
38
+ if err != nil {
39
+ return nil, nil, err
40
+ }
41
+ return &wsstream.WsStream{Conn: wsConn}, wsConn, nil
42
+}