-
Notifications
You must be signed in to change notification settings - Fork 70
/
server.go
156 lines (132 loc) · 4.47 KB
/
server.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
package main
import (
"compress/gzip"
"context"
"encoding/json"
"errors"
"fmt"
"io"
"net"
"net/http"
"time"
"github.com/filecoin-project/boost-graphsync/storeutil"
"github.com/filecoin-project/boost/extern/boostd-data/model"
"github.com/filecoin-project/boost/metrics"
"github.com/filecoin-project/dagstore/mount"
"github.com/filecoin-project/go-address"
"github.com/filecoin-project/go-state-types/abi"
"github.com/ipfs/boxo/blockstore"
"github.com/ipfs/go-cid"
"github.com/ipld/frisbii"
"github.com/rs/cors"
)
//go:generate go run github.com/golang/mock/mockgen -destination=mocks/mock_booster_http.go -package=mocks_booster_http -source=server.go HttpServerApi,serverApi
var ErrNotFound = errors.New("not found")
// For data served by the endpoints in the HTTP server that never changes
// (eg pieces identified by a piece CID) send a cache header with a constant,
// non-zero last modified time.
var lastModified = time.UnixMilli(1)
type apiVersion struct {
Version string `json:"Version"`
}
type HttpServer struct {
path string
listenAddr string
port int
api HttpServerApi
opts HttpServerOptions
idxPage string
ctx context.Context
cancel context.CancelFunc
server *http.Server
}
type HttpServerApi interface {
GetPieceDeals(ctx context.Context, pieceCID cid.Cid) ([]model.DealInfo, error)
IsUnsealed(ctx context.Context, minerAddr address.Address, sectorID abi.SectorNumber, offset abi.UnpaddedPieceSize, length abi.UnpaddedPieceSize) (bool, error)
UnsealSectorAt(ctx context.Context, minerAddr address.Address, sectorID abi.SectorNumber, pieceOffset abi.UnpaddedPieceSize, length abi.UnpaddedPieceSize) (mount.Reader, error)
}
type HttpServerOptions struct {
Blockstore blockstore.Blockstore
ServePieces bool
ServeTrustless bool
CompressionLevel int
LogWriter io.Writer // for a standardised log write format
LogHandler frisbii.LogHandler // for more granular control over log output
}
func NewHttpServer(path string, listenAddr string, port int, api HttpServerApi, opts *HttpServerOptions) *HttpServer {
if opts == nil {
opts = &HttpServerOptions{ServePieces: true, ServeTrustless: false, CompressionLevel: gzip.NoCompression}
}
return &HttpServer{path: path, listenAddr: listenAddr, port: port, api: api, opts: *opts, idxPage: parseTemplate(*opts)}
}
func (s *HttpServer) pieceBasePath() string {
return s.path + "/piece/"
}
func (s *HttpServer) ipfsBasePath() string {
return s.path + "/ipfs/"
}
func newCors() *cors.Cors {
options := cors.Options{
AllowedHeaders: []string{"*"},
}
c := cors.New(options)
return c
}
func (s *HttpServer) Start(ctx context.Context) error {
if !s.opts.ServePieces && !s.opts.ServeTrustless {
return errors.New("no content to serve")
}
s.ctx, s.cancel = context.WithCancel(ctx)
c := newCors()
handler := http.NewServeMux()
if s.opts.ServePieces {
handler.HandleFunc(s.pieceBasePath(), s.pieceHandler())
}
if s.opts.ServeTrustless {
if s.opts.Blockstore == nil {
return errors.New("no blockstore provided for trustless gateway")
}
lsys := storeutil.LinkSystemForBlockstore(s.opts.Blockstore)
handler.Handle(
s.ipfsBasePath(),
frisbii.NewHttpIpfs(ctx, lsys, frisbii.WithCompressionLevel(s.opts.CompressionLevel)),
)
}
handler.HandleFunc("/", s.handleIndex)
handler.HandleFunc("/index.html", s.handleIndex)
handler.HandleFunc("/info", s.handleInfo)
handler.Handle("/metrics", metrics.Exporter("booster_http")) // metrics
s.server = &http.Server{
Addr: fmt.Sprintf("%s:%d", s.listenAddr, s.port),
Handler: c.Handler(
frisbii.NewLogMiddleware(handler, frisbii.WithLogWriter(s.opts.LogWriter), frisbii.WithLogHandler(s.opts.LogHandler)),
),
// This context will be the parent of the context associated with all
// incoming requests
BaseContext: func(listener net.Listener) context.Context {
return s.ctx
},
}
go func() {
if err := s.server.ListenAndServe(); err != http.ErrServerClosed {
log.Fatalf("http.ListenAndServe(): %v", err.Error())
}
}()
return nil
}
func (s *HttpServer) Stop() error {
s.cancel()
return s.server.Close()
}
func (s *HttpServer) handleIndex(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK)
w.Write([]byte(s.idxPage)) //nolint:errcheck
}
func (s *HttpServer) handleInfo(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusOK)
v := apiVersion{
Version: "0.3.0",
}
json.NewEncoder(w).Encode(v) //nolint:errcheck
}