From 22422d0ad2052dcc12680ba0dc2c3aef5a150df3 Mon Sep 17 00:00:00 2001 From: Maxim Lebedev Date: Sun, 19 Nov 2023 13:41:13 +0600 Subject: [PATCH 1/8] :label: Do not make File path absolute --- internal/domain/file.go | 1 - 1 file changed, 1 deletion(-) diff --git a/internal/domain/file.go b/internal/domain/file.go index 180253c..38b27fe 100644 --- a/internal/domain/file.go +++ b/internal/domain/file.go @@ -32,7 +32,6 @@ func NewFile(path string) File { translationBaseName: "", uniqueId: "", } - out.path, _ = filepath.Abs(path) out.baseFileName = strings.TrimSuffix(out.logicalName, filepath.Ext(out.logicalName)) parts := strings.Split(out.baseFileName, ".") From c8c5a89269de903c4fe1c1dcce31d624559e06d9 Mon Sep 17 00:00:00 2001 From: Maxim Lebedev Date: Sun, 19 Nov 2023 13:41:53 +0600 Subject: [PATCH 2/8] :label: Removed bytes memory in Resource domain due stack overflow --- internal/domain/resource.go | 23 +++---------------- .../resource/repository/fs/fs_resource.go | 8 +------ 2 files changed, 4 insertions(+), 27 deletions(-) diff --git a/internal/domain/resource.go b/internal/domain/resource.go index f71693b..174b321 100644 --- a/internal/domain/resource.go +++ b/internal/domain/resource.go @@ -1,7 +1,6 @@ package domain import ( - "bytes" "image" _ "image/gif" _ "image/jpeg" @@ -19,7 +18,6 @@ type Resource struct { File File modTime time.Time - reader io.ReadSeeker params map[string]any // TODO(toby3d): set from Page configuration mediaType MediaType key string @@ -29,7 +27,7 @@ type Resource struct { image image.Config } -func NewResource(modTime time.Time, content []byte, key string) *Resource { +func NewResource(modTime time.Time, r io.Reader, key string) *Resource { mediaType, _, _ := mime.ParseMediaType(mime.TypeByExtension(path.Ext(key))) out := &Resource{ File: NewFile(key), @@ -39,12 +37,11 @@ func NewResource(modTime time.Time, content []byte, key string) *Resource { title: "", // TODO(toby3d): set from Page configuration params: make(map[string]any), // TODO(toby3d): set from Page configuration mediaType: NewMediaType(mediaType), - reader: bytes.NewReader(content), } switch path.Ext(key) { default: - out.resourceType, _ = ParseResourceType(out.mediaType.mainType) + out.resourceType = ResourceType(out.mediaType.mainType) case ".md": out.resourceType = ResourceTypePage case ".webmanifest": @@ -53,7 +50,7 @@ func NewResource(modTime time.Time, content []byte, key string) *Resource { switch out.resourceType { case ResourceTypeImage: - out.image, _, _ = image.DecodeConfig(out.reader) + out.image, _, _ = image.DecodeConfig(r) } return out @@ -85,20 +82,6 @@ func (r Resource) ResourceType() ResourceType { return r.resourceType } -func (r Resource) Content() []byte { - content, _ := io.ReadAll(r.reader) - - return content -} - -func (r Resource) Read(p []byte) (int, error) { - return r.reader.Read(p) -} - -func (r Resource) Seek(offset int64, whence int) (int64, error) { - return r.reader.Seek(offset, whence) -} - func (f Resource) GoString() string { return "domain.Resource(" + f.key + ")" } diff --git a/internal/resource/repository/fs/fs_resource.go b/internal/resource/repository/fs/fs_resource.go index 2b52af2..9413e47 100644 --- a/internal/resource/repository/fs/fs_resource.go +++ b/internal/resource/repository/fs/fs_resource.go @@ -6,7 +6,6 @@ import ( _ "image/gif" _ "image/jpeg" _ "image/png" - "io" "io/fs" _ "golang.org/x/image/bmp" @@ -38,12 +37,7 @@ func (repo *fileServerResourceRepository) Get(ctx context.Context, p string) (*d } defer f.Close() - content, err := io.ReadAll(f) - if err != nil { - return nil, fmt.Errorf("cannot read resource content on path '%s': %w", p, err) - } - - return domain.NewResource(info.ModTime(), content, p), nil + return domain.NewResource(info.ModTime(), f, p), nil } func (repo *fileServerResourceRepository) Fetch(ctx context.Context, pattern string) (domain.Resources, int, error) { From 3a2515c25513d84236d747d7df1d0a0d649316ea Mon Sep 17 00:00:00 2001 From: Maxim Lebedev Date: Sun, 19 Nov 2023 13:51:15 +0600 Subject: [PATCH 3/8] :label: Created Static domain --- internal/domain/static.go | 36 ++++++++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) create mode 100644 internal/domain/static.go diff --git a/internal/domain/static.go b/internal/domain/static.go new file mode 100644 index 0000000..7b12f32 --- /dev/null +++ b/internal/domain/static.go @@ -0,0 +1,36 @@ +package domain + +import ( + "io" + "time" +) + +type Static struct { + modTime time.Time + readSeeker io.ReadSeeker + name string +} + +func NewStatic(rs io.ReadSeeker, modTime time.Time, name string) *Static { + return &Static{ + name: name, + modTime: modTime, + readSeeker: rs, + } +} + +func (s Static) Name() string { + return s.name +} + +func (s Static) ModTime() time.Time { + return s.modTime +} + +func (s *Static) Read(p []byte) (int, error) { + return s.readSeeker.Read(p) +} + +func (s *Static) Seek(offset int64, whence int) (int64, error) { + return s.readSeeker.Seek(offset, whence) +} From 62bc90fd3efa80c6002389b2c1a31976175a148a Mon Sep 17 00:00:00 2001 From: Maxim Lebedev Date: Sun, 19 Nov 2023 13:51:45 +0600 Subject: [PATCH 4/8] :recycle: Replaced Resource on Static in static module contracts --- internal/static/repository.go | 10 +++++----- internal/static/repository/fs/fs_static.go | 15 ++++++++------- internal/static/usecase.go | 2 +- internal/static/usecase/static_ucase.go | 6 +++--- 4 files changed, 17 insertions(+), 16 deletions(-) diff --git a/internal/static/repository.go b/internal/static/repository.go index 95bb822..cbbcbeb 100644 --- a/internal/static/repository.go +++ b/internal/static/repository.go @@ -8,11 +8,11 @@ import ( type ( Repository interface { - // Get returns Resource on path if exists - Get(ctx context.Context, path string) (*domain.Resource, error) + // Get returns Static on path if exists + Get(ctx context.Context, path string) (*domain.Static, error) // Fetch returns all resources from dir recursevly. - Fetch(ctx context.Context, pattern string) (domain.Resources, int, error) + Fetch(ctx context.Context, pattern string) ([]*domain.Static, int, error) } dummyRepository struct{} @@ -22,10 +22,10 @@ func NewDummyRepository() dummyRepository { return dummyRepository{} } -func (dummyRepository) Get(ctx context.Context, path string) (*domain.Resource, error) { +func (dummyRepository) Get(ctx context.Context, path string) (*domain.Static, error) { return nil, nil } -func (dummyRepository) Fetch(ctx context.Context, pattern string) (domain.Resources, int, error) { +func (dummyRepository) Fetch(ctx context.Context, pattern string) ([]*domain.Static, int, error) { return nil, 0, nil } diff --git a/internal/static/repository/fs/fs_static.go b/internal/static/repository/fs/fs_static.go index 9dc17f9..adb5609 100644 --- a/internal/static/repository/fs/fs_static.go +++ b/internal/static/repository/fs/fs_static.go @@ -1,6 +1,7 @@ package fs import ( + "bytes" "context" "fmt" _ "image/gif" @@ -26,7 +27,7 @@ func NewFileServerStaticRepository(root fs.FS) static.Repository { } } -func (repo *fileServerStaticRepository) Get(ctx context.Context, p string) (*domain.Resource, error) { +func (repo *fileServerStaticRepository) Get(ctx context.Context, p string) (*domain.Static, error) { info, err := fs.Stat(repo.root, p) if err != nil { return nil, fmt.Errorf("cannot stat static on path '%s': %w", p, err) @@ -40,13 +41,13 @@ func (repo *fileServerStaticRepository) Get(ctx context.Context, p string) (*dom content, err := io.ReadAll(f) if err != nil { - return nil, fmt.Errorf("cannot read static content on path '%s': %w", p, err) + return nil, fmt.Errorf("cannot copy opened '%s' static contents into buffer: %w", p, err) } - return domain.NewResource(info.ModTime(), content, p), nil + return domain.NewStatic(bytes.NewReader(content), info.ModTime(), info.Name()), nil } -func (repo *fileServerStaticRepository) Fetch(ctx context.Context, pattern string) (domain.Resources, int, error) { +func (repo *fileServerStaticRepository) Fetch(ctx context.Context, pattern string) ([]*domain.Static, int, error) { var ( err error matches []string @@ -74,11 +75,11 @@ func (repo *fileServerStaticRepository) Fetch(ctx context.Context, pattern strin } } - out := make(domain.Resources, 0, len(matches)) + out := make([]*domain.Static, 0, len(matches)) for i := range matches { - if r, err := repo.Get(ctx, matches[i]); err == nil { - out = append(out, r) + if s, err := repo.Get(ctx, matches[i]); err == nil { + out = append(out, s) } } diff --git a/internal/static/usecase.go b/internal/static/usecase.go index 7a80ee1..9eb623d 100644 --- a/internal/static/usecase.go +++ b/internal/static/usecase.go @@ -7,5 +7,5 @@ import ( ) type UseCase interface { - Do(ctx context.Context, path string) (*domain.Resource, error) + Do(ctx context.Context, path string) (*domain.Static, error) } diff --git a/internal/static/usecase/static_ucase.go b/internal/static/usecase/static_ucase.go index fa89e40..beee6aa 100644 --- a/internal/static/usecase/static_ucase.go +++ b/internal/static/usecase/static_ucase.go @@ -20,13 +20,13 @@ func NewStaticUseCase(statics static.Repository) static.UseCase { } } -func (ucase *staticUseCase) Do(ctx context.Context, p string) (*domain.Resource, error) { +func (ucase *staticUseCase) Do(ctx context.Context, p string) (*domain.Static, error) { p = strings.TrimPrefix(path.Clean(p), "/") - f, err := ucase.statics.Get(ctx, p) + s, err := ucase.statics.Get(ctx, p) if err != nil { return nil, fmt.Errorf("cannot get static file: %w", err) } - return f, nil + return s, rs, nil } From 867782367e170e0f8e7eb1724d813119be0de761 Mon Sep 17 00:00:00 2001 From: Maxim Lebedev Date: Sun, 19 Nov 2023 13:52:42 +0600 Subject: [PATCH 5/8] :building_construction: Use static module for serving static files --- main.go | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/main.go b/main.go index e92e784..bd42626 100644 --- a/main.go +++ b/main.go @@ -83,13 +83,12 @@ func NewApp(ctx context.Context, config *domain.Config) (*App, error) { pages := pagefsrepo.NewFileSystemPageRepository(contentDir) pager := pageucase.NewPageUseCase(pages, resources) handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - // TODO(toby3d): use exists static use case or split that on static and resource modules? // INFO(toby3d): any static file is public and unprotected by design, so it's safe to search it // first before deep down to any page or it's resource which might be secured by middleware or // something else. static, err := staticer.Do(r.Context(), strings.TrimPrefix(r.URL.Path, "/")) if err == nil { - http.ServeContent(w, r, static.Name(), domain.ResourceModTime(static), static) + http.ServeContent(w, r, static.Name(), static.ModTime(), static) return } From b10c237f509802700222621093bf424d8a7d0481 Mon Sep 17 00:00:00 2001 From: Maxim Lebedev Date: Sun, 19 Nov 2023 13:53:12 +0600 Subject: [PATCH 6/8] :label: Refactored ResourceType for Resources.MatchType universal usage --- internal/domain/resource_type.go | 38 ++++++++------------------------ 1 file changed, 9 insertions(+), 29 deletions(-) diff --git a/internal/domain/resource_type.go b/internal/domain/resource_type.go index 4b22471..c71b798 100644 --- a/internal/domain/resource_type.go +++ b/internal/domain/resource_type.go @@ -2,44 +2,24 @@ package domain import ( "errors" - "fmt" - "strings" ) -type ResourceType struct { - resourceType string -} +type ResourceType string var ( - ResourceTypeUnd ResourceType = ResourceType{} // "und" - ResourceTypeAudio ResourceType = ResourceType{"audio"} // "audio" - ResourceTypeImage ResourceType = ResourceType{"image"} // "image" - ResourceTypePage ResourceType = ResourceType{"page"} // "page" - ResourceTypeText ResourceType = ResourceType{"text"} // "text" - ResourceTypeVideo ResourceType = ResourceType{"video"} // "video" + ResourceTypeUnd ResourceType = "" // "und" + ResourceTypeAudio ResourceType = "audio" // "audio" + ResourceTypeImage ResourceType = "image" // "image" + ResourceTypePage ResourceType = "page" // "page" + ResourceTypeText ResourceType = "text" // "text" + ResourceTypeVideo ResourceType = "video" // "video" ) var ErrResourceType error = errors.New("unsupported ResourceType enum") -var stringsResourceTypes = map[string]ResourceType{ - ResourceTypeAudio.resourceType: ResourceTypeAudio, - ResourceTypeImage.resourceType: ResourceTypeImage, - ResourceTypePage.resourceType: ResourceTypePage, - ResourceTypeText.resourceType: ResourceTypeText, - ResourceTypeVideo.resourceType: ResourceTypeVideo, -} - -func ParseResourceType(raw string) (ResourceType, error) { - if rt, ok := stringsResourceTypes[strings.ToLower(raw)]; ok { - return rt, nil - } - - return ResourceTypeUnd, fmt.Errorf("%w: got '%s', want %s", ErrResourceType, raw, stringsResourceTypes) -} - func (rt ResourceType) String() string { - if rt.resourceType != "" { - return rt.resourceType + if rt != "" { + return string(rt) } return "und" From 5c75e6f9e9d9162222a0bff41c59c1c684fc9a62 Mon Sep 17 00:00:00 2001 From: Maxim Lebedev Date: Sun, 19 Nov 2023 13:57:44 +0600 Subject: [PATCH 7/8] :bug: Fixed redundant static use case return --- internal/static/usecase/static_ucase.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/static/usecase/static_ucase.go b/internal/static/usecase/static_ucase.go index beee6aa..e663e75 100644 --- a/internal/static/usecase/static_ucase.go +++ b/internal/static/usecase/static_ucase.go @@ -28,5 +28,5 @@ func (ucase *staticUseCase) Do(ctx context.Context, p string) (*domain.Static, e return nil, fmt.Errorf("cannot get static file: %w", err) } - return s, rs, nil + return s, nil } From 860d56cfc5417246e50c924a8e16ed7be43f80df Mon Sep 17 00:00:00 2001 From: Maxim Lebedev Date: Sun, 19 Nov 2023 14:02:09 +0600 Subject: [PATCH 8/8] :construction: Serve Page Resource bytes --- main.go | 24 +++++++++++++++++++++--- 1 file changed, 21 insertions(+), 3 deletions(-) diff --git a/main.go b/main.go index bd42626..b9c6a6f 100644 --- a/main.go +++ b/main.go @@ -5,10 +5,12 @@ package main import ( + "bytes" "context" "errors" "flag" "fmt" + "io" "io/fs" "log" "net" @@ -113,8 +115,7 @@ func NewApp(ctx context.Context, config *domain.Config) (*App, error) { if s.DefaultLanguage != domain.LanguageUnd { supported = append( - []language.Tag{language.Make(s.DefaultLanguage.Code())}, - supported..., + []language.Tag{language.Make(s.DefaultLanguage.Code())}, supported..., ) } @@ -163,7 +164,24 @@ func NewApp(ctx context.Context, config *domain.Config) (*App, error) { return } - http.ServeContent(w, r, res.Name(), domain.ResourceModTime(res), res) + // TODO(toby3d) : ugly workaround, must be refactored + resFile, err := contentDir.Open(res.File.Path()) + if err != nil { + http.Error(w, "cannot open: "+err.Error(), http.StatusInternalServerError) + + return + } + defer resFile.Close() + + resBytes, err := io.ReadAll(resFile) + if err != nil { + http.Error(w, "cannot read all: "+err.Error(), http.StatusInternalServerError) + + return + } + defer resFile.Close() + + http.ServeContent(w, r, res.Name(), domain.ResourceModTime(res), bytes.NewReader(resBytes)) return }