Skip to content

Commit 2c0e5df

Browse files
author
xelat09
committed
implement ContentServerDataStore for azure blob storage too
1 parent e20b174 commit 2c0e5df

File tree

3 files changed

+92
-0
lines changed

3 files changed

+92
-0
lines changed

pkg/azurestore/azureservice.go

+68
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,9 @@ import (
2121
"errors"
2222
"fmt"
2323
"io"
24+
"net/http"
2425
"sort"
26+
"strconv"
2527
"strings"
2628

2729
"github.com/Azure/azure-sdk-for-go/sdk/azcore"
@@ -67,6 +69,8 @@ type AzBlob interface {
6769
Upload(ctx context.Context, body io.ReadSeeker) error
6870
// Download returns a readcloser to download the contents of the blob
6971
Download(ctx context.Context) (io.ReadCloser, error)
72+
// Serves the contents of the blob directly handling HTTP Range requests if set
73+
ServeContent(ctx context.Context, w http.ResponseWriter, r *http.Request) error
7074
// Get the offset of the blob and its indexes
7175
GetOffset(ctx context.Context) (int64, error)
7276
// Commit the uploaded blocks to the BlockBlob
@@ -187,6 +191,31 @@ func (blockBlob *BlockBlob) Download(ctx context.Context) (io.ReadCloser, error)
187191
return resp.Body, nil
188192
}
189193

194+
// Serve content respecting range header
195+
func (blockBlob *BlockBlob) ServeContent(ctx context.Context, w http.ResponseWriter, r *http.Request) error {
196+
var downloadOptions, err = parseHTTPRange(r)
197+
if err != nil {
198+
return err
199+
}
200+
resp, err := blockBlob.BlobClient.DownloadStream(ctx, downloadOptions)
201+
if err != nil {
202+
return err
203+
}
204+
205+
statusCode := http.StatusOK
206+
if resp.ContentRange != nil {
207+
// Use 206 Partial Content for range requests
208+
statusCode = http.StatusPartialContent
209+
} else if resp.ContentLength != nil && *resp.ContentLength == 0 {
210+
statusCode = http.StatusNoContent
211+
}
212+
w.WriteHeader(statusCode)
213+
214+
_, err = io.Copy(w, resp.Body)
215+
resp.Body.Close()
216+
return err
217+
}
218+
190219
func (blockBlob *BlockBlob) GetOffset(ctx context.Context) (int64, error) {
191220
// Get the offset of the file from azure storage
192221
// For the blob, show each block (ID and size) that is a committed part of it.
@@ -253,6 +282,11 @@ func (infoBlob *InfoBlob) Download(ctx context.Context) (io.ReadCloser, error) {
253282
return resp.Body, nil
254283
}
255284

285+
// ServeContent is not needed for infoBlob
286+
func (infoBlob *InfoBlob) ServeContent(ctx context.Context, w http.ResponseWriter, r *http.Request) error {
287+
return nil
288+
}
289+
256290
// infoBlob does not utilise offset, so just return 0, nil
257291
func (infoBlob *InfoBlob) GetOffset(ctx context.Context) (int64, error) {
258292
return 0, nil
@@ -309,3 +343,37 @@ func checkForNotFoundError(err error) error {
309343
}
310344
return err
311345
}
346+
347+
// simple parse http ranging, no multipart ranges/no if-range/no last-modified, not supported by azure anyway
348+
func parseHTTPRange(r *http.Request) (*azblob.DownloadStreamOptions, error) {
349+
rangeHeader := r.Header.Get("Range")
350+
if rangeHeader == "" {
351+
// this is totally fine, Range header is not required
352+
return nil, nil
353+
}
354+
355+
const prefix = "bytes="
356+
if !strings.HasPrefix(rangeHeader, prefix) {
357+
return nil, fmt.Errorf("invalid Range header format")
358+
}
359+
360+
rangeParts := strings.Split(strings.TrimPrefix(rangeHeader, prefix), "-")
361+
if len(rangeParts) != 2 {
362+
return nil, fmt.Errorf("invalid Range header format")
363+
}
364+
365+
offset, err := strconv.ParseInt(rangeParts[0], 10, 64)
366+
if err != nil {
367+
return nil, fmt.Errorf("invalid offset in Range header")
368+
}
369+
370+
count, err := strconv.ParseInt(rangeParts[1], 10, 64)
371+
if err != nil {
372+
return nil, fmt.Errorf("invalid count in Range header")
373+
}
374+
375+
downloadOptions := azblob.DownloadStreamOptions{}
376+
downloadOptions.Range.Offset = offset
377+
downloadOptions.Range.Count = count - offset + 1
378+
return &downloadOptions, nil
379+
}

pkg/azurestore/azurestore.go

+10
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import (
88
"fmt"
99
"io"
1010
"io/fs"
11+
"net/http"
1112
"os"
1213
"strings"
1314

@@ -47,6 +48,7 @@ func (store AzureStore) UseIn(composer *handler.StoreComposer) {
4748
composer.UseCore(store)
4849
composer.UseTerminater(store)
4950
composer.UseLengthDeferrer(store)
51+
composer.UseContentServer(store)
5052
}
5153

5254
func (store AzureStore) NewUpload(ctx context.Context, info handler.FileInfo) (handler.Upload, error) {
@@ -149,6 +151,10 @@ func (store AzureStore) AsLengthDeclarableUpload(upload handler.Upload) handler.
149151
return upload.(*AzUpload)
150152
}
151153

154+
func (store AzureStore) AsServableUpload(upload handler.Upload) handler.ServableUpload {
155+
return upload.(*AzUpload)
156+
}
157+
152158
func (upload *AzUpload) WriteChunk(ctx context.Context, offset int64, src io.Reader) (int64, error) {
153159
// Create a temporary file for holding the uploaded data
154160
file, err := os.CreateTemp(upload.tempDir, "tusd-az-tmp-")
@@ -214,6 +220,10 @@ func (upload *AzUpload) GetReader(ctx context.Context) (io.ReadCloser, error) {
214220
return upload.BlockBlob.Download(ctx)
215221
}
216222

223+
func (upload *AzUpload) ServeContent(ctx context.Context, w http.ResponseWriter, r *http.Request) error {
224+
return upload.BlockBlob.ServeContent(ctx, w, r)
225+
}
226+
217227
// Finish the file upload and commit the block list
218228
func (upload *AzUpload) FinishUpload(ctx context.Context) error {
219229
return upload.BlockBlob.Commit(ctx)

pkg/azurestore/azurestore_mock_test.go

+14
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)