master
go 165 lines 4.65 KB
Raw
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 }