102 lines
3.0 KiB
Go
102 lines
3.0 KiB
Go
package hostupdate
|
|
|
|
import (
|
|
"crypto/subtle"
|
|
"encoding/json"
|
|
"errors"
|
|
"io"
|
|
"net/http"
|
|
"strings"
|
|
)
|
|
|
|
type Server struct {
|
|
manager *Manager
|
|
token string
|
|
}
|
|
|
|
func NewServer(manager *Manager, token string) (*Server, error) {
|
|
if manager == nil {
|
|
return nil, errors.New("更新管理器不能为空")
|
|
}
|
|
if len(strings.TrimSpace(token)) < 32 {
|
|
return nil, errors.New("CANVAS_UPDATER_TOKEN 至少需要 32 个字符")
|
|
}
|
|
return &Server{manager: manager, token: strings.TrimSpace(token)}, nil
|
|
}
|
|
|
|
func (s *Server) Handler() http.Handler {
|
|
mux := http.NewServeMux()
|
|
mux.HandleFunc("GET /v1/status", func(w http.ResponseWriter, _ *http.Request) { writeJSON(w, http.StatusOK, s.manager.Snapshot()) })
|
|
mux.HandleFunc("POST /v1/check", func(w http.ResponseWriter, r *http.Request) {
|
|
status, err := s.manager.Check(r.Context())
|
|
if err != nil {
|
|
writeError(w, http.StatusBadGateway, err, status)
|
|
return
|
|
}
|
|
writeJSON(w, http.StatusOK, status)
|
|
})
|
|
mux.HandleFunc("POST /v1/update", func(w http.ResponseWriter, r *http.Request) {
|
|
var request StartRequest
|
|
if err := decodeJSON(r, &request); err != nil {
|
|
writeError(w, http.StatusBadRequest, err, nil)
|
|
return
|
|
}
|
|
status, err := s.manager.StartUpdate(request.TargetVersion)
|
|
if err != nil {
|
|
writeError(w, http.StatusConflict, err, status)
|
|
return
|
|
}
|
|
writeJSON(w, http.StatusAccepted, status)
|
|
})
|
|
mux.HandleFunc("POST /v1/rollback", func(w http.ResponseWriter, r *http.Request) {
|
|
var request RollbackRequest
|
|
if err := decodeJSON(r, &request); err != nil {
|
|
writeError(w, http.StatusBadRequest, err, nil)
|
|
return
|
|
}
|
|
status, err := s.manager.StartRollback(request.Reason)
|
|
if err != nil {
|
|
writeError(w, http.StatusConflict, err, status)
|
|
return
|
|
}
|
|
writeJSON(w, http.StatusAccepted, status)
|
|
})
|
|
return s.authorize(mux)
|
|
}
|
|
|
|
func (s *Server) authorize(next http.Handler) http.Handler {
|
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
provided := strings.TrimSpace(strings.TrimPrefix(r.Header.Get("Authorization"), "Bearer "))
|
|
if len(provided) != len(s.token) || subtle.ConstantTimeCompare([]byte(provided), []byte(s.token)) != 1 {
|
|
writeError(w, http.StatusUnauthorized, errors.New("更新器认证失败"), nil)
|
|
return
|
|
}
|
|
w.Header().Set("Cache-Control", "no-store")
|
|
next.ServeHTTP(w, r)
|
|
})
|
|
}
|
|
|
|
func decodeJSON(r *http.Request, target any) error {
|
|
defer r.Body.Close()
|
|
decoder := json.NewDecoder(io.LimitReader(r.Body, 64<<10))
|
|
decoder.DisallowUnknownFields()
|
|
if err := decoder.Decode(target); err != nil {
|
|
return err
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func writeError(w http.ResponseWriter, status int, err error, data any) {
|
|
writeJSONStatus(w, status, map[string]any{"error": err.Error(), "data": data})
|
|
}
|
|
|
|
func writeJSON(w http.ResponseWriter, status int, value any) {
|
|
writeJSONStatus(w, status, value)
|
|
}
|
|
|
|
func writeJSONStatus(w http.ResponseWriter, status int, value any) {
|
|
w.Header().Set("Content-Type", "application/json; charset=utf-8")
|
|
w.WriteHeader(status)
|
|
_ = json.NewEncoder(w).Encode(value)
|
|
}
|