| 1 | /* |
| 2 | Package corehttp provides utilities for the webui, gateways, and other |
| 3 | high-level HTTP interfaces to IPFS. |
| 4 | */ |
| 5 | package corehttp |
| 6 | |
| 7 | import ( |
| 8 | "context" |
| 9 | "fmt" |
| 10 | "net" |
| 11 | "net/http" |
| 12 | "time" |
| 13 | |
| 14 | logging "github.com/ipfs/go-log/v2" |
| 15 | core "github.com/ipfs/kubo/core" |
| 16 | ma "github.com/multiformats/go-multiaddr" |
| 17 | manet "github.com/multiformats/go-multiaddr/net" |
| 18 | ) |
| 19 | |
| 20 | var log = logging.Logger("core/server") |
| 21 | |
| 22 | // shutdownTimeout is the timeout after which we'll stop waiting for hung |
| 23 | // commands to return on shutdown. |
| 24 | const shutdownTimeout = 30 * time.Second |
| 25 | |
| 26 | // ServeOption registers any HTTP handlers it provides on the given mux. |
| 27 | // It returns the mux to expose to future options, which may be a new mux if it |
| 28 | // is interested in mediating requests to future options, or the same mux |
| 29 | // initially passed in if not. |
| 30 | type ServeOption func(*core.IpfsNode, net.Listener, *http.ServeMux) (*http.ServeMux, error) |
| 31 | |
| 32 | // MakeHandler turns a list of ServeOptions into a http.Handler that implements |
| 33 | // all of the given options, in order. |
| 34 | func MakeHandler(n *core.IpfsNode, l net.Listener, options ...ServeOption) (http.Handler, error) { |
| 35 | topMux := http.NewServeMux() |
| 36 | mux := topMux |
| 37 | for _, option := range options { |
| 38 | var err error |
| 39 | mux, err = option(n, l, mux) |
| 40 | if err != nil { |
| 41 | return nil, err |
| 42 | } |
| 43 | } |
| 44 | handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { |
| 45 | // ServeMux does not support requests with CONNECT method, |
| 46 | // so we need to handle them separately |
| 47 | // https://golang.org/src/net/http/request.go#L111 |
| 48 | if r.Method == http.MethodConnect { |
| 49 | w.WriteHeader(http.StatusOK) |
| 50 | return |
| 51 | } |
| 52 | topMux.ServeHTTP(w, r) |
| 53 | }) |
| 54 | return handler, nil |
| 55 | } |
| 56 | |
| 57 | // ListenAndServe runs an HTTP server listening at |listeningMultiAddr| with |
| 58 | // the given serve options. The address must be provided in multiaddr format. |
| 59 | // |
| 60 | // TODO intelligently parse address strings in other formats so long as they |
| 61 | // unambiguously map to a valid multiaddr. e.g. for convenience, ":8080" should |
| 62 | // map to "/ip4/0.0.0.0/tcp/8080". |
| 63 | func ListenAndServe(n *core.IpfsNode, listeningMultiAddr string, options ...ServeOption) error { |
| 64 | addr, err := ma.NewMultiaddr(listeningMultiAddr) |
| 65 | if err != nil { |
| 66 | return err |
| 67 | } |
| 68 | |
| 69 | list, err := manet.Listen(addr) |
| 70 | if err != nil { |
| 71 | return err |
| 72 | } |
| 73 | |
| 74 | // we might have listened to /tcp/0 - let's see what we are listing on |
| 75 | addr = list.Multiaddr() |
| 76 | fmt.Printf("RPC API server listening on %s\n", addr) |
| 77 | |
| 78 | return Serve(n, manet.NetListener(list), options...) |
| 79 | } |
| 80 | |
| 81 | // Serve accepts incoming HTTP connections on the listener and passes them |
| 82 | // to ServeOption handlers. |
| 83 | func Serve(node *core.IpfsNode, lis net.Listener, options ...ServeOption) error { |
| 84 | return ServeWithReady(node, lis, nil, options...) |
| 85 | } |
| 86 | |
| 87 | // ServeWithReady is like Serve but signals on the ready channel when the |
| 88 | // server is about to accept connections. The channel is closed right before |
| 89 | // server.Serve() is called. |
| 90 | // |
| 91 | // This is useful for callers that need to perform actions (like writing |
| 92 | // address files) only after the server is guaranteed to be accepting |
| 93 | // connections, avoiding race conditions where clients see the file before |
| 94 | // the server is ready. |
| 95 | // |
| 96 | // Passing nil for ready is equivalent to calling Serve(). |
| 97 | func ServeWithReady(node *core.IpfsNode, lis net.Listener, ready chan<- struct{}, options ...ServeOption) error { |
| 98 | // make sure we close this no matter what. |
| 99 | defer lis.Close() |
| 100 | |
| 101 | handler, err := MakeHandler(node, lis, options...) |
| 102 | if err != nil { |
| 103 | return err |
| 104 | } |
| 105 | |
| 106 | addr, err := manet.FromNetAddr(lis.Addr()) |
| 107 | if err != nil { |
| 108 | return err |
| 109 | } |
| 110 | |
| 111 | select { |
| 112 | case <-node.Context().Done(): |
| 113 | return fmt.Errorf("failed to start server, process closing") |
| 114 | default: |
| 115 | } |
| 116 | |
| 117 | server := &http.Server{ |
| 118 | Handler: handler, |
| 119 | } |
| 120 | |
| 121 | var serverError error |
| 122 | serverClosed := make(chan struct{}) |
| 123 | go func() { |
| 124 | if ready != nil { |
| 125 | close(ready) |
| 126 | } |
| 127 | serverError = server.Serve(lis) |
| 128 | close(serverClosed) |
| 129 | }() |
| 130 | |
| 131 | // wait for server to exit. |
| 132 | select { |
| 133 | case <-serverClosed: |
| 134 | // if node being closed before server exits, close server |
| 135 | case <-node.Context().Done(): |
| 136 | log.Infof("server at %s terminating...", addr) |
| 137 | |
| 138 | go func() { |
| 139 | ticker := time.NewTicker(5 * time.Second) |
| 140 | defer ticker.Stop() |
| 141 | for { |
| 142 | select { |
| 143 | case <-ticker.C: |
| 144 | log.Infof("waiting for server at %s to terminate...", addr) |
| 145 | case <-serverClosed: |
| 146 | return |
| 147 | } |
| 148 | } |
| 149 | }() |
| 150 | |
| 151 | // This timeout shouldn't be necessary if all of our commands |
| 152 | // are obeying their contexts but we should have *some* timeout. |
| 153 | ctx, cancel := context.WithTimeout(context.Background(), shutdownTimeout) |
| 154 | defer cancel() |
| 155 | err := server.Shutdown(ctx) |
| 156 | |
| 157 | // Should have already closed but we still need to wait for it |
| 158 | // to set the error. |
| 159 | <-serverClosed |
| 160 | serverError = err |
| 161 | } |
| 162 | |
| 163 | log.Infof("server at %s terminated", addr) |
| 164 | return serverError |
| 165 | } |