diff --git a/gateway/FEATURES.md b/gateway/handlers/features/FEATURES.md similarity index 100% rename from gateway/FEATURES.md rename to gateway/handlers/features/FEATURES.md diff --git a/gateway/handlers/features/features.go b/gateway/handlers/features/features.go new file mode 100644 index 00000000..eadf4f63 --- /dev/null +++ b/gateway/handlers/features/features.go @@ -0,0 +1,66 @@ +package features + +import ( + "bytes" + "context" + _ "embed" + "errors" + "net/http" + "regexp" + "sync" + "time" + + "github.com/movsb/taoblog/modules/utils" + "github.com/movsb/taoblog/service/modules/renderers/plantuml" +) + +//go:embed FEATURES.md +var featuresMd []byte +var featuresTime = time.Now() + +var ( + light []byte + dark []byte + lock sync.RWMutex +) + +func New() http.Handler { + return http.HandlerFunc(features) +} + +func features(w http.ResponseWriter, r *http.Request) { + lock.Lock() + if len(light) == 0 || len(dark) == 0 { + if err := prepare(); err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + lock.Unlock() + return + } + } + lock.Unlock() + + content := utils.IIF(r.PathValue(`theme`) == `light`, light, dark) + w.Header().Add(`Content-Type`, `image/svg+xml`) + http.ServeContent(w, r, `features.svg`, featuresTime, bytes.NewReader(content)) +} + +func prepare() error { + reFeaturesPlantUML := regexp.MustCompile("```plantuml((?sU).+)```") + matches := reFeaturesPlantUML.FindSubmatch(featuresMd) + if len(matches) != 2 { + return errors.New(`no features found`) + } + compressed, err := plantuml.Compress(matches[1]) + if err != nil { + return err + } + light, err = plantuml.Fetch(context.Background(), `https://www.plantuml.com/plantuml`, `svg`, compressed, false) + if err != nil { + return err + } + dark, err = plantuml.Fetch(context.Background(), `https://www.plantuml.com/plantuml`, `svg`, compressed, true) + if err != nil { + return err + } + return nil +} diff --git a/gateway/main.go b/gateway/main.go index ae1abdb7..f06439bc 100644 --- a/gateway/main.go +++ b/gateway/main.go @@ -1,20 +1,19 @@ package gateway import ( - "bytes" "context" _ "embed" "encoding/json" "io" "net/http" "net/url" - "regexp" "strconv" "time" "github.com/grpc-ecosystem/grpc-gateway/v2/runtime" "github.com/movsb/taoblog/gateway/handlers/apidoc" "github.com/movsb/taoblog/gateway/handlers/assets" + "github.com/movsb/taoblog/gateway/handlers/features" "github.com/movsb/taoblog/gateway/handlers/robots" "github.com/movsb/taoblog/gateway/handlers/rss" "github.com/movsb/taoblog/gateway/handlers/sitemap" @@ -26,7 +25,6 @@ import ( "github.com/movsb/taoblog/protocols/go/proto" "github.com/movsb/taoblog/service" dynamic "github.com/movsb/taoblog/service/modules/renderers/_dynamic" - "github.com/movsb/taoblog/service/modules/renderers/plantuml" "github.com/movsb/taoblog/service/modules/webhooks" "github.com/movsb/taoblog/theme/modules/handle304" "google.golang.org/grpc" @@ -34,10 +32,6 @@ import ( "google.golang.org/protobuf/encoding/protojson" ) -//go:embed FEATURES.md -var featuresMd []byte -var featuresTime = time.Now() - type Gateway struct { mux *http.ServeMux service *service.Service @@ -91,8 +85,6 @@ func (g *Gateway) register(ctx context.Context, mux *http.ServeMux, mux2 *runtim proto.RegisterTaoBlogHandlerFromEndpoint(ctx, mux2, g.service.GrpcAddress(), dialOptions) proto.RegisterSearchHandlerFromEndpoint(ctx, mux2, g.service.GrpcAddress(), dialOptions) - mux2.HandlePath(`GET`, `/v3/features/{theme}`, features) - mux2.HandlePath(`GET`, `/v3/avatar/{id}`, g.getAvatar) mux2.HandlePath(`POST`, `/v3/webhooks/github`, g.githubWebhook()) @@ -102,6 +94,9 @@ func (g *Gateway) register(ctx context.Context, mux *http.ServeMux, mux2 *runtim info := utils.Must1(g.service.GetInfo(ctx, &proto.GetInfoRequest{})) + // 博客功能集 + mc.Handle(`GET /v3/features/{theme}`, features.New()) + // API 文档 mc.Handle(`GET /v3/api/`, http.StripPrefix(`/v3/api`, apidoc.New())) @@ -192,28 +187,3 @@ func (g *Gateway) getAvatar(w http.ResponseWriter, req *http.Request, params map } g.service.GetAvatar(in) } - -var reFeaturesPlantUML = regexp.MustCompile("```plantuml((?sU).+)```") - -func features(w http.ResponseWriter, r *http.Request, params map[string]string) { - matches := reFeaturesPlantUML.FindSubmatch(featuresMd) - if len(matches) == 2 { - compressed, err := plantuml.Compress(matches[1]) - if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - return - } - darkMode := false - if params[`theme`] == `dark` { - darkMode = true - } - content, err := plantuml.Fetch(r.Context(), `https://www.plantuml.com/plantuml`, `svg`, compressed, darkMode) - if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - return - } - w.Header().Add(`Content-Type`, `image/svg+xml`) - http.ServeContent(w, r, `features.svg`, featuresTime, bytes.NewReader(content)) - return - } -}