@@ -12,11 +12,10 @@ import (
1212 "net/http/httputil"
1313 "net/url"
1414 "os"
15- "strings"
1615
1716 "github.com/aws/aws-sdk-go-v2/aws"
18- //"github.com/aws/aws-sdk-go-v2/aws/signer/v4"
1917 "github.com/aws/aws-sdk-go-v2/config"
18+ "github.com/aws/aws-sdk-go-v2/feature/s3/manager"
2019 "github.com/aws/aws-sdk-go-v2/service/s3"
2120)
2221
@@ -42,7 +41,10 @@ func main() {
4241 cfg .ImgproxyEndpoint = "http://localhost:8080"
4342 }
4443
45- s3Client := initS3Client (cfg )
44+ uploader := manager .NewUploader (initS3Client (cfg ), func (u * manager.Uploader ) {
45+ u .PartSize = 5 * 1024 * 1024
46+ u .BufferProvider = manager .NewBufferedReadSeekerWriteToPool (10 * 1024 * 1024 )
47+ })
4648
4749 // Initialize the proxy
4850 target , err := url .Parse (cfg .ImgproxyEndpoint )
@@ -52,105 +54,67 @@ func main() {
5254 }
5355 proxy := httputil .NewSingleHostReverseProxy (target )
5456
55- // Add response modifier for S3 upload
5657 proxy .ModifyResponse = func (resp * http.Response ) error {
57- // Only process successful responses
5858 if resp .StatusCode == http .StatusOK {
59- // Read the entire response body
60- body , err := io .ReadAll (resp .Body )
61- if err != nil {
62- slog .Error ("Failed to read response body" , "error" , err )
63- return err
64- }
65- resp .Body .Close ()
66-
67- // Start S3 upload in a goroutine as best-effort
59+ var buf bytes.Buffer
60+ teeReader := io .TeeReader (resp .Body , & buf )
61+
6862 go func () {
69- ctx := context .Background ()
70- if err := uploadToS3 (ctx , s3Client , cfg , bytes .NewReader (body ), resp .Request .URL .Path ); err != nil {
63+ if err := uploadToS3 (context .Background (), uploader , cfg , & buf , resp .Request .URL .Path ); err != nil {
7164 slog .Error ("S3 upload failed" , "error" , err )
7265 }
7366 }()
7467
75- // Set the response body to a new reader with the cached content
76- resp .Body = io .NopCloser (bytes .NewReader (body ))
68+ resp .Body = io .NopCloser (teeReader )
7769 }
7870 return nil
7971 }
8072
81- http .HandleFunc ("/" , proxyHandler (s3Client , proxy ))
82- slog .Info ("Starting proxy server" , "port" , os .Getenv ("PROXY_HTTP_PORT" ))
83- err = http .ListenAndServe (fmt .Sprintf (":%s" , os .Getenv ("PROXY_HTTP_PORT" )), nil )
84- if err != nil {
85- slog .Error ("Server failed" , "error" , err )
86- }
87- }
88-
89- func proxyHandler (s3Client * s3.Client , proxy * httputil.ReverseProxy ) http.HandlerFunc {
90- return func (w http.ResponseWriter , r * http.Request ) {
91- slog .Info ("Proxying request" , "path" , r .URL .Path )
73+ http .HandleFunc ("/" , func (w http.ResponseWriter , r * http.Request ) {
9274 proxy .ServeHTTP (w , r )
93- }
94- }
75+ })
9576
96- // generateS3Key creates a hash from the imgproxy URL parts after the signature
97- func generateS3Key (path string ) string {
98- // Split path into components
99- parts := strings .Split (strings .TrimPrefix (path , "/" ), "/" )
100- if len (parts ) < 3 {
101- return ""
77+ if err := http .ListenAndServe (fmt .Sprintf (":%s" , os .Getenv ("PROXY_HTTP_PORT" )), nil ); err != nil {
78+ slog .Error ("Server failed" , "error" , err )
10279 }
103-
104- // Everything after signature (processing options and source URL)
105- toHash := strings .Join (parts [1 :], "/" )
106-
107- // Generate MD5 hash
108- hash := md5 .Sum ([]byte (toHash ))
109- return hex .EncodeToString (hash [:])
11080}
11181
112- func uploadToS3 (ctx context.Context , client * s3.Client , cfg Config , r io.Reader , path string ) error {
113- // Generate key from URL path
82+ func uploadToS3 (ctx context.Context , uploader * manager.Uploader , cfg Config , r io.Reader , path string ) error {
11483 key := generateS3Key (path )
115- if key == "" {
116- return fmt .Errorf ("invalid URL path format" )
117- }
11884
119- slog .Info ("Uploading to S3" , "path" , path , "key" , key )
120- _ , err := client .PutObject (ctx , & s3.PutObjectInput {
85+ _ , err := uploader .Upload (ctx , & s3.PutObjectInput {
12186 Bucket : aws .String (cfg .S3Bucket ),
12287 Key : aws .String (key ),
12388 Body : r ,
12489 })
12590
126- return err
91+ if err != nil {
92+ slog .Error ("Upload failed" , "path" , path , "key" , key , "error" , err )
93+ return err
94+ }
95+
96+ slog .Info ("Uploaded to S3" , "path" , path , "bucket" , cfg .S3Bucket , "key" , key )
97+ return nil
98+ }
99+
100+ // generateS3Key creates a hash from the imgproxy URL path
101+ func generateS3Key (path string ) string {
102+ hash := md5 .Sum ([]byte (path ))
103+ return hex .EncodeToString (hash [:])
127104}
128105
129106func initS3Client (cfg Config ) * s3.Client {
130- customResolver := aws .EndpointResolverWithOptionsFunc (
131- func (service , region string , opts ... interface {}) (aws.Endpoint , error ) {
132- if cfg .S3Endpoint != "" {
133- return aws.Endpoint {
134- PartitionID : "aws" ,
135- URL : cfg .S3Endpoint ,
136- SigningRegion : cfg .S3Region ,
137- }, nil
138- }
139- return aws.Endpoint {}, & aws.EndpointNotFoundError {}
140- },
141- )
142-
143- awsCfg , err := config .LoadDefaultConfig (context .Background (),
144- config .WithEndpointResolverWithOptions (customResolver ),
145- config .WithRegion (cfg .S3Region ),
146- )
107+ sdkConfig , err := config .LoadDefaultConfig (context .Background ())
147108 if err != nil {
148109 slog .Error ("Failed to initialize AWS config" , "error" , err )
149110 os .Exit (1 )
150111 }
151112
152- // Create S3 client with path-style addressing required for LocalStack
153- return s3 .NewFromConfig (awsCfg , func (o * s3.Options ) {
113+ svc := s3 .NewFromConfig (sdkConfig , func (o * s3.Options ) {
114+ o .BaseEndpoint = aws .String (cfg .S3Endpoint )
115+ o .Region = cfg .S3Region
154116 o .UsePathStyle = true
155117 })
118+
119+ return svc
156120}
0 commit comments