github.com/ipni/storetheindex@v0.8.30/assigner/server/server.go (about)

     1  package server
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"net"
     7  	"net/http"
     8  
     9  	logging "github.com/ipfs/go-log/v2"
    10  	"github.com/ipni/go-libipni/announce/message"
    11  	"github.com/ipni/storetheindex/assigner/core"
    12  	"github.com/libp2p/go-libp2p/core/peer"
    13  )
    14  
    15  var log = logging.Logger("assigner/server")
    16  
    17  type Server struct {
    18  	assigner  *core.Assigner
    19  	server    *http.Server
    20  	listener  net.Listener
    21  	healthMsg string
    22  }
    23  
    24  func New(listen string, assigner *core.Assigner, options ...Option) (*Server, error) {
    25  	opts, err := getOpts(options)
    26  	if err != nil {
    27  		return nil, err
    28  	}
    29  
    30  	l, err := net.Listen("tcp", listen)
    31  	if err != nil {
    32  		return nil, err
    33  	}
    34  
    35  	mux := http.NewServeMux()
    36  	server := &http.Server{
    37  		Handler:      mux,
    38  		WriteTimeout: opts.writeTimeout,
    39  		ReadTimeout:  opts.readTimeout,
    40  	}
    41  	s := &Server{
    42  		assigner: assigner,
    43  		server:   server,
    44  		listener: l,
    45  	}
    46  
    47  	s.healthMsg = "assigner ready"
    48  	if opts.version != "" {
    49  		s.healthMsg += " " + opts.version
    50  	}
    51  
    52  	// Direct announce.
    53  	mux.HandleFunc("/announce", s.announce)
    54  	// Health check.
    55  	mux.HandleFunc("/health", s.health)
    56  
    57  	// Depricated
    58  	mux.HandleFunc("/ingest/announce", s.announce)
    59  
    60  	return s, nil
    61  }
    62  
    63  func (s *Server) URL() string {
    64  	return fmt.Sprint("http://", s.listener.Addr().String())
    65  }
    66  
    67  func (s *Server) Start() error {
    68  	log.Infow("http server listening", "listen_addr", s.listener.Addr())
    69  	return s.server.Serve(s.listener)
    70  }
    71  
    72  func (s *Server) Close() error {
    73  	log.Info("http server shutdown")
    74  	return s.server.Shutdown(context.Background())
    75  }
    76  
    77  // PUT /announce
    78  func (s *Server) announce(w http.ResponseWriter, r *http.Request) {
    79  	if !methodOK(w, r, http.MethodPut) {
    80  		return
    81  	}
    82  
    83  	w.Header().Set("Content-Type", "application/json")
    84  	defer r.Body.Close()
    85  
    86  	an := message.Message{}
    87  	if err := an.UnmarshalCBOR(r.Body); err != nil {
    88  		http.Error(w, err.Error(), http.StatusBadRequest)
    89  		return
    90  	}
    91  	if len(an.Addrs) == 0 {
    92  		http.Error(w, "must specify location to fetch on direct announcments", http.StatusBadRequest)
    93  		return
    94  	}
    95  	addrs, err := an.GetAddrs()
    96  	if err != nil {
    97  		err = fmt.Errorf("could not decode addrs from announce message: %s", err)
    98  		http.Error(w, err.Error(), http.StatusBadRequest)
    99  		return
   100  	}
   101  
   102  	ais, err := peer.AddrInfosFromP2pAddrs(addrs...)
   103  	if err != nil {
   104  		http.Error(w, err.Error(), http.StatusBadRequest)
   105  		return
   106  	}
   107  	if len(ais) > 1 {
   108  		http.Error(w, "peer id must be the same for all addresses", http.StatusBadRequest)
   109  		return
   110  	}
   111  	addrInfo := ais[0]
   112  
   113  	if !s.assigner.Allowed(addrInfo.ID) {
   114  		http.Error(w, "announce requests not allowed from peer", http.StatusForbidden)
   115  		return
   116  	}
   117  
   118  	// Use background context because this will be an async process. We don't
   119  	// want to attach the context to the request context that started this.
   120  	err = s.assigner.Announce(context.Background(), an.Cid, addrInfo)
   121  	if err != nil {
   122  		http.Error(w, err.Error(), http.StatusInternalServerError)
   123  		return
   124  	}
   125  	w.WriteHeader(http.StatusNoContent)
   126  }
   127  
   128  func (s *Server) health(w http.ResponseWriter, r *http.Request) {
   129  	if !methodOK(w, r, http.MethodGet) {
   130  		return
   131  	}
   132  	w.Header().Set("Cache-Control", "no-cache")
   133  	http.Error(w, s.healthMsg, http.StatusOK)
   134  }
   135  
   136  func methodOK(w http.ResponseWriter, r *http.Request, method string) bool {
   137  	if r.Method != method {
   138  		w.Header().Set("Allow", method)
   139  		http.Error(w, "", http.StatusMethodNotAllowed)
   140  		return false
   141  	}
   142  	return true
   143  }