Last active
October 28, 2021 08:38
-
-
Save inesusvet/fec89b5f7f826da8d48e7094ffb30b02 to your computer and use it in GitHub Desktop.
Simple HTTP server to accept file uploads and serve files from work dir
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package main | |
import ( | |
"fmt" | |
"io" | |
"io/ioutil" | |
"log" | |
"net/http" | |
"os" | |
"path" | |
"sort" | |
"strconv" | |
"strings" | |
) | |
var workdir string | |
var port uint64 | |
// fileExists checks if a file exists and is not a directory before we | |
// try using it to prevent further errors. | |
func fileExists(filename string) bool { | |
info, err := os.Stat(filename) | |
if os.IsNotExist(err) { | |
return false | |
} | |
return !info.IsDir() | |
} | |
// dump copies contents from a reader to a file in the workdir. | |
// It creates required subdirectories if required | |
func dump(workdir string, filename string, reader io.Reader) error { | |
tmp, err := ioutil.TempFile("", "upload-*") | |
if err != nil { | |
log.Println(err) | |
return err | |
} | |
tmp_filename := tmp.Name() // default file mode is 600 | |
err = os.Chmod(tmp_filename, os.FileMode(0644)) | |
if err != nil { | |
log.Println(err) | |
return err | |
} | |
if _, err := io.Copy(tmp, reader); err != nil { | |
log.Println(err) | |
return err | |
} | |
if err := tmp.Close(); err != nil { | |
log.Println(err) | |
return err | |
} | |
target_filename := path.Join(workdir, filename) | |
target_dir := path.Dir(target_filename) | |
err = os.MkdirAll(target_dir, os.FileMode(0755)) | |
if err != nil { | |
log.Println(err) | |
return err | |
} | |
err = os.Rename(tmp_filename, target_filename) | |
if err != nil { | |
log.Println(err) | |
return err | |
} | |
return nil | |
} | |
func postHandler(w http.ResponseWriter, r *http.Request) { | |
r.ParseMultipartForm(10 << 20) | |
if r.MultipartForm == nil { | |
http.Error(w, "No multipart form payload parsed", 400) | |
return | |
} | |
var name string | |
var keys []string | |
for key, _ := range r.MultipartForm.File { | |
keys = append(keys, key) | |
} | |
if len(keys) < 1 { | |
http.Error(w, "At least one file is required in POST payload", 400) | |
return | |
} | |
sort.Strings(keys) | |
name = keys[0] | |
if name == "" { | |
log.Println("No file name was specified") | |
http.Error(w, "No file uploaded", 400) | |
return | |
} | |
file, _, err := r.FormFile(name) | |
if err != nil { | |
log.Println(err) | |
http.Error(w, "Cannot read file from POST payload", 400) | |
return | |
} | |
defer file.Close() | |
relative_path := strings.Trim(r.URL.Path, "/") | |
err = dump(workdir, relative_path, file) | |
if err != nil { | |
http.Error(w, "Cannot write file to disk", 500) | |
return | |
} | |
} | |
func getHandler(w http.ResponseWriter, r *http.Request) { | |
relative_path := strings.Trim(r.URL.Path, "/") | |
full_path := path.Join(workdir, relative_path) | |
if fileExists(full_path) { | |
http.ServeFile(w, r, full_path) | |
} else { | |
http.NotFound(w, r) | |
} | |
} | |
func deleteHandler(w http.ResponseWriter, r *http.Request) { | |
relative_path := strings.Trim(r.URL.Path, "/") | |
full_path := path.Join(workdir, relative_path) | |
if !fileExists(full_path) { | |
http.NotFound(w, r) | |
return | |
} | |
err := os.Remove(full_path) | |
if err != nil { | |
http.Error(w, "Cannot delete file", 500) | |
} | |
} | |
func handler(w http.ResponseWriter, r *http.Request) { | |
log.Printf("%v %v\n", r.Method, r.URL.Path) | |
if r.Method == http.MethodGet { | |
getHandler(w, r) | |
return | |
} | |
if r.Method == http.MethodPost { | |
postHandler(w, r) | |
return | |
} | |
if r.Method == http.MethodDelete { | |
deleteHandler(w, r) | |
return | |
} | |
http.Error(w, "Only GET, POST and DELETE methods are allowed", 405) | |
} | |
func init() { | |
workdir = os.Getenv("WORKDIR") | |
if workdir == "" { | |
cwd, err := os.Getwd() | |
if err != nil { | |
log.Fatal(err) | |
} | |
workdir = cwd | |
} | |
port_text := os.Getenv("PORT") | |
if port_text != "" { | |
parsed, err := strconv.ParseUint(port_text, 10, 32) | |
if err != nil { | |
log.Fatal(err) | |
} | |
port = parsed | |
} else { | |
port = 8080 | |
} | |
} | |
func main() { | |
fmt.Printf("Using %v as work directory\n", workdir) | |
http.HandleFunc("/", handler) | |
port_text := fmt.Sprintf(":%v", port) | |
fmt.Println("Start listening on port", port_text) | |
log.Fatal(http.ListenAndServe(port_text, nil)) | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
version: "3" | |
services: | |
catcher: | |
build: . | |
environment: | |
WORKDIR: "/tmp" | |
PORT: "8080" | |
ports: | |
- "8080:8080" | |
volumes: | |
- "./data:/tmp" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
FROM golang:1.15.5-alpine AS builder | |
ADD catcher.go /go/ | |
RUN go build catcher.go | |
FROM alpine | |
COPY --from=builder /go/catcher /bin | |
CMD ["/bin/catcher"] |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment