Files
monibuca/pkg/config/http.go
2024-12-26 14:25:30 +08:00

242 lines
7.3 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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)
}