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  }