storj/certificate/authorization/endpoint.go
Egon Elbre a129a8bd35 all: separate err check for http
We want to avoid net/http dependency in errs2 package, hence we removed
http.ErrServerClosed from IgnoreCanceled and IsCanceled check. Now we
need to add that check explicitly to every http endpoint.

Change-Id: I62b1cc0a0a2d3b43301d713a7951e5022145f88f
2020-04-16 18:50:24 +03:00

118 lines
2.9 KiB
Go

// Copyright (C) 2019 Storj Labs, Inc.
// See LICENSE for copying information.
package authorization
import (
"context"
"errors"
"fmt"
"net"
"net/http"
"path"
"github.com/zeebo/errs"
"go.uber.org/zap"
"golang.org/x/sync/errgroup"
"storj.io/common/errs2"
)
// ErrEndpoint is the default error class for the authorization endpoint.
var ErrEndpoint = errs.Class("authorization endpoint error")
// Endpoint provides a http endpoint for interacting with an authorization service.
type Endpoint struct {
log *zap.Logger
service *Service
server http.Server
listener net.Listener
}
// NewEndpoint creates a authorization endpoint.
func NewEndpoint(log *zap.Logger, service *Service, listener net.Listener) *Endpoint {
mux := http.NewServeMux()
endpoint := &Endpoint{
log: log,
listener: listener,
service: service,
server: http.Server{
Addr: listener.Addr().String(),
Handler: mux,
},
}
mux.HandleFunc("/v1/authorizations/", endpoint.handleAuthorization)
return endpoint
}
// Run starts the endpoint HTTP server and waits for the context to be
// cancelled or for `Close` to be called.
func (endpoint *Endpoint) Run(ctx context.Context) (err error) {
defer mon.Task()(&ctx)(&err)
ctx, cancel := context.WithCancel(ctx)
var group errgroup.Group
group.Go(func() error {
<-ctx.Done()
return endpoint.server.Shutdown(context.Background())
})
group.Go(func() error {
defer cancel()
err := endpoint.server.Serve(endpoint.listener)
if errs2.IsCanceled(err) || errors.Is(err, http.ErrServerClosed) {
err = nil
}
return err
})
return group.Wait()
}
// Close closes the endpoint HTTP server.
func (endpoint *Endpoint) Close() error {
return endpoint.server.Close()
}
func (endpoint *Endpoint) handleAuthorization(writer http.ResponseWriter, httpReq *http.Request) {
var err error
ctx := httpReq.Context()
mon.Task()(&ctx)(&err)
if httpReq.Method != http.MethodPut {
msg := fmt.Sprintf("unsupported HTTP method: %s", httpReq.Method)
// NB: err set for `mon.Task` call.
err = ErrEndpoint.New("%s", msg)
http.Error(writer, msg, http.StatusMethodNotAllowed)
return
}
userID := path.Base(httpReq.URL.Path)
if userID == "authorizations" || userID == "" {
msg := "missing user ID body"
err = ErrEndpoint.New("%s", msg)
http.Error(writer, msg, http.StatusUnprocessableEntity)
return
}
token, err := endpoint.service.GetOrCreate(ctx, userID)
if err != nil {
msg := "error creating authorization"
err = ErrEndpoint.Wrap(err)
endpoint.log.Error(msg, zap.Error(err))
http.Error(writer, msg, http.StatusInternalServerError)
return
}
writer.WriteHeader(http.StatusCreated)
if _, err = writer.Write([]byte(token.String())); err != nil {
msg := "error writing response"
err = ErrEndpoint.Wrap(err)
endpoint.log.Error(msg, zap.Error(err))
// NB: status cannot be changed and the resource *was* created.
http.Error(writer, msg, http.StatusCreated)
return
}
}