Simple S3-like server for development purposes, written in Go
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

Split server and handlers to separate module

+157 -150
-150
gos3dir.go
··· 1 1 package main 2 2 3 3 import ( 4 - "errors" 5 4 "flag" 6 - "io" 7 - "io/fs" 8 5 "log" 9 6 "net/http" 10 7 "os" 11 - "path/filepath" 12 8 ) 13 9 14 10 func addServerHeaders(serverName string) func(http.Handler) http.Handler { ··· 27 23 next.ServeHTTP(w, r) 28 24 }) 29 25 } 30 - } 31 - 32 - type server struct { 33 - rootDir string 34 - logger *log.Logger 35 - } 36 - 37 - func (s *server) ls(w http.ResponseWriter, r *http.Request) { 38 - root, err := os.OpenRoot(s.rootDir) 39 - if err != nil { 40 - http.Error(w, err.Error(), http.StatusInternalServerError) 41 - } 42 - defer root.Close() 43 - 44 - bucketName := r.PathValue("bucket") 45 - if bucketName == "" { 46 - // List available buckets 47 - files, err := fs.ReadDir(root.FS(), ".") 48 - if err != nil { 49 - http.Error(w, err.Error(), http.StatusInternalServerError) 50 - return 51 - } 52 - s.logger.Printf("Buckets: %s", files) 53 - } else { 54 - // FIXME: "The returned path ends in a slash only if it represents a root directory, 55 - // such as "/" on Unix or `C:\` on Windows." 56 - prefix := r.URL.Query().Get("prefix") 57 - path := filepath.Join(bucketName, prefix) 58 - s.logger.Printf("Path %s, original prefix %s", path, prefix) 59 - } 60 - 61 - // TODO: Probably requires actual XML 62 - w.WriteHeader(http.StatusNotImplemented) 63 - } 64 - 65 - func (s *server) mb(w http.ResponseWriter, r *http.Request) { 66 - root, err := os.OpenRoot(s.rootDir) 67 - if err != nil { 68 - http.Error(w, err.Error(), http.StatusInternalServerError) 69 - } 70 - defer root.Close() 71 - 72 - path := filepath.Join(r.PathValue("bucket"), r.PathValue("key")) 73 - 74 - bucketName := filepath.SplitList(path)[0] 75 - if err := root.Mkdir(bucketName, 0775); err != nil { 76 - if errors.Is(err, fs.ErrExist) { 77 - http.Error(w, "Your previous request to create the named bucket succeeded and you already own it.", http.StatusConflict) 78 - } else { 79 - http.Error(w, err.Error(), http.StatusInternalServerError) 80 - } 81 - return 82 - } 83 - 84 - w.WriteHeader(http.StatusOK) 85 - } 86 - 87 - func (s *server) rb(w http.ResponseWriter, r *http.Request) { 88 - root, err := os.OpenRoot(s.rootDir) 89 - if err != nil { 90 - http.Error(w, err.Error(), http.StatusInternalServerError) 91 - } 92 - defer root.Close() 93 - 94 - path := filepath.Join(r.PathValue("bucket"), r.PathValue("key")) 95 - 96 - bucketName := filepath.SplitList(path)[0] 97 - files, err := fs.ReadDir(root.FS(), bucketName) 98 - if err != nil { 99 - if errors.Is(err, fs.ErrNotExist) { 100 - http.Error(w, "The specified bucket does not exist", http.StatusConflict) 101 - } else { 102 - http.Error(w, err.Error(), http.StatusInternalServerError) 103 - } 104 - return 105 - } 106 - if len(files) > 0 { 107 - http.Error(w, "The bucket you tried to delete is not empty", http.StatusConflict) 108 - return 109 - } 110 - 111 - if err := root.Remove(bucketName); err != nil { 112 - http.Error(w, err.Error(), http.StatusInternalServerError) 113 - return 114 - } 115 - 116 - w.WriteHeader(http.StatusOK) 117 - } 118 - 119 - func (s *server) cp(w http.ResponseWriter, r *http.Request) { 120 - root, err := os.OpenRoot(s.rootDir) 121 - if err != nil { 122 - http.Error(w, err.Error(), http.StatusInternalServerError) 123 - } 124 - defer root.Close() 125 - 126 - path := filepath.Join(r.PathValue("bucket"), r.PathValue("key")) 127 - 128 - // Prevents `cp` from accidentally creating a new bucket if it doesn't exist 129 - bucketName := filepath.SplitList(path)[0] 130 - if _, err := root.Stat(bucketName); err != nil { 131 - if errors.Is(err, fs.ErrNotExist) { 132 - http.Error(w, "The specified bucket does not exist", http.StatusNotFound) 133 - } else { 134 - http.Error(w, err.Error(), http.StatusInternalServerError) 135 - } 136 - return 137 - } 138 - 139 - // Safely create nested directories if needed 140 - if err := root.MkdirAll(filepath.Dir(path), 0775); err != nil { 141 - http.Error(w, err.Error(), http.StatusInternalServerError) 142 - return 143 - } 144 - 145 - file, err := root.Create(path) 146 - if err != nil { 147 - http.Error(w, err.Error(), http.StatusInternalServerError) 148 - return 149 - } 150 - defer file.Close() 151 - 152 - // TODO: (Claude) "if io.Copy fails, you've already created the file. 153 - // Consider cleaning it up or using os.CreateTemp + os.Rename for atomicity." 154 - if _, err := io.Copy(file, r.Body); err != nil { 155 - http.Error(w, err.Error(), http.StatusInternalServerError) 156 - return 157 - } 158 - w.WriteHeader(http.StatusOK) 159 - } 160 - 161 - func (s *server) rm(w http.ResponseWriter, r *http.Request) { 162 - root, err := os.OpenRoot(s.rootDir) 163 - if err != nil { 164 - http.Error(w, err.Error(), http.StatusInternalServerError) 165 - } 166 - defer root.Close() 167 - 168 - // TODO: Dangling empty directory 169 - path := filepath.Join(r.PathValue("bucket"), r.PathValue("key")) 170 - if err := root.Remove(path); err != nil { 171 - http.Error(w, err.Error(), http.StatusInternalServerError) 172 - return 173 - } 174 - 175 - w.WriteHeader(http.StatusOK) 176 26 } 177 27 178 28 func main() {
+157
server.go
··· 1 + package main 2 + 3 + import ( 4 + "errors" 5 + "io" 6 + "io/fs" 7 + "log" 8 + "net/http" 9 + "os" 10 + "path/filepath" 11 + ) 12 + 13 + type server struct { 14 + rootDir string 15 + logger *log.Logger 16 + } 17 + 18 + func (s *server) ls(w http.ResponseWriter, r *http.Request) { 19 + root, err := os.OpenRoot(s.rootDir) 20 + if err != nil { 21 + http.Error(w, err.Error(), http.StatusInternalServerError) 22 + } 23 + defer root.Close() 24 + 25 + bucketName := r.PathValue("bucket") 26 + if bucketName == "" { 27 + // List available buckets 28 + files, err := fs.ReadDir(root.FS(), ".") 29 + if err != nil { 30 + http.Error(w, err.Error(), http.StatusInternalServerError) 31 + return 32 + } 33 + s.logger.Printf("Buckets: %s", files) 34 + } else { 35 + // FIXME: "The returned path ends in a slash only if it represents a root directory, 36 + // such as "/" on Unix or `C:\` on Windows." 37 + prefix := r.URL.Query().Get("prefix") 38 + path := filepath.Join(bucketName, prefix) 39 + s.logger.Printf("Path %s, original prefix %s", path, prefix) 40 + } 41 + 42 + // TODO: Probably requires actual XML 43 + w.WriteHeader(http.StatusNotImplemented) 44 + } 45 + 46 + func (s *server) mb(w http.ResponseWriter, r *http.Request) { 47 + root, err := os.OpenRoot(s.rootDir) 48 + if err != nil { 49 + http.Error(w, err.Error(), http.StatusInternalServerError) 50 + } 51 + defer root.Close() 52 + 53 + path := filepath.Join(r.PathValue("bucket"), r.PathValue("key")) 54 + 55 + bucketName := filepath.SplitList(path)[0] 56 + if err := root.Mkdir(bucketName, 0775); err != nil { 57 + if errors.Is(err, fs.ErrExist) { 58 + http.Error(w, "Your previous request to create the named bucket succeeded and you already own it.", http.StatusConflict) 59 + } else { 60 + http.Error(w, err.Error(), http.StatusInternalServerError) 61 + } 62 + return 63 + } 64 + 65 + w.WriteHeader(http.StatusOK) 66 + } 67 + 68 + func (s *server) rb(w http.ResponseWriter, r *http.Request) { 69 + root, err := os.OpenRoot(s.rootDir) 70 + if err != nil { 71 + http.Error(w, err.Error(), http.StatusInternalServerError) 72 + } 73 + defer root.Close() 74 + 75 + path := filepath.Join(r.PathValue("bucket"), r.PathValue("key")) 76 + 77 + bucketName := filepath.SplitList(path)[0] 78 + files, err := fs.ReadDir(root.FS(), bucketName) 79 + if err != nil { 80 + if errors.Is(err, fs.ErrNotExist) { 81 + http.Error(w, "The specified bucket does not exist", http.StatusConflict) 82 + } else { 83 + http.Error(w, err.Error(), http.StatusInternalServerError) 84 + } 85 + return 86 + } 87 + if len(files) > 0 { 88 + http.Error(w, "The bucket you tried to delete is not empty", http.StatusConflict) 89 + return 90 + } 91 + 92 + if err := root.Remove(bucketName); err != nil { 93 + http.Error(w, err.Error(), http.StatusInternalServerError) 94 + return 95 + } 96 + 97 + w.WriteHeader(http.StatusOK) 98 + } 99 + 100 + func (s *server) cp(w http.ResponseWriter, r *http.Request) { 101 + root, err := os.OpenRoot(s.rootDir) 102 + if err != nil { 103 + http.Error(w, err.Error(), http.StatusInternalServerError) 104 + } 105 + defer root.Close() 106 + 107 + path := filepath.Join(r.PathValue("bucket"), r.PathValue("key")) 108 + 109 + // Prevents `cp` from accidentally creating a new bucket if it doesn't exist 110 + bucketName := filepath.SplitList(path)[0] 111 + if _, err := root.Stat(bucketName); err != nil { 112 + if errors.Is(err, fs.ErrNotExist) { 113 + http.Error(w, "The specified bucket does not exist", http.StatusNotFound) 114 + } else { 115 + http.Error(w, err.Error(), http.StatusInternalServerError) 116 + } 117 + return 118 + } 119 + 120 + // Safely create nested directories if needed 121 + if err := root.MkdirAll(filepath.Dir(path), 0775); err != nil { 122 + http.Error(w, err.Error(), http.StatusInternalServerError) 123 + return 124 + } 125 + 126 + file, err := root.Create(path) 127 + if err != nil { 128 + http.Error(w, err.Error(), http.StatusInternalServerError) 129 + return 130 + } 131 + defer file.Close() 132 + 133 + // TODO: (Claude) "if io.Copy fails, you've already created the file. 134 + // Consider cleaning it up or using os.CreateTemp + os.Rename for atomicity." 135 + if _, err := io.Copy(file, r.Body); err != nil { 136 + http.Error(w, err.Error(), http.StatusInternalServerError) 137 + return 138 + } 139 + w.WriteHeader(http.StatusOK) 140 + } 141 + 142 + func (s *server) rm(w http.ResponseWriter, r *http.Request) { 143 + root, err := os.OpenRoot(s.rootDir) 144 + if err != nil { 145 + http.Error(w, err.Error(), http.StatusInternalServerError) 146 + } 147 + defer root.Close() 148 + 149 + // TODO: Dangling empty directory 150 + path := filepath.Join(r.PathValue("bucket"), r.PathValue("key")) 151 + if err := root.Remove(path); err != nil { 152 + http.Error(w, err.Error(), http.StatusInternalServerError) 153 + return 154 + } 155 + 156 + w.WriteHeader(http.StatusOK) 157 + }