Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit 1d8b69a

Browse files
authored
chore: revert back to FileServer to prevent bytes in memory (#87)
1 parent cd7ab5f commit 1d8b69a

File tree

9 files changed

+58
-100
lines changed

9 files changed

+58
-100
lines changed

‎api/api.go‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -112,7 +112,7 @@ func New(options *Options) *API {
112112
r.Post("/api/extensionquery", api.extensionQuery)
113113

114114
// Endpoint for getting an extension's files or the extension zip.
115-
r.Mount("/files", http.StripPrefix("/files", storage.HTTPFileServer(options.Storage)))
115+
r.Mount("/files", http.StripPrefix("/files", options.Storage.FileServer()))
116116

117117
// VS Code can use the files in the response to get file paths but it will
118118
// sometimes ignore that and use requests to /assets with hardcoded types to

‎go.mod‎

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@ require (
99
github.com/go-chi/httprate v0.14.1
1010
github.com/google/uuid v1.6.0
1111
github.com/lithammer/fuzzysearch v1.1.8
12-
github.com/spf13/afero v1.11.0
1312
github.com/spf13/cobra v1.8.1
1413
github.com/stretchr/testify v1.9.0
1514
golang.org/x/mod v0.19.0
@@ -18,6 +17,7 @@ require (
1817
)
1918

2019
require (
20+
cloud.google.com/go/logging v1.8.1 // indirect
2121
github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect
2222
github.com/cespare/xxhash/v2 v2.3.0 // indirect
2323
github.com/charmbracelet/lipgloss v0.7.1 // indirect
@@ -38,6 +38,9 @@ require (
3838
golang.org/x/sys v0.17.0 // indirect
3939
golang.org/x/term v0.17.0 // indirect
4040
golang.org/x/text v0.14.0 // indirect
41+
google.golang.org/genproto v0.0.0-20231106174013-bbf56f31fb17 // indirect
42+
google.golang.org/genproto/googleapis/api v0.0.0-20231106174013-bbf56f31fb17 // indirect
43+
google.golang.org/genproto/googleapis/rpc v0.0.0-20231120223509-83a465c0220f // indirect
4144
google.golang.org/protobuf v1.32.0 // indirect
4245
gopkg.in/yaml.v3 v3.0.1 // indirect
4346
)

‎go.sum‎

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,10 @@ cloud.google.com/go/compute v1.23.3 h1:6sVlXXBmbd7jNX0Ipq0trII3e4n1/MsADLK6a+aiV
55
cloud.google.com/go/compute v1.23.3/go.mod h1:VCgBUoMnIVIR0CscqQiPJLAG25E3ZRZMzcFZeQ+h8CI=
66
cloud.google.com/go/compute/metadata v0.2.3 h1:mg4jlk7mCAj6xXp9UJ4fjI9VUI5rubuGBW5aJ7UnBMY=
77
cloud.google.com/go/compute/metadata v0.2.3/go.mod h1:VAV5nSsACxMJvgaAuX6Pk2AawlZn8kiOGuCv6gTkwuA=
8-
cloud.google.com/go/logging v1.7.0 h1:CJYxlNNNNAMkHp9em/YEXcfJg+rPDg7YfwoRpMU+t5I=
9-
cloud.google.com/go/logging v1.7.0/go.mod h1:3xjP2CjkM3ZkO73aj4ASA5wRPGGCRrPIAeNqVNkzY8M=
10-
cloud.google.com/go/longrunning v0.5.1 h1:Fr7TXftcqTudoyRJa113hyaqlGdiBQkp0Gq7tErFDWI=
11-
cloud.google.com/go/longrunning v0.5.1/go.mod h1:spvimkwdz6SPWKEt/XBij79E9fiTkHSQl/fRUUQJYJc=
8+
cloud.google.com/go/logging v1.8.1 h1:26skQWPeYhvIasWKm48+Eq7oUqdcdbwsCVwz5Ys0FvU=
9+
cloud.google.com/go/logging v1.8.1/go.mod h1:TJjR+SimHwuC8MZ9cjByQulAMgni+RkXeI3wwctHJEI=
10+
cloud.google.com/go/longrunning v0.5.4 h1:w8xEcbZodnA2BbW6sVirkkoC+1gP8wS57EUUgGS0GVg=
11+
cloud.google.com/go/longrunning v0.5.4/go.mod h1:zqNVncI0BOP8ST6XQD1+VcvuShMmq7+xFSzOL++V0dI=
1212
github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k=
1313
github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8=
1414
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
@@ -56,8 +56,6 @@ github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJ
5656
github.com/rivo/uniseg v0.4.4 h1:8TfxU8dW6PdqD27gjM8MVNuicgxIjxpm4K7x4jp8sis=
5757
github.com/rivo/uniseg v0.4.4/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
5858
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
59-
github.com/spf13/afero v1.11.0 h1:WJQKhtpdm3v2IzqG8VMqrr6Rf3UYpEF239Jy9wNepM8=
60-
github.com/spf13/afero v1.11.0/go.mod h1:GH9Y3pIexgf1MTIWtNGyogA5MwRIDXGUr+hbWNoBjkY=
6159
github.com/spf13/cobra v1.8.1 h1:e5/vxKd/rZsfSJMUX1agtjeTDf+qv1/JdBF8gg5k9ZM=
6260
github.com/spf13/cobra v1.8.1/go.mod h1:wHxEcudfqmLYa8iTfL+OuZPbBZkmvliBWKIezN3kD9Y=
6361
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=

‎storage/artifactory.go‎

Lines changed: 18 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@ import (
77
"errors"
88
"fmt"
99
"io"
10-
"io/fs"
1110
"net/http"
1211
"os"
1312
"path"
@@ -16,7 +15,6 @@ import (
1615
"sync"
1716
"time"
1817

19-
"github.com/spf13/afero/mem"
2018
"golang.org/x/sync/errgroup"
2119
"golang.org/x/xerrors"
2220

@@ -277,37 +275,25 @@ func (s *Artifactory) AddExtension(ctx context.Context, manifest *VSIXManifest,
277275
return s.uri + dir, nil
278276
}
279277

280-
// Open returns a file from Artifactory.
281-
// TODO: Since we only extract a subset of files perhaps if the file does not
282-
// exist we should download the vsix and extract the requested file as a
283-
// fallback. Obviously this seems like quite a bit of overhead so we would
284-
// then emit a warning so we can notice that VS Code has added new asset types
285-
// that we should be extracting to avoid that overhead. Other solutions could
286-
// be implemented though like extracting the VSIX to disk locally and only
287-
// going to Artifactory for the VSIX when it is missing on disk (basically
288-
// using the disk as a cache).
289-
func (s *Artifactory) Open(ctx context.Context, fp string) (fs.File, error) {
290-
resp, code, err := s.read(ctx, fp)
291-
if code != http.StatusOK || err != nil {
292-
switch code {
293-
case http.StatusNotFound:
294-
return nil, fs.ErrNotExist
295-
case http.StatusForbidden:
296-
return nil, fs.ErrPermission
297-
default:
298-
return nil, err
278+
func (s *Artifactory) FileServer() http.Handler {
279+
// TODO: Since we only extract a subset of files perhaps if the file does not
280+
// exist we should download the vsix and extract the requested file as a
281+
// fallback. Obviously this seems like quite a bit of overhead so we would
282+
// then emit a warning so we can notice that VS Code has added new asset types
283+
// that we should be extracting to avoid that overhead. Other solutions could
284+
// be implemented though like extracting the VSIX to disk locally and only
285+
// going to Artifactory for the VSIX when it is missing on disk (basically
286+
// using the disk as a cache).
287+
return http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
288+
reader, code, err := s.read(r.Context(), r.URL.Path)
289+
if err != nil {
290+
http.Error(rw, err.Error(), code)
291+
return
299292
}
300-
}
301-
302-
// TODO: Do no copy the bytes into memory, stream them rather than
303-
// storing the entire file into memory.
304-
f := mem.NewFileHandle(mem.CreateFile(fp))
305-
_, err = io.Copy(f, resp)
306-
if err != nil {
307-
return nil, err
308-
}
309-
310-
return f, nil
293+
defer reader.Close()
294+
rw.WriteHeader(http.StatusOK)
295+
_, _ = io.Copy(rw, reader)
296+
})
311297
}
312298

313299
func (s *Artifactory) Manifest(ctx context.Context, publisher, name string, version Version) (*VSIXManifest, error) {

‎storage/local.go‎

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@ import (
44
"context"
55
"fmt"
66
"io"
7-
"io/fs"
87
"net/http"
98
"os"
109
"path/filepath"
@@ -142,8 +141,8 @@ func (s *Local) AddExtension(ctx context.Context, manifest *VSIXManifest, vsix [
142141
return dir, nil
143142
}
144143

145-
func (s *Local) Open(_ context.Context, fpstring) (fs.File, error) {
146-
return http.Dir(s.extdir).Open(fp)
144+
func (s *Local) FileServer() http.Handler {
145+
return http.FileServer(http.Dir(s.extdir))
147146
}
148147

149148
func (s *Local) Manifest(ctx context.Context, publisher, name string, version Version) (*VSIXManifest, error) {

‎storage/signature.go‎

Lines changed: 25 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,13 @@ package storage
22

33
import (
44
"context"
5-
"io/fs"
6-
"path/filepath"
5+
"net/http"
6+
"strconv"
77
"strings"
88

9-
"github.com/spf13/afero/mem"
10-
"golang.org/x/xerrors"
11-
129
"cdr.dev/slog"
10+
"github.com/coder/code-marketplace/api/httpapi"
11+
"github.com/coder/code-marketplace/api/httpmw"
1312

1413
"github.com/coder/code-marketplace/extensionsign"
1514
)
@@ -70,7 +69,7 @@ func (s *Signature) Manifest(ctx context.Context, publisher, name string, versio
7069
return manifest, nil
7170
}
7271

73-
// Open will intercept requests for signed extensions payload.
72+
// FileServer will intercept requests for signed extensions payload.
7473
// It does this by looking for 'SigzipFileExtension' or p7s.sig.
7574
//
7675
// The signed payload is completely empty. Nothing it actually signed.
@@ -85,18 +84,26 @@ func (s *Signature) Manifest(ctx context.Context, publisher, name string, versio
8584
// for linux users.
8685
// For windows users, the signature must be valid, and this implementation
8786
// will not work.
88-
func (s *Signature) Open(ctx context.Context, fp string) (fs.File, error) {
89-
if s.SigningEnabled() && strings.HasSuffix(filepath.Base(fp), SigzipFileExtension) {
90-
// hijack this request, return an empty signature payload
91-
signed, err := extensionsign.IncludeEmptySignature()
92-
if err != nil {
93-
return nil, xerrors.Errorf("sign and zip manifest: %w", err)
94-
}
87+
func (s *Signature) FileServer() http.Handler {
88+
return http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
89+
if s.SigningEnabled() && strings.HasSuffix(r.URL.Path, SigzipFileExtension) {
90+
// hijack this request, return an empty signature payload
91+
signed, err := extensionsign.IncludeEmptySignature()
92+
if err != nil {
93+
httpapi.Write(rw, http.StatusInternalServerError, httpapi.ErrorResponse{
94+
Message: "Unable to generate empty signature for extension",
95+
Detail: err.Error(),
96+
RequestID: httpmw.RequestID(r),
97+
})
98+
return
99+
}
95100

96-
f := mem.NewFileHandle(mem.CreateFile(fp))
97-
_, err = f.Write(signed)
98-
return f, err
99-
}
101+
rw.Header().Set("Content-Length", strconv.FormatInt(int64(len(signed)), 10))
102+
rw.WriteHeader(http.StatusOK)
103+
_, _ = rw.Write(signed)
104+
return
105+
}
100106

101-
return s.Storage.Open(ctx, fp)
107+
s.Storage.FileServer().ServeHTTP(rw, r)
108+
})
102109
}

‎storage/storage.go‎

Lines changed: 3 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@ import (
66
"encoding/xml"
77
"fmt"
88
"io"
9-
"io/fs"
109
"net/http"
1110
"os"
1211
"regexp"
@@ -211,10 +210,9 @@ type Storage interface {
211210
// for verification purposes. Extra files can be included, but not required.
212211
// All extra files will be placed relative to the manifest outside the vsix.
213212
AddExtension(ctx context.Context, manifest *VSIXManifest, vsix []byte, extra ...File) (string, error)
214-
// Open mirrors the fs.FS interface of Open, except with a context.
215-
// The Open should return files from the extension storage, and used for
216-
// serving extensions.
217-
Open(ctx context.Context, name string) (fs.File, error)
213+
// FileServer provides a handler for fetching extension repository files from
214+
// a client.
215+
FileServer() http.Handler
218216
// Manifest returns the manifest bytes for the provided extension. The
219217
// extension asset itself (the VSIX) will be included on the manifest even if
220218
// it does not exist on the manifest on disk.
@@ -237,17 +235,6 @@ type Storage interface {
237235
WalkExtensions(ctx context.Context, fn func(manifest *VSIXManifest, versions []Version) error) error
238236
}
239237

240-
// HTTPFileServer creates an http.Handler that serves files from the provided
241-
// storage.
242-
func HTTPFileServer(s Storage) http.Handler {
243-
return http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
244-
http.FileServerFS(&contextFs{
245-
ctx: r.Context(),
246-
open: s.Open,
247-
}).ServeHTTP(rw, r)
248-
})
249-
}
250-
251238
type File struct {
252239
RelativePath string
253240
Content []byte
@@ -442,12 +429,3 @@ func ParseExtensionID(id string) (string, string, string, error) {
442429
}
443430
return match[0][1], match[0][2], match[0][3], nil
444431
}
445-
446-
type contextFs struct {
447-
ctx context.Context
448-
open func(ctx context.Context, name string) (fs.File, error)
449-
}
450-
451-
func (c *contextFs) Open(name string) (fs.File, error) {
452-
return c.open(c.ctx, name)
453-
}

‎storage/storage_test.go‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -201,7 +201,7 @@ func testFileServer(t *testing.T, factory storageFactory) {
201201
req := httptest.NewRequest("GET", test.path, nil)
202202
rec := httptest.NewRecorder()
203203

204-
server := storage.HTTPFileServer(f.storage)
204+
server := f.storage.FileServer()
205205
server.ServeHTTP(rec, req)
206206

207207
resp := rec.Result()

‎testutil/mockstorage.go‎

Lines changed: 0 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,10 @@ package testutil
33
import (
44
"context"
55
"errors"
6-
"io/fs"
76
"net/http"
87
"os"
9-
"path/filepath"
108
"sort"
119

12-
"github.com/spf13/afero/mem"
13-
1410
"github.com/coder/code-marketplace/storage"
1511
)
1612

@@ -26,15 +22,6 @@ func NewMockStorage() *MockStorage {
2622
func (s *MockStorage) AddExtension(ctx context.Context, manifest *storage.VSIXManifest, vsix []byte, extra ...storage.File) (string, error) {
2723
return "", errors.New("not implemented")
2824
}
29-
func (s *MockStorage) Open(ctx context.Context, path string) (fs.File, error) {
30-
if filepath.Base(path) == "nonexistent" {
31-
return nil, fs.ErrNotExist
32-
}
33-
34-
f := mem.NewFileHandle(mem.CreateFile(path))
35-
_, _ = f.Write([]byte("foobar"))
36-
return f, nil
37-
}
3825

3926
func (s *MockStorage) FileServer() http.Handler {
4027
return http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {

0 commit comments

Comments
(0)

AltStyle によって変換されたページ (->オリジナル) /