Skip to content

Instantly share code, notes, and snippets.

@inesusvet
Last active October 28, 2021 08:38
Show Gist options
  • Save inesusvet/fec89b5f7f826da8d48e7094ffb30b02 to your computer and use it in GitHub Desktop.
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
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))
}
version: "3"
services:
catcher:
build: .
environment:
WORKDIR: "/tmp"
PORT: "8080"
ports:
- "8080:8080"
volumes:
- "./data:/tmp"
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