init(v0.0.1)

This commit is contained in:
wjqserver 2025-05-28 18:25:28 +08:00
parent aea609ce29
commit 484f2f016b
13 changed files with 2813 additions and 1 deletions

231
serve.go Normal file
View file

@ -0,0 +1,231 @@
package touka
import (
"context"
"crypto/tls"
"errors"
"fmt"
"log"
"net"
"net/http"
"os"
"os/signal"
"sync"
"syscall"
"time"
)
const defaultShutdownTimeout = 5 * time.Second // 定义默认的优雅关闭超时时间
// resolveAddress 辅助函数,处理传入的地址参数。
func resolveAddress(addr []string) string {
switch len(addr) {
case 0:
return ":8080" // 默认端口
case 1:
return addr[0]
default:
panic("too many parameters for Run method") // 参数过多则报错
}
}
// Run 启动 HTTP 服务器。
// 接受一个可选的地址参数,如果未提供则默认为 ":8080"。
func (engine *Engine) Run(addr ...string) (err error) {
address := resolveAddress(addr) // 解析服务器地址
log.Printf("Touka server listening on %s\n", address)
err = http.ListenAndServe(address, engine) // 启动 HTTP 服务器
return
}
// getShutdownTimeout 解析可选的超时参数,如果未提供或无效,则返回默认超时。
func getShutdownTimeout(timeouts []time.Duration) time.Duration {
var timeout time.Duration
if len(timeouts) > 0 {
timeout = timeouts[0]
if timeout <= 0 {
log.Printf("Warning: Provided shutdown timeout (%v) is non-positive. Using default timeout %v.\n", timeout, defaultShutdownTimeout)
timeout = defaultShutdownTimeout
}
} else {
timeout = defaultShutdownTimeout
}
return timeout
}
// handleGracefulShutdown 处理一个或多个 http.Server 实例的优雅关闭。
// 它监听操作系统信号,并在指定超时时间内尝试关闭所有服务器。
func handleGracefulShutdown(servers []*http.Server, timeout time.Duration) error {
quit := make(chan os.Signal, 1)
signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)
<-quit
log.Println("Shutting down Touka server(s)...")
ctx, cancel := context.WithTimeout(context.Background(), timeout)
defer cancel()
var wg sync.WaitGroup
var errs []error
var errsMutex sync.Mutex // 保护 errs 切片
for _, srv := range servers {
srv := srv // capture loop variable
wg.Add(1)
go func() {
defer wg.Done()
if err := srv.Shutdown(ctx); err != nil {
errsMutex.Lock()
if err == context.DeadlineExceeded {
log.Printf("Server %s shutdown timed out after %v.\n", srv.Addr, timeout)
errs = append(errs, fmt.Errorf("server %s shutdown timed out", srv.Addr))
} else {
log.Printf("Server %s forced to shutdown: %v\n", srv.Addr, err)
errs = append(errs, fmt.Errorf("server %s forced to shutdown: %w", srv.Addr, err))
}
errsMutex.Unlock()
}
}()
}
wg.Wait() // 等待所有服务器的关闭 Goroutine 完成
if len(errs) > 0 {
return errors.Join(errs...) // 返回所有收集到的错误
}
log.Println("Touka server(s) exited gracefully.")
return nil
}
// RunShutdown 启动 HTTP 服务器并支持优雅关闭。
// 它监听操作系统信号 (SIGINT, SIGTERM),并在指定超时时间内优雅地关闭服务器。
// addr: 服务器监听的地址,例如 ":8080"。
// timeouts: 可选的超时时间,如果未提供,则默认为 5 秒。
func (engine *Engine) RunShutdown(addr string, timeouts ...time.Duration) error {
timeout := getShutdownTimeout(timeouts)
srv := &http.Server{
Addr: addr,
Handler: engine, // Engine 实现了 http.Handler 接口
}
// 启动服务器在单独的 Goroutine 中运行,以便主 Goroutine 可以监听信号
go func() {
log.Printf("Touka HTTP server listening on %s\n", addr)
if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed {
log.Fatalf("Touka HTTP server listen error: %s\n", err)
}
}()
return handleGracefulShutdown([]*http.Server{srv}, timeout)
}
// RunWithTLS 启动 HTTPS 服务器并支持优雅关闭。
// 用户需自行创建并传入 *tls.Config 实例,以提供完整的 TLS 配置自由度。
// addr: 服务器监听的地址,例如 ":8443"。
// tlsConfig: 包含 TLS 证书、密钥及其他配置的 tls.Config 实例。
// timeouts: 可选的超时时间,如果未提供,则默认为 5 秒。
func (engine *Engine) RunWithTLS(addr string, tlsConfig *tls.Config, timeouts ...time.Duration) error {
if tlsConfig == nil {
return errors.New("tls.Config must not be nil for RunWithTLS")
}
timeout := getShutdownTimeout(timeouts)
srv := &http.Server{
Addr: addr,
Handler: engine,
TLSConfig: tlsConfig, // 使用用户传入的 tls.Config
}
if engine.useDefaultProtocols {
//加入HTTP2支持
engine.SetProtocols(&ProtocolsConfig{
Http1: true,
Http2: true, // 默认启用 HTTP/2
Http2_Cleartext: false,
})
}
go func() {
log.Printf("Touka HTTPS server listening on %s\n", addr)
if err := srv.ListenAndServeTLS("", ""); err != nil && err != http.ErrServerClosed {
log.Fatalf("Touka HTTPS server listen error: %s\n", err)
}
}()
return handleGracefulShutdown([]*http.Server{srv}, timeout)
}
// RunWithTLSRedir 启动 HTTP 和 HTTPS 服务器,并将所有 HTTP 请求重定向到 HTTPS。
// httpAddr: HTTP 服务器监听的地址,例如 ":80"。
// httpsAddr: HTTPS 服务器监听的地址,例如 ":443"。
// tlsConfig: 包含 TLS 证书、密钥及其他配置的 tls.Config 实例,用于 HTTPS 服务器。
// timeouts: 可选的超时时间,如果未提供,则默认为 5 秒。
func (engine *Engine) RunWithTLSRedir(httpAddr, httpsAddr string, tlsConfig *tls.Config, timeouts ...time.Duration) error {
if tlsConfig == nil {
return errors.New("tls.Config must not be nil for RunWithTLSRedir")
}
timeout := getShutdownTimeout(timeouts)
// HTTPS Server
httpsSrv := &http.Server{
Addr: httpsAddr,
Handler: engine,
TLSConfig: tlsConfig, // 使用用户传入的 tls.Config
}
if engine.useDefaultProtocols {
//加入HTTP2支持
engine.SetProtocols(&ProtocolsConfig{
Http1: true,
Http2: true, // 默认启用 HTTP/2
Http2_Cleartext: false,
})
}
// HTTP Server for redirection
httpSrv := &http.Server{
Addr: httpAddr,
Handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// 从 r.Host 提取 hostname例如 "localhost:8080" -> "localhost"
hostOnly, _, err := net.SplitHostPort(r.Host)
if err != nil { // r.Host 可能没有端口,例如 "example.com"
hostOnly = r.Host
}
// 从 httpsAddr 提取目标 HTTPS 端口,例如 ":443" -> "443"
_, targetHttpsPort, err := net.SplitHostPort(httpsAddr)
if err != nil { // httpsAddr 必须包含一个有效的端口
log.Fatalf("Error: Invalid HTTPS address '%s' for redirection. Must specify a port (e.g., ':443').", httpsAddr)
}
var redirectHost string
if targetHttpsPort == "443" {
redirectHost = hostOnly // 如果是默认 HTTPS 端口,则无需在 URL 中显式指定端口
} else {
redirectHost = net.JoinHostPort(hostOnly, targetHttpsPort) // 否则,显式指定端口
}
// 构建目标 HTTPS URL
targetURL := "https://" + redirectHost + r.URL.RequestURI()
http.Redirect(w, r, targetURL, http.StatusMovedPermanently) // 301 Permanent Redirect
}),
}
// Start HTTPS server
go func() {
log.Printf("Touka HTTPS server listening on %s\n", httpsAddr)
if err := httpsSrv.ListenAndServeTLS("", ""); err != nil && err != http.ErrServerClosed { // 同样,传入空字符串
log.Fatalf("Touka HTTPS server listen error: %s\n", err)
}
}()
// Start HTTP redirect server
go func() {
log.Printf("Touka HTTP redirect server listening on %s\n", httpAddr)
if err := httpSrv.ListenAndServe(); err != nil && err != http.ErrServerClosed {
log.Fatalf("Touka HTTP redirect server listen error: %s\n", err)
}
}()
return handleGracefulShutdown([]*http.Server{httpsSrv, httpSrv}, timeout)
}