Initial commit

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
谢宇宁
2026-09-29 11:47:47 +07:00
co-authored by Claude Opus 5.5
commit 0fbdba5ccf
622 changed files with 72105 additions and 0 deletions
+182
View File
@@ -0,0 +1,182 @@
package server
import (
"fmt"
"io/ioutil"
"net/http"
"os"
"os/signal"
"path/filepath"
"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) {
chdirToAppRoot()
showVersion()
if err := initConfig(); err != nil {
log.Error("init config failed", log.E(err))
os.Exit(1)
}
initHttpServer()
initDHServer()
//tgbot.SendToProgram("启动", "app启动完毕")
shutdownHandler()
}
// chdirToAppRoot 发版后 supervisor 的工作目录经常不是二进制所在目录。
// 仅当二进制旁边已有 conf/app.yaml 时才切换,避免本地 go run 切到临时目录。
func chdirToAppRoot() {
exe, err := os.Executable()
if err != nil {
return
}
dir := filepath.Dir(exe)
if resolved, err := filepath.EvalSymlinks(dir); err == nil {
dir = resolved
}
if _, err := os.Stat(filepath.Join(dir, "conf", "app.yaml")); err != nil {
return
}
if err := os.Chdir(dir); err != nil {
log.Warn("chdir app root failed", log.E(err), log.Any("dir", dir))
}
}
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")
}