From 4f01edb9230f58ff84b0dd892c931ec8ac9aad55 Mon Sep 17 00:00:00 2001 From: Brian Picciano Date: Tue, 13 Sep 2022 12:56:08 +0200 Subject: move src out of srv, clean up default.nix and Makefile --- srv/src/http/api.go | 272 ---------------------------------------------------- 1 file changed, 272 deletions(-) delete mode 100644 srv/src/http/api.go (limited to 'srv/src/http/api.go') diff --git a/srv/src/http/api.go b/srv/src/http/api.go deleted file mode 100644 index 01cad50..0000000 --- a/srv/src/http/api.go +++ /dev/null @@ -1,272 +0,0 @@ -// Package api implements the HTTP-based api for the mediocre-blog. -package http - -import ( - "context" - "embed" - "encoding/json" - "errors" - "fmt" - "html/template" - "net" - "net/http" - "net/url" - "os" - "strings" - "time" - - lru "github.com/hashicorp/golang-lru" - "github.com/mediocregopher/blog.mediocregopher.com/srv/cfg" - "github.com/mediocregopher/blog.mediocregopher.com/srv/http/apiutil" - "github.com/mediocregopher/blog.mediocregopher.com/srv/mailinglist" - "github.com/mediocregopher/blog.mediocregopher.com/srv/post" - "github.com/mediocregopher/blog.mediocregopher.com/srv/pow" - "github.com/mediocregopher/mediocre-go-lib/v2/mctx" - "github.com/mediocregopher/mediocre-go-lib/v2/mlog" -) - -//go:embed static -var staticFS embed.FS - -// Params are used to instantiate a new API instance. All fields are required -// unless otherwise noted. -type Params struct { - Logger *mlog.Logger - PowManager pow.Manager - - PostStore post.Store - PostAssetStore post.AssetStore - PostDraftStore post.DraftStore - - MailingList mailinglist.MailingList - - // PublicURL is the base URL which site visitors can navigate to. - PublicURL *url.URL - - // ListenProto and ListenAddr are passed into net.Listen to create the - // API's listener. Both "tcp" and "unix" protocols are explicitly - // supported. - ListenProto, ListenAddr string - - // AuthUsers keys are usernames which are allowed to edit server-side data, - // and the values are the password hash which accompanies those users. The - // password hash must have been produced by NewPasswordHash. - AuthUsers map[string]string - - // AuthRatelimit indicates how much time must pass between subsequent auth - // attempts. - AuthRatelimit time.Duration -} - -// SetupCfg implement the cfg.Cfger interface. -func (p *Params) SetupCfg(cfg *cfg.Cfg) { - - publicURLStr := cfg.String("http-public-url", "http://localhost:4000", "URL this service is accessible at") - - cfg.StringVar(&p.ListenProto, "http-listen-proto", "tcp", "Protocol to listen for HTTP requests with") - cfg.StringVar(&p.ListenAddr, "http-listen-addr", ":4000", "Address/path to listen for HTTP requests on") - - httpAuthUsersStr := cfg.String("http-auth-users", "{}", "JSON object with usernames as values and password hashes (produced by the hash-password binary) as values. Denotes users which are able to edit server-side data") - - httpAuthRatelimitStr := cfg.String("http-auth-ratelimit", "5s", "Minimum duration which must be waited between subsequent auth attempts") - - cfg.OnInit(func(context.Context) error { - - err := json.Unmarshal([]byte(*httpAuthUsersStr), &p.AuthUsers) - - if err != nil { - return fmt.Errorf("unmarshaling -http-auth-users: %w", err) - } - - if p.AuthRatelimit, err = time.ParseDuration(*httpAuthRatelimitStr); err != nil { - return fmt.Errorf("unmarshaling -http-auth-ratelimit: %w", err) - } - - *publicURLStr = strings.TrimSuffix(*publicURLStr, "/") - if p.PublicURL, err = url.Parse(*publicURLStr); err != nil { - return fmt.Errorf("parsing -http-public-url: %w", err) - } - - return nil - }) -} - -// Annotate implements mctx.Annotator interface. -func (p *Params) Annotate(a mctx.Annotations) { - a["httpPublicURL"] = p.PublicURL - a["httpListenProto"] = p.ListenProto - a["httpListenAddr"] = p.ListenAddr - a["httpAuthRatelimit"] = p.AuthRatelimit -} - -// API will listen on the port configured for it, and serve HTTP requests for -// the mediocre-blog. -type API interface { - Shutdown(ctx context.Context) error -} - -type api struct { - params Params - srv *http.Server - - redirectTpl *template.Template - auther Auther -} - -// New initializes and returns a new API instance, including setting up all -// listening ports. -func New(params Params) (API, error) { - - l, err := net.Listen(params.ListenProto, params.ListenAddr) - if err != nil { - return nil, fmt.Errorf("creating listen socket: %w", err) - } - - if params.ListenProto == "unix" { - if err := os.Chmod(params.ListenAddr, 0777); err != nil { - return nil, fmt.Errorf("chmod-ing unix socket: %w", err) - } - } - - a := &api{ - params: params, - auther: NewAuther(params.AuthUsers, params.AuthRatelimit), - } - - a.redirectTpl = a.mustParseTpl("redirect.html") - - a.srv = &http.Server{Handler: a.handler()} - - go func() { - - err := a.srv.Serve(l) - if err != nil && !errors.Is(err, http.ErrServerClosed) { - ctx := mctx.Annotate(context.Background(), a.params) - params.Logger.Fatal(ctx, "serving http server", err) - } - }() - - return a, nil -} - -func (a *api) Shutdown(ctx context.Context) error { - defer a.auther.Close() - if err := a.srv.Shutdown(ctx); err != nil { - return err - } - - return nil -} - -func (a *api) apiHandler() http.Handler { - mux := http.NewServeMux() - - mux.Handle("/pow/challenge", a.newPowChallengeHandler()) - mux.Handle("/pow/check", - a.requirePowMiddleware( - http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {}), - ), - ) - - mux.Handle("/mailinglist/subscribe", a.requirePowMiddleware(a.mailingListSubscribeHandler())) - mux.Handle("/mailinglist/finalize", a.mailingListFinalizeHandler()) - mux.Handle("/mailinglist/unsubscribe", a.mailingListUnsubscribeHandler()) - - return apiutil.MethodMux(map[string]http.Handler{ - "POST": mux, - }) -} - -func (a *api) blogHandler() http.Handler { - - cache, err := lru.New(5000) - - // instantiating the lru cache can't realistically fail - if err != nil { - panic(err) - } - - mux := http.NewServeMux() - - mux.Handle("/posts/", http.StripPrefix("/posts", - apiutil.MethodMux(map[string]http.Handler{ - "GET": a.renderPostHandler(), - "POST": a.postPostHandler(), - "DELETE": a.deletePostHandler(false), - "PREVIEW": a.previewPostHandler(), - }), - )) - - mux.Handle("/assets/", http.StripPrefix("/assets", - apiutil.MethodMux(map[string]http.Handler{ - "GET": a.getPostAssetHandler(), - "POST": a.postPostAssetHandler(), - "DELETE": a.deletePostAssetHandler(), - }), - )) - - mux.Handle("/drafts/", http.StripPrefix("/drafts", - - // everything to do with drafts is protected - authMiddleware(a.auther)( - - apiutil.MethodMux(map[string]http.Handler{ - "GET": a.renderDraftPostHandler(), - "POST": a.postDraftPostHandler(), - "DELETE": a.deletePostHandler(true), - "PREVIEW": a.previewPostHandler(), - }), - ), - )) - - mux.Handle("/static/", http.FileServer(http.FS(staticFS))) - mux.Handle("/follow", a.renderDumbTplHandler("follow.html")) - mux.Handle("/admin", a.renderDumbTplHandler("admin.html")) - mux.Handle("/mailinglist/unsubscribe", a.renderDumbTplHandler("unsubscribe.html")) - mux.Handle("/mailinglist/finalize", a.renderDumbTplHandler("finalize.html")) - mux.Handle("/feed.xml", a.renderFeedHandler()) - mux.Handle("/", a.renderIndexHandler()) - - h := apiutil.MethodMux(map[string]http.Handler{ - "GET": applyMiddlewares( - mux, - logReqMiddleware, // only log GETs on cache miss - cacheMiddleware(cache), - ), - "*": applyMiddlewares( - mux, - purgeCacheOnOKMiddleware(cache), - authMiddleware(a.auther), - ), - }) - - return h -} - -func (a *api) handler() http.Handler { - - mux := http.NewServeMux() - - mux.Handle("/api/", http.StripPrefix("/api", a.apiHandler())) - mux.Handle("/", a.blogHandler()) - - h := apiutil.MethodMux(map[string]http.Handler{ - "GET": applyMiddlewares( - mux, - ), - "*": applyMiddlewares( - mux, - a.checkCSRFMiddleware, - addResponseHeadersMiddleware(map[string]string{ - "Cache-Control": "no-store, max-age=0", - "Pragma": "no-cache", - "Expires": "0", - }), - logReqMiddleware, - ), - }) - - h = setLoggerMiddleware(a.params.Logger)(h) - - return h -} -- cgit v1.2.3