-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathhandler_http.go
136 lines (119 loc) · 3.86 KB
/
handler_http.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
package main
import (
"context"
"crypto/tls"
_ "embed"
"mime"
"net"
"net/http"
"slices"
"strings"
"sync"
"github.com/mileusna/useragent"
"github.com/phuslu/log"
"github.com/puzpuzpuz/xsync/v3"
)
type HTTPHandler interface {
http.Handler
Load() error
}
type HTTPServerHandler struct {
Config HTTPConfig
ServerNames []string
ClientHelloMap *xsync.MapOf[string, *tls.ClientHelloInfo]
UserAgentMap *CachingMap[string, useragent.UserAgent]
RegionResolver *RegionResolver
ForwardHandler HTTPHandler
WebHandler HTTPHandler
}
type RequestInfo struct {
RemoteIP string
ServerAddr string
ServerName string
TLSVersion TLSVersion
ClientHelloInfo *tls.ClientHelloInfo
ClientHelloRaw []byte
TraceID log.XID
UserAgent useragent.UserAgent
GeoipInfo GeoipInfo
LogContext log.Context
}
var RequestInfoContextKey = struct {
name string
}{"request-info"}
var riPool = sync.Pool{
New: func() interface{} {
return new(RequestInfo)
},
}
func (h *HTTPServerHandler) Load() error {
for ext, typ := range mimeTypes {
mime.AddExtensionType(ext, typ)
}
return nil
}
func (h *HTTPServerHandler) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
ri := riPool.Get().(*RequestInfo)
defer riPool.Put(ri)
ri.RemoteIP, _, _ = net.SplitHostPort(req.RemoteAddr)
ri.ServerAddr = req.Context().Value(http.LocalAddrContextKey).(net.Addr).String()
if req.TLS != nil {
ri.ServerName = req.TLS.ServerName
ri.TLSVersion = TLSVersion(req.TLS.Version)
} else {
ri.ServerName = ""
ri.TLSVersion = 0
}
ri.ClientHelloInfo, ri.ClientHelloRaw = nil, nil
if h.ClientHelloMap != nil {
if v, ok := h.ClientHelloMap.Load(req.RemoteAddr); ok {
ri.ClientHelloInfo = v
if header := GetMirrorHeader(ri.ClientHelloInfo.Conn); header != nil {
ri.ClientHelloRaw = header.B
}
}
}
// fix http3 request
if req.Proto == "" && ri.ClientHelloInfo != nil && len(ri.ClientHelloInfo.SupportedProtos) > 0 && ri.ClientHelloInfo.SupportedProtos[0] == "h3" {
req.Proto, req.ProtoMajor, req.ProtoMinor = "HTTP/3.0", 3, 0
}
ri.UserAgent, _, _ = h.UserAgentMap.Get(req.Header.Get("User-Agent"))
if h.RegionResolver.MaxmindReader != nil {
ri.GeoipInfo.Country, ri.GeoipInfo.Region, ri.GeoipInfo.City, _ = h.RegionResolver.LookupCity(context.Background(), net.ParseIP(ri.RemoteIP))
}
ri.TraceID = log.NewXID()
ri.LogContext = log.NewContext(ri.LogContext[:0]).
Xid("trace_id", ri.TraceID).
Str("server_name", ri.ServerName).
Str("server_addr", ri.ServerAddr).
Str("tls_version", ri.TLSVersion.String()).
Str("remote_ip", ri.RemoteIP).
Str("user_agent", req.UserAgent()).
Str("http_method", req.Method).
Str("http_proto", req.Proto).
Str("http_host", req.Host).
Str("http_url", req.URL.String()).
Str("useragent_os", ri.UserAgent.OS+" "+ri.UserAgent.OSVersion).
Str("useragent_browser", ri.UserAgent.Name+" "+ri.UserAgent.Version).
Str("remote_country", ri.GeoipInfo.Country).
Str("remote_region", ri.GeoipInfo.Region).
Str("remote_city", ri.GeoipInfo.City).
Value()
hostname := req.Host
if h, _, err := net.SplitHostPort(req.Host); err == nil {
hostname = h
}
containsHostname := slices.Contains(h.ServerNames, hostname) ||
slices.ContainsFunc(h.ServerNames, func(s string) bool { return s != "" && s[0] == '*' && strings.HasSuffix(hostname, s[1:]) })
req = req.WithContext(context.WithValue(req.Context(), RequestInfoContextKey, ri))
switch {
case hostname != "" && !containsHostname:
h.ForwardHandler.ServeHTTP(rw, req)
case containsHostname && h.Config.Forward.Websocket != "" && req.URL.Path == h.Config.Forward.Websocket && ((req.Method == http.MethodGet && req.ProtoMajor == 1) || (req.Method == http.MethodConnect && req.ProtoAtLeast(2, 0))):
h.ForwardHandler.ServeHTTP(rw, req)
case req.Method == http.MethodConnect:
h.ForwardHandler.ServeHTTP(rw, req)
default:
h.WebHandler.ServeHTTP(rw, req)
}
}