summaryrefslogtreecommitdiff
path: root/src/http/api.go
diff options
context:
space:
mode:
Diffstat (limited to 'src/http/api.go')
-rw-r--r--src/http/api.go286
1 files changed, 0 insertions, 286 deletions
diff --git a/src/http/api.go b/src/http/api.go
deleted file mode 100644
index ffe0f25..0000000
--- a/src/http/api.go
+++ /dev/null
@@ -1,286 +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.getPostsHandler(),
- "EDIT": a.editPostHandler(false),
- "MANAGE": a.managePostsHandler(),
- "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(),
- "MANAGE": a.managePostAssetsHandler(),
- "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{
- "EDIT": a.editPostHandler(true),
- "MANAGE": a.manageDraftPostsHandler(),
- "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())
-
- readOnlyMiddlewares := []middleware{
- logReqMiddleware, // only log GETs on cache miss
- cacheMiddleware(cache),
- }
-
- readWriteMiddlewares := []middleware{
- purgeCacheOnOKMiddleware(cache),
- authMiddleware(a.auther),
- }
-
- h := apiutil.MethodMux(map[string]http.Handler{
- "GET": applyMiddlewares(mux, readOnlyMiddlewares...),
- "MANAGE": applyMiddlewares(mux, readOnlyMiddlewares...),
- "EDIT": applyMiddlewares(mux, readOnlyMiddlewares...),
- "*": applyMiddlewares(mux, readWriteMiddlewares...),
- })
-
- return h
-}
-
-func (a *api) handler() http.Handler {
-
- mux := http.NewServeMux()
-
- mux.Handle("/api/", applyMiddlewares(
- http.StripPrefix("/api", a.apiHandler()),
- logReqMiddleware,
- ))
-
- mux.Handle("/", a.blogHandler())
-
- noCacheMiddleware := addResponseHeadersMiddleware(map[string]string{
- "Cache-Control": "no-store, max-age=0",
- "Pragma": "no-cache",
- "Expires": "0",
- })
-
- h := applyMiddlewares(
- apiutil.MethodMux(map[string]http.Handler{
- "GET": applyMiddlewares(mux),
- "MANAGE": applyMiddlewares(mux, noCacheMiddleware),
- "EDIT": applyMiddlewares(mux, noCacheMiddleware),
- "*": applyMiddlewares(
- mux,
- a.checkCSRFMiddleware,
- noCacheMiddleware,
- ),
- }),
- setLoggerMiddleware(a.params.Logger),
- )
-
- return h
-}