github.com/storacha/go-ucanto@v0.7.2/server/retrieval/server.go (about) 1 package retrieval 2 3 import ( 4 "bytes" 5 "context" 6 "errors" 7 "fmt" 8 "io" 9 "net/http" 10 "net/url" 11 "strings" 12 "time" 13 14 ipldprime "github.com/ipld/go-ipld-prime" 15 "github.com/ipld/go-ipld-prime/codec/dagjson" 16 "github.com/storacha/go-ucanto/core/dag/blockstore" 17 "github.com/storacha/go-ucanto/core/delegation" 18 "github.com/storacha/go-ucanto/core/invocation" 19 "github.com/storacha/go-ucanto/core/ipld" 20 "github.com/storacha/go-ucanto/core/message" 21 "github.com/storacha/go-ucanto/core/receipt" 22 "github.com/storacha/go-ucanto/core/receipt/ran" 23 "github.com/storacha/go-ucanto/core/result" 24 "github.com/storacha/go-ucanto/core/result/failure" 25 "github.com/storacha/go-ucanto/principal" 26 "github.com/storacha/go-ucanto/server" 27 "github.com/storacha/go-ucanto/server/transaction" 28 "github.com/storacha/go-ucanto/transport" 29 "github.com/storacha/go-ucanto/transport/headercar" 30 hcmsg "github.com/storacha/go-ucanto/transport/headercar/message" 31 thttp "github.com/storacha/go-ucanto/transport/http" 32 "github.com/storacha/go-ucanto/ucan" 33 ) 34 35 type Request struct { 36 // Relative URL requested. 37 URL *url.URL 38 // Headers are the HTTP headers sent in the HTTP request to the server. 39 Headers http.Header 40 } 41 42 type Response struct { 43 // Status is the HTTP status that should be returned. e.g. 206 when returning 44 // a range request. 45 Status int 46 // Headers are additional HTTP headers to return in the response. At minimum 47 // they should include the Content-Length header, but should also include 48 // Content-Range for byte range responses. 49 Headers http.Header 50 // Body is the data to return in the response body. 51 Body io.ReadCloser 52 } 53 54 func NewResponse(status int, headers http.Header, body io.ReadCloser) Response { 55 return Response{Status: status, Headers: headers, Body: body} 56 } 57 58 // ServiceMethod is an invocation handler. It is different to 59 // [server.ServiceMethod] in that it allows an [Response] to be 60 // returned as part of the [transation.Transation], which for a retrieval server 61 // will determine the HTTP headers and body content of the HTTP response. The 62 // usual handler response (out and effects) are added to the X-Agent-Message 63 // HTTP header. 64 type ServiceMethod[O ipld.Builder, X failure.IPLDBuilderFailure] func( 65 context.Context, 66 invocation.Invocation, 67 server.InvocationContext, 68 Request, 69 ) (transaction.Transaction[O, X], Response, error) 70 71 // Service is a mapping of service names to handlers, used to define a 72 // service implementation. 73 type Service = map[ucan.Ability]ServiceMethod[ipld.Builder, failure.IPLDBuilderFailure] 74 75 // CachingServer is a retrieval server that also caches invocations/delegations 76 // to allow invocations with delegations chains bigger than HTTP header size 77 // limits to be executed as multiple requests. 78 type CachingServer interface { 79 server.Server[Service] 80 Cache() delegation.Store 81 } 82 83 // NewServer creates a retrieval server, which is a UCAN server that comes 84 // pre-loaded with a [headercar] codec. 85 // 86 // Handlers have an additional return value - the data to return in the body 87 // of the response as well as HTTP headers and status code. They also have an 88 // additional parameter, which are the details of the request - the URL that was 89 // requested and the HTTP headers. 90 // 91 // They require a delegation cache, which allows delegations that are too big 92 // for the header to be sent in multiple rounds. By default an in-memory cache 93 // is provided if none is passed in options. 94 // 95 // The carheader codec will accept agent messages where the invocation is a CID 96 // that can be looked up in the delegations cache. 97 // 98 // The delegations cache should be a size bounded LRU to prevent DoS attacks. 99 func NewServer(id principal.Signer, options ...Option) (*Server, error) { 100 cfg := srvConfig{service: Service{}} 101 for _, opt := range options { 102 if err := opt(&cfg); err != nil { 103 return nil, err 104 } 105 } 106 107 dlgCache := cfg.delegationCache 108 if dlgCache == nil { 109 dc, err := NewMemoryDelegationCache(MemoryDelegationCacheSize) 110 if err != nil { 111 return nil, err 112 } 113 dlgCache = dc 114 } 115 116 codec := headercar.NewInboundCodec() 117 srvOpts := []server.Option{server.WithInboundCodec(codec)} 118 if cfg.canIssue != nil { 119 srvOpts = append(srvOpts, server.WithCanIssue(cfg.canIssue)) 120 } 121 if cfg.catch != nil { 122 srvOpts = append(srvOpts, server.WithErrorHandler(cfg.catch)) 123 } 124 if cfg.logReceipt != nil { 125 srvOpts = append(srvOpts, server.WithReceiptLogger(cfg.logReceipt)) 126 } 127 if cfg.validateAuthorization != nil { 128 srvOpts = append(srvOpts, server.WithRevocationChecker(cfg.validateAuthorization)) 129 } 130 if cfg.resolveProof != nil { 131 srvOpts = append(srvOpts, server.WithProofResolver(cfg.resolveProof)) 132 } 133 if cfg.parsePrincipal != nil { 134 srvOpts = append(srvOpts, server.WithPrincipalParser(cfg.parsePrincipal)) 135 } 136 if cfg.resolveDIDKey != nil { 137 srvOpts = append(srvOpts, server.WithPrincipalResolver(cfg.resolveDIDKey)) 138 } 139 if len(cfg.authorityProofs) > 0 { 140 srvOpts = append(srvOpts, server.WithAuthorityProofs(cfg.authorityProofs...)) 141 } 142 143 srv, err := server.NewServer(id, srvOpts...) 144 if err != nil { 145 return nil, fmt.Errorf("creating server: %w", err) 146 } 147 148 return &Server{ 149 server: srv, 150 service: cfg.service, 151 delegationCache: dlgCache, 152 }, nil 153 } 154 155 type Server struct { 156 server server.Server[server.Service] 157 service Service 158 delegationCache delegation.Store 159 } 160 161 func (srv *Server) ID() principal.Signer { 162 return srv.server.ID() 163 } 164 165 func (srv *Server) Service() Service { 166 return srv.service 167 } 168 169 func (srv *Server) Context() server.InvocationContext { 170 return srv.server.Context() 171 } 172 173 func (srv *Server) Codec() transport.InboundCodec { 174 return srv.server.Codec() 175 } 176 177 // Request handles an inbound HTTP request to the retrieval server. The request 178 // URL will only be non-empty if this method is called with a request that is a 179 // [transport.InboundHTTPRequest]. 180 func (srv *Server) Request(ctx context.Context, request transport.HTTPRequest) (transport.HTTPResponse, error) { 181 return Handle(ctx, srv, request) 182 } 183 184 func (srv *Server) Run(ctx context.Context, invocation server.ServiceInvocation) (receipt.AnyReceipt, error) { 185 rcpt, _, err := Run(ctx, srv, invocation, Request{}) 186 return rcpt, err 187 } 188 189 func (srv *Server) Cache() delegation.Store { 190 return srv.delegationCache 191 } 192 193 func (srv *Server) Catch(err server.HandlerExecutionError[any]) { 194 srv.server.Catch(err) 195 } 196 197 func (srv *Server) LogReceipt(ctx context.Context, rcpt receipt.AnyReceipt, inv invocation.Invocation) error { 198 return srv.server.LogReceipt(ctx, rcpt, inv) 199 } 200 201 var _ CachingServer = (*Server)(nil) 202 203 func Handle(ctx context.Context, srv CachingServer, request transport.HTTPRequest) (transport.HTTPResponse, error) { 204 resp, err := handle(ctx, srv, request) 205 if err != nil { 206 return nil, err 207 } 208 209 // ensure headers are not nil 210 headers := resp.Headers() 211 if headers == nil { 212 headers = http.Header{} 213 } else { 214 headers = headers.Clone() 215 } 216 // ensure the Vary header is set for ALL responses 217 // https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Vary 218 headers.Add("Vary", hcmsg.HeaderName) 219 220 if resp.Body() == nil { 221 return thttp.NewResponse(resp.Status(), http.NoBody, headers), nil 222 } 223 return thttp.NewResponse(resp.Status(), resp.Body(), headers), nil 224 } 225 226 func handle(ctx context.Context, srv CachingServer, request transport.HTTPRequest) (transport.HTTPResponse, error) { 227 selection, aerr := srv.Codec().Accept(request) 228 if aerr != nil { 229 return thttp.NewResponse(aerr.Status(), io.NopCloser(strings.NewReader(aerr.Error())), aerr.Headers()), nil 230 } 231 232 msg, err := selection.Decoder().Decode(request) 233 if err != nil { 234 msg := fmt.Sprintf("The server failed to decode the request payload. Please format the payload according to the specified media type: %s", err.Error()) 235 return thttp.NewResponse(http.StatusBadRequest, io.NopCloser(strings.NewReader(msg)), nil), nil 236 } 237 238 // retrieval server supports only 1 invocation in the agent message, since 239 // only a single handler can use the body. 240 invs := msg.Invocations() 241 if len(invs) != 1 { 242 var rcpts []receipt.AnyReceipt 243 res := result.NewFailure(NewAgentMessageInvocationCountError()) 244 for _, l := range invs { 245 rcpt, err := receipt.Issue(srv.ID(), res, ran.FromLink(l)) 246 if err != nil { 247 return nil, fmt.Errorf("issuing invocation error receipt: %w", err) 248 } 249 rcpts = append(rcpts, rcpt) 250 } 251 out, err := message.Build(nil, rcpts) 252 if err != nil { 253 return nil, fmt.Errorf("building invocation error message: %w", err) 254 } 255 resp, err := selection.Encoder().Encode(out) 256 if err != nil { 257 return nil, fmt.Errorf("encoding invocation error message: %w", err) 258 } 259 return resp, nil 260 } 261 262 retreq := Request{Headers: request.Headers()} 263 if inreq, ok := request.(transport.InboundHTTPRequest); ok { 264 retreq.URL = inreq.URL() 265 } 266 267 out, execResp, err := Execute(ctx, srv, msg, retreq) 268 if err != nil { 269 return nil, fmt.Errorf("executing invocations: %w", err) 270 } 271 272 // if there is no agent message to respond with, we simply respond with the 273 // response from execution (i.e. missing proofs response) 274 if out == nil { 275 return thttp.NewResponse(execResp.Status, execResp.Body, execResp.Headers), nil 276 } 277 278 encResp, err := selection.Encoder().Encode(out) 279 if err != nil { 280 return nil, fmt.Errorf("encoding response message: %w", err) 281 } 282 283 // Use status from execution response if non-zero and encode response status 284 // is zero or 200. 285 status := encResp.Status() 286 if execResp.Status != 0 && (status == 0 || status == http.StatusOK) { 287 status = execResp.Status 288 } 289 290 // Merge headers 291 headers := encResp.Headers() 292 if execResp.Headers != nil { 293 if headers == nil { 294 headers = http.Header{} 295 } 296 for name, values := range execResp.Headers { 297 for _, v := range values { 298 headers.Add(name, v) 299 } 300 } 301 } 302 303 return thttp.NewResponse(status, execResp.Body, headers), nil 304 } 305 306 func Execute(ctx context.Context, srv CachingServer, msg message.AgentMessage, req Request) (message.AgentMessage, Response, error) { 307 // retrieval server supports only 1 invocation in the agent message, since 308 // only a single handler can use the body. 309 invs := msg.Invocations() 310 if len(invs) != 1 { 311 var rcpts []receipt.AnyReceipt 312 res := result.NewFailure(NewAgentMessageInvocationCountError()) 313 for _, l := range invs { 314 rcpt, err := receipt.Issue(srv.ID(), res, ran.FromLink(l)) 315 if err != nil { 316 return nil, Response{}, err 317 } 318 rcpts = append(rcpts, rcpt) 319 } 320 out, err := message.Build(nil, rcpts) 321 if err != nil { 322 return nil, Response{}, err 323 } 324 return out, Response{Status: http.StatusBadRequest}, nil 325 } 326 327 inv, err := ExtractInvocation(ctx, invs[0], msg, srv.Cache()) 328 if err != nil { 329 mpe := MissingProofs{} 330 if errors.As(err, &mpe) { 331 n, err := mpe.ToIPLD() 332 if err != nil { 333 return nil, Response{}, fmt.Errorf("building missing proofs IPLD view: %w", err) 334 } 335 body, err := ipldprime.Encode(n, dagjson.Encode) 336 if err != nil { 337 return nil, Response{}, fmt.Errorf("encoding missing proofs repsonse: %w", err) 338 } 339 headers := http.Header{} 340 expiry := time.Now().Add(10 * time.Minute).Unix() // TODO: honour this? 341 headers.Set("X-UCAN-Cache-Expiry", fmt.Sprintf("%d", expiry)) 342 headers.Set("Content-Type", "application/json") 343 return nil, Response{ 344 Status: http.StatusNotExtended, 345 Body: io.NopCloser(bytes.NewReader(body)), 346 Headers: headers, 347 }, nil 348 } 349 return nil, Response{}, err 350 } 351 352 rcpt, resp, err := Run(ctx, srv, inv, req) 353 if err != nil { 354 return nil, Response{}, fmt.Errorf("running invocation: %w", err) 355 } 356 out, err := message.Build(nil, []receipt.AnyReceipt{rcpt}) 357 if err != nil { 358 return nil, Response{}, fmt.Errorf("building agent message: %w", err) 359 } 360 return out, resp, nil 361 } 362 363 func ExtractInvocation(ctx context.Context, root ipld.Link, msg message.AgentMessage, cache delegation.Store) (invocation.Invocation, error) { 364 bs, err := blockstore.NewBlockStore(blockstore.WithBlocksIterator(msg.Blocks())) 365 if err != nil { 366 return nil, fmt.Errorf("creating blockstore from agent message: %w", err) 367 } 368 369 var dlgs []delegation.Delegation 370 var newdlgs []delegation.Delegation 371 chkpfs := []ipld.Link{root} 372 var missingpfs []ipld.Link 373 for len(chkpfs) > 0 { 374 prf := chkpfs[0] 375 chkpfs = chkpfs[1:] 376 377 blk, ok, err := bs.Get(prf) 378 if err != nil { 379 return nil, fmt.Errorf("getting block %s: %w", prf.String(), err) 380 } 381 382 var dlg delegation.Delegation 383 if ok { 384 dlg, err = delegation.NewDelegation(blk, bs) 385 if err != nil { 386 return nil, fmt.Errorf("creating delegation %s: %w", prf.String(), err) 387 } 388 newdlgs = append(newdlgs, dlg) 389 } else { 390 dlg, ok, err = cache.Get(ctx, prf) 391 if err != nil { 392 return nil, fmt.Errorf("getting delegation %s from cache: %w", prf.String(), err) 393 } 394 if !ok { 395 missingpfs = append(missingpfs, prf) 396 continue 397 } 398 } 399 400 dlgs = append(dlgs, dlg) 401 chkpfs = append(chkpfs, dlg.Proofs()...) 402 } 403 404 if len(missingpfs) > 0 { 405 for _, dlg := range newdlgs { 406 err := cache.Put(ctx, dlg) // cache new delegations for subsequent request 407 if err != nil { 408 return nil, fmt.Errorf("caching delegation %s: %w", dlg.Link().String(), err) 409 } 410 } 411 return nil, NewMissingProofsError(missingpfs) 412 } 413 414 // Add the blocks from the delegations to the blockstore and create a new 415 // invocation which has everything. 416 for _, dlg := range dlgs { 417 for b, err := range dlg.Export() { 418 if err != nil { 419 return nil, fmt.Errorf("exporting blocks from delegation %s: %w", dlg.Link().String(), err) 420 } 421 err = bs.Put(b) 422 if err != nil { 423 return nil, fmt.Errorf("putting block %s: %w", b.Link().String(), err) 424 } 425 } 426 } 427 return invocation.NewInvocationView(root, bs) 428 } 429 430 // Run is similar to [server.Run] except the receipts that are issued do not 431 // include the invocation block(s) in order to save bytes when transmitting the 432 // receipt in HTTP headers. 433 func Run(ctx context.Context, srv server.Server[Service], invocation server.ServiceInvocation, req Request) (receipt.AnyReceipt, Response, error) { 434 caps := invocation.Capabilities() 435 // Invocation needs to have one single capability 436 if len(caps) != 1 { 437 capErr := server.NewInvocationCapabilityError(invocation.Capabilities()) 438 rcpt, err := receipt.Issue(srv.ID(), result.NewFailure(capErr), ran.FromLink(invocation.Link())) 439 return rcpt, Response{}, err 440 } 441 442 cap := caps[0] 443 handle, ok := srv.Service()[cap.Can()] 444 if !ok { 445 notFoundErr := server.NewHandlerNotFoundError(cap) 446 rcpt, err := receipt.Issue(srv.ID(), result.NewFailure(notFoundErr), ran.FromLink(invocation.Link())) 447 return rcpt, Response{}, err 448 } 449 450 tx, resp, err := handle(ctx, invocation, srv.Context(), req) 451 if err != nil { 452 if errors.Is(err, context.Canceled) { 453 return nil, Response{}, err 454 } 455 execErr := server.NewHandlerExecutionError(err, cap) 456 srv.Catch(execErr) 457 rcpt, err := receipt.Issue(srv.ID(), result.NewFailure(execErr), ran.FromLink(invocation.Link())) 458 if err != nil { 459 return nil, Response{}, err 460 } 461 return rcpt, resp, nil 462 } 463 464 fx := tx.Fx() 465 var opts []receipt.Option 466 if fx != nil { 467 opts = append(opts, receipt.WithJoin(fx.Join()), receipt.WithFork(fx.Fork()...)) 468 } 469 470 rcpt, err := receipt.Issue(srv.ID(), tx.Out(), ran.FromLink(invocation.Link()), opts...) 471 if err != nil { 472 return nil, Response{}, err 473 } 474 475 if err := srv.LogReceipt(ctx, rcpt, invocation); err != nil { 476 return nil, Response{}, err 477 } 478 479 return rcpt, resp, nil 480 }