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 }