mirror of
https://github.com/langhuihui/monibuca.git
synced 2025-12-24 13:48:04 +08:00
242 lines
7.3 KiB
Go
242 lines
7.3 KiB
Go
package config
|
||
|
||
import (
|
||
"crypto/sha256"
|
||
"crypto/subtle"
|
||
"crypto/tls"
|
||
"log/slog"
|
||
"net/http"
|
||
|
||
"m7s.live/v5/pkg/task"
|
||
|
||
"github.com/grpc-ecosystem/grpc-gateway/v2/runtime"
|
||
|
||
"time"
|
||
)
|
||
|
||
var _ HTTPConfig = (*HTTP)(nil)
|
||
|
||
type Middleware func(string, http.Handler) http.Handler
|
||
type HTTP struct {
|
||
ListenAddr string `desc:"监听地址"`
|
||
ListenAddrTLS string `desc:"监听地址HTTPS"`
|
||
CertFile string `desc:"HTTPS证书文件"`
|
||
KeyFile string `desc:"HTTPS密钥文件"`
|
||
CORS bool `default:"true" desc:"是否自动添加CORS头"` //是否自动添加CORS头
|
||
UserName string `desc:"基本身份认证用户名"`
|
||
Password string `desc:"基本身份认证密码"`
|
||
ReadTimeout time.Duration `desc:"读取超时"`
|
||
WriteTimeout time.Duration `desc:"写入超时"`
|
||
IdleTimeout time.Duration `desc:"空闲超时"`
|
||
mux *http.ServeMux
|
||
grpcMux *runtime.ServeMux
|
||
middlewares []Middleware
|
||
}
|
||
type HTTPConfig interface {
|
||
GetHTTPConfig() *HTTP
|
||
// Handle(string, http.Handler)
|
||
// Handler(*http.Request) (http.Handler, string)
|
||
// AddMiddleware(Middleware)
|
||
}
|
||
|
||
func (config *HTTP) GetHandler() http.Handler {
|
||
if config.grpcMux != nil {
|
||
return config.grpcMux
|
||
}
|
||
return config.mux
|
||
}
|
||
|
||
func (config *HTTP) CreateHttpMux() *http.ServeMux {
|
||
config.mux = http.NewServeMux()
|
||
return config.mux
|
||
}
|
||
|
||
func (config *HTTP) GetGRPCMux() *runtime.ServeMux {
|
||
return config.grpcMux
|
||
}
|
||
|
||
func (config *HTTP) SetMux(mux *runtime.ServeMux) {
|
||
config.grpcMux = mux
|
||
handler := func(w http.ResponseWriter, r *http.Request, pathParams map[string]string) {
|
||
config.mux.ServeHTTP(w, r)
|
||
}
|
||
mux.HandlePath("GET", "/", handler)
|
||
mux.HandlePath("POST", "/", handler)
|
||
}
|
||
|
||
func (config *HTTP) AddMiddleware(middleware Middleware) {
|
||
config.middlewares = append(config.middlewares, middleware)
|
||
}
|
||
|
||
func (config *HTTP) Handle(path string, f http.Handler, last bool) {
|
||
if config.mux == nil {
|
||
config.mux = http.NewServeMux()
|
||
}
|
||
if config.CORS {
|
||
f = CORS(f)
|
||
}
|
||
if config.UserName != "" && config.Password != "" {
|
||
f = BasicAuth(config.UserName, config.Password, f)
|
||
}
|
||
for _, middleware := range config.middlewares {
|
||
f = middleware(path, f)
|
||
}
|
||
config.mux.Handle(path, f)
|
||
}
|
||
|
||
func (config *HTTP) GetHTTPConfig() *HTTP {
|
||
return config
|
||
}
|
||
|
||
// func (config *HTTP) Handler(r *http.Request) (h http.Handler, pattern string) {
|
||
// return config.mux.Handler(r)
|
||
// }
|
||
|
||
func (config *HTTP) CreateHTTPWork(logger *slog.Logger) *ListenHTTPWork {
|
||
ret := &ListenHTTPWork{HTTP: config}
|
||
ret.Logger = logger.With("addr", config.ListenAddr)
|
||
return ret
|
||
}
|
||
|
||
func (config *HTTP) CreateHTTPSWork(logger *slog.Logger) *ListenHTTPSWork {
|
||
ret := &ListenHTTPSWork{ListenHTTPWork{HTTP: config}}
|
||
ret.Logger = logger.With("addr", config.ListenAddrTLS)
|
||
return ret
|
||
}
|
||
|
||
func CORS(next http.Handler) http.Handler {
|
||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||
header := w.Header()
|
||
header.Set("Access-Control-Allow-Credentials", "true")
|
||
header.Set("Cross-Origin-Resource-Policy", "cross-origin")
|
||
header.Set("Access-Control-Allow-Headers", "Content-Type,Access-Token,Authorization")
|
||
header.Set("Access-Control-Allow-Private-Network", "true")
|
||
origin := r.Header["Origin"]
|
||
if len(origin) == 0 {
|
||
header.Set("Access-Control-Allow-Origin", "*")
|
||
} else {
|
||
header.Set("Access-Control-Allow-Origin", origin[0])
|
||
}
|
||
if next != nil && r.Method != "OPTIONS" {
|
||
next.ServeHTTP(w, r)
|
||
}
|
||
})
|
||
}
|
||
|
||
func BasicAuth(u, p string, next http.Handler) http.Handler {
|
||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||
// Extract the username and password from the request
|
||
// Authorization header. If no Authentication header is present
|
||
// or the header value is invalid, then the 'ok' return value
|
||
// will be false.
|
||
username, password, ok := r.BasicAuth()
|
||
if ok {
|
||
// Calculate SHA-256 hashes for the provided and expected
|
||
// usernames and passwords.
|
||
usernameHash := sha256.Sum256([]byte(username))
|
||
passwordHash := sha256.Sum256([]byte(password))
|
||
expectedUsernameHash := sha256.Sum256([]byte(u))
|
||
expectedPasswordHash := sha256.Sum256([]byte(p))
|
||
|
||
// 使用 subtle.ConstantTimeCompare() 进行校验
|
||
// the provided username and password hashes equal the
|
||
// expected username and password hashes. ConstantTimeCompare
|
||
// 如果值相等,则返回1,否则返回0。
|
||
// Importantly, we should to do the work to evaluate both the
|
||
// username and password before checking the return values to
|
||
// 避免泄露信息。
|
||
usernameMatch := (subtle.ConstantTimeCompare(usernameHash[:], expectedUsernameHash[:]) == 1)
|
||
passwordMatch := (subtle.ConstantTimeCompare(passwordHash[:], expectedPasswordHash[:]) == 1)
|
||
|
||
// If the username and password are correct, then call
|
||
// the next handler in the chain. Make sure to return
|
||
// afterwards, so that none of the code below is run.
|
||
if usernameMatch && passwordMatch {
|
||
if next != nil {
|
||
next.ServeHTTP(w, r)
|
||
}
|
||
return
|
||
}
|
||
}
|
||
|
||
// If the Authentication header is not present, is invalid, or the
|
||
// username or password is wrong, then set a WWW-Authenticate
|
||
// header to inform the client that we expect them to use basic
|
||
// authentication and send a 401 Unauthorized response.
|
||
w.Header().Set("WWW-Authenticate", `Basic realm="restricted", charset="UTF-8"`)
|
||
http.Error(w, "Unauthorized", http.StatusUnauthorized)
|
||
})
|
||
}
|
||
|
||
type ListenHTTPWork struct {
|
||
task.Task
|
||
*HTTP
|
||
*http.Server
|
||
}
|
||
|
||
func (task *ListenHTTPWork) Start() (err error) {
|
||
task.Server = &http.Server{
|
||
Addr: task.ListenAddr,
|
||
ReadTimeout: task.HTTP.ReadTimeout,
|
||
WriteTimeout: task.HTTP.WriteTimeout,
|
||
IdleTimeout: task.HTTP.IdleTimeout,
|
||
Handler: task.GetHandler(),
|
||
}
|
||
return
|
||
}
|
||
|
||
func (task *ListenHTTPWork) Go() error {
|
||
task.Info("listen http")
|
||
return task.Server.ListenAndServe()
|
||
}
|
||
|
||
func (task *ListenHTTPWork) Dispose() {
|
||
task.Info("http server stop")
|
||
task.Server.Close()
|
||
}
|
||
|
||
type ListenHTTPSWork struct {
|
||
ListenHTTPWork
|
||
}
|
||
|
||
func (task *ListenHTTPSWork) Start() (err error) {
|
||
cer, _ := tls.X509KeyPair(LocalCert, LocalKey)
|
||
task.Server = &http.Server{
|
||
Addr: task.HTTP.ListenAddrTLS,
|
||
ReadTimeout: task.HTTP.ReadTimeout,
|
||
WriteTimeout: task.HTTP.WriteTimeout,
|
||
IdleTimeout: task.HTTP.IdleTimeout,
|
||
Handler: task.HTTP.GetHandler(),
|
||
TLSConfig: &tls.Config{
|
||
Certificates: []tls.Certificate{cer},
|
||
CipherSuites: []uint16{
|
||
tls.TLS_AES_128_GCM_SHA256,
|
||
tls.TLS_CHACHA20_POLY1305_SHA256,
|
||
tls.TLS_AES_256_GCM_SHA384,
|
||
//tls.TLS_RSA_WITH_AES_128_CBC_SHA,
|
||
//tls.TLS_RSA_WITH_AES_256_CBC_SHA,
|
||
//tls.TLS_RSA_WITH_AES_128_GCM_SHA256,
|
||
//tls.TLS_RSA_WITH_AES_256_GCM_SHA384,
|
||
tls.TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305,
|
||
tls.TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305,
|
||
tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
|
||
tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,
|
||
tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,
|
||
tls.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,
|
||
tls.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256,
|
||
tls.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA,
|
||
tls.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256,
|
||
tls.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA,
|
||
tls.TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA,
|
||
tls.TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA,
|
||
},
|
||
},
|
||
}
|
||
return
|
||
}
|
||
|
||
func (task *ListenHTTPSWork) Go() error {
|
||
task.Info("listen https")
|
||
return task.Server.ListenAndServeTLS(task.HTTP.CertFile, task.HTTP.KeyFile)
|
||
}
|