Files
谢宇宁andClaude Opus 5.5 3639d2184a Initial commit
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-22 14:47:17 +07:00

162 lines
3.9 KiB
Go
Raw Permalink 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 server
import (
"fmt"
"io/ioutil"
"net/http"
"os"
"os/signal"
"strconv"
"strings"
"syscall"
"time"
"videoweb/app/appg"
"videoweb/app/router"
// "videoweb/app/service/configser"
// "videoweb/app/service/translationser"
"videoweb/pub/common/log"
"videoweb/pub/common/srvversion"
// "videoweb/pub/storage/proxy"
// "videoweb/web/service"
"gopkg.in/yaml.v2"
)
func init() {
time.Local, _ = time.LoadLocation("Asia/Shanghai")
}
func Run(args []string) {
showVersion()
if err := initConfig(); err != nil {
log.Error("init config failed", log.E(err))
os.Exit(1)
}
initHttpServer()
initDHServer()
//tgbot.SendToProgram("启动", "app启动完毕")
shutdownHandler()
}
func initConfig() error {
buf, err := ioutil.ReadFile("conf/app.yaml")
if err != nil {
return err
}
if err = yaml.Unmarshal(buf, &appg.Conf); err != nil {
return err
}
return nil
}
func showVersion() {
log.Info(fmt.Sprintf("sevice version:%s", srvversion.SERVICE_VERSION))
log.Info(fmt.Sprintf("git version:%s", srvversion.GIT_VERSION))
log.Info(fmt.Sprintf("build date:%s", srvversion.BUILD_DATE))
log.Info(fmt.Sprintf("build ip:%s", srvversion.BUILD_IP))
}
// headWriter 拦截 WriteHeader 和 Write,丢弃 body 并记录长度,
// 在 flush 时补设 Content-Length 后再发送真正的状态码。
type headWriter struct {
http.ResponseWriter
contentLength int
statusCode int
wroteHeader bool
}
func (w *headWriter) WriteHeader(code int) {
if !w.wroteHeader {
w.statusCode = code
w.wroteHeader = true
}
}
func (w *headWriter) Write(b []byte) (int, error) {
if !w.wroteHeader {
w.statusCode = http.StatusOK
w.wroteHeader = true
}
w.contentLength += len(b)
return len(b), nil
}
// Flush 实现 http.Flusher,HEAD 响应无 body,做 no-op
func (w *headWriter) Flush() {}
// Unwrap 让 http.NewResponseController 能发现底层 writer 的其他接口
func (w *headWriter) Unwrap() http.ResponseWriter { return w.ResponseWriter }
// flush 将缓存的 Content-Length 和状态码写入底层 ResponseWriter
func (w *headWriter) flush() {
if !w.wroteHeader {
w.statusCode = http.StatusOK
w.wroteHeader = true
}
if w.contentLength > 0 && w.Header().Get("Content-Length") == "" {
w.Header().Set("Content-Length", strconv.Itoa(w.contentLength))
}
w.ResponseWriter.WriteHeader(w.statusCode)
}
// headToGet 将 HEAD 改写为 GET 让 Gin 路由匹配,同时用 headWriter 丢弃 body。
// 通过 X-Origin-Head 内部 header 让 templatecache / filecache 等中间件识别出原始 HEAD 请求,
// 从而避免一次性 URL 污染缓存造成 DoS 放大(进入前先 Del 防客户端伪造)。
func headToGet(h http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
r.Header.Del("X-Origin-Head")
if r.Method == http.MethodHead {
r.Method = http.MethodGet
r.Header.Set("X-Origin-Head", "1")
hw := &headWriter{ResponseWriter: w}
h.ServeHTTP(hw, r)
hw.flush()
return
}
h.ServeHTTP(w, r)
})
}
func initHttpServer() {
addr := fmt.Sprintf(":%s", appg.Conf.ListenAddr)
srv := &http.Server{
Addr: addr,
Handler: headToGet(router.Init()),
}
go func() {
// service connections
log.Info("服务已启动", log.Any("地址", addr))
if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed {
log.Fatal("listen:", log.E(err))
}
}()
}
func initDHServer() {
if strings.TrimSpace(appg.Conf.DHListenAddr) == "" {
return
}
addr := fmt.Sprintf(":%s", appg.Conf.DHListenAddr)
srv := &http.Server{
Addr: addr,
Handler: headToGet(router.DHInit()),
}
go func() {
log.Info("导航服务已启动", log.Any("地址", addr))
if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed {
log.Fatal("listen:", log.E(err))
}
}()
}
func shutdownHandler() {
quit := make(chan os.Signal, 1)
signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM, os.Kill)
<-quit
log.Warn("Shutdown... server")
}