github.com/storacha/go-ucanto@v0.7.2/examples/retrieval/server/server.go (about) 1 package main 2 3 import ( 4 "context" 5 "errors" 6 "fmt" 7 "io" 8 "net/http" 9 "os" 10 "path" 11 12 "github.com/storacha/go-ucanto/core/invocation" 13 "github.com/storacha/go-ucanto/core/receipt/fx" 14 "github.com/storacha/go-ucanto/core/result" 15 "github.com/storacha/go-ucanto/core/result/failure" 16 "github.com/storacha/go-ucanto/examples/retrieval/capabilities/content" 17 "github.com/storacha/go-ucanto/server" 18 "github.com/storacha/go-ucanto/server/retrieval" 19 "github.com/storacha/go-ucanto/testing/fixtures" 20 thttp "github.com/storacha/go-ucanto/transport/http" 21 "github.com/storacha/go-ucanto/ucan" 22 ) 23 24 func main() { 25 server, err := retrieval.NewServer( 26 fixtures.Service, 27 retrieval.WithServiceMethod( 28 content.Serve.Can(), 29 retrieval.Provide( 30 content.Serve, 31 func(ctx context.Context, cap ucan.Capability[content.ServeCaveats], inv invocation.Invocation, ictx server.InvocationContext, req retrieval.Request) (result.Result[content.ServeOk, failure.IPLDBuilderFailure], fx.Effects, retrieval.Response, error) { 32 filepath := path.Join(".", "data", req.URL.String()+".blob") 33 file, err := os.Open(filepath) 34 if err != nil { 35 return nil, nil, retrieval.Response{}, err 36 } 37 info, err := file.Stat() 38 if err != nil { 39 return nil, nil, retrieval.Response{}, err 40 } 41 nb := cap.Nb() 42 response := retrieval.Response{Status: http.StatusOK, Headers: http.Header{}, Body: file} 43 response.Headers.Set("Content-Length", fmt.Sprintf("%d", info.Size())) 44 if len(nb.Range) > 0 { // handle byte range request 45 start, end := nb.Range[0], nb.Range[1] 46 length := end - start + 1 47 response.Headers.Set("Content-Length", fmt.Sprintf("%d", length)) 48 response.Headers.Set("Content-Range", fmt.Sprintf("bytes %d-%d/%d", start, end, info.Size())) 49 response.Status = http.StatusPartialContent 50 response.Body = newFileSectionReader(file, start, length) 51 } 52 result := result.Ok[content.ServeOk, failure.IPLDBuilderFailure](content.ServeOk(nb)) 53 return result, nil, response, nil 54 }, 55 ), 56 ), 57 ) 58 if err != nil { 59 panic(fmt.Errorf("creating UCAN server: %w", err)) 60 } 61 62 mux := http.NewServeMux() 63 mux.HandleFunc("/{digest}", func(w http.ResponseWriter, r *http.Request) { 64 resp, err := server.Request(r.Context(), thttp.NewInboundRequest(r.URL, r.Body, r.Header)) 65 if err != nil { 66 http.Error(w, err.Error(), 500) 67 return 68 } 69 for name, values := range resp.Headers() { 70 for _, value := range values { 71 w.Header().Add(name, value) 72 } 73 } 74 w.WriteHeader(resp.Status()) 75 body := resp.Body() 76 io.Copy(w, body) 77 body.Close() 78 }) 79 80 httpServer := &http.Server{ 81 Addr: ":3000", 82 Handler: mux, 83 MaxHeaderBytes: 2 * 1024, 84 } 85 86 fmt.Printf("ID: %s\n", fixtures.Service.DID()) 87 fmt.Println("Listening on: http://localhost:3000") 88 err = httpServer.ListenAndServe() 89 if err != nil { 90 if !errors.Is(err, http.ErrServerClosed) { 91 panic(err) 92 } 93 } 94 }