用于文件上传但不使用ParseMultipartForm的GO REST端点

Newbie here. I'm trying to build a web server with a RESTful API so that I can test a curl command for file uploads. I was able to create the web server and an endpoint for file uploads.

Here is my upload endpoint:

func Upload(w http.ResponseWriter, r *http.Request) {
    if err := r.ParseMultipartForm(MAX_MEMORY); err != nil {
        log.Println(err)
        http.Error(w, err.Error(), http.StatusForbidden)
    }

    fmt.Println("Endpoint hit: Upload")

    for key, value := range r.MultipartForm.Value {
        fmt.Fprintf(w, "%s:%s ", key, value)
        log.Printf("%s:%s", key, value)
    }

    for _, fileHeaders := range r.MultipartForm.File {
        for _, fileHeader := range fileHeaders {
            file, _ := fileHeader.Open()
            path := fmt.Sprintf("files/%s", fileHeader.Filename)
            buf, _ := ioutil.ReadAll(file)
            ioutil.WriteFile(path, buf, os.ModePerm)
            log.Println(http.StatusOK, fmt.Sprintf("file %s has been uploaded", fileHeader.Filename))
        }
    }
}

This endpoint works with the following curl command:

curl -F 'data=@/path/to/file/foo.tar.gz' localhost:8080/upload

However, this curl command does not:

curl -f -s -S -T /path/to/file/foo.tar.gz http://localhost:8080/upload
curl: (22) The requested URL returned error: 405 Method Not Allowed

I need help creating an endpoint that will accept

curl -f -s -S -T /path/to/file/foo.tar.gz http://localhost:8080/upload

Thank you.

Edit: Here is my routes.go file.

package main

import (
    "net/http"
    "github.com/gorilla/mux"
)

type Route struct {
    Name        string
    Method      string
    Pattern     string
    HandlerFunc http.HandlerFunc
}

type Routes []Route

func NewRouter() *mux.Router {
    router := mux.NewRouter().StrictSlash(true)
    for _, route := range routes {
        router.
        Methods(route.Method).
        Path(route.Pattern).
        Name(route.Name).
        Handler(route.HandlerFunc)
    }

    return router
}


var routes = Routes{
    Route{
        "Index",
        "GET",
        "/",
        Index,
    },
    Route{
        "Upload",
        "POST",
        "/upload",
        Upload,
    },
}

Using curl -f -s -S -T /path/to/file/foo.tar.gz http://localhost:8080/upload without any additional options that would set the content type, if there are such options (i'm not sure, my curl knowledge is almost non existent), you are sending a request with minimal headers, none of them indicating the type of the content.

For example logging the r.Header will print something like this:

map[Accept:[*/*] Content-Length:[18] Expect:[100-continue] User-Agent:[curl/7.54.0]]

This means that all that multipart code in your Upload handler is simply not necessary.


The content of the body is what you want, so you can use io.Copy to store that body into a file.

func Upload(w http.ResponseWriter, r *http.Request) {
    f, err := os.Create("filename")
    if err != nil {
        panic(err)
    }
    defer f.Close()

    if _, err := io.Copy(f, r.Body); err != nil {
        panic(err)
    }
    if err := f.Sync(); err != nil {
        panic(err)
    }
    fmt.Fprintln(w, "upload done...")
}

Since you're using curl -f -s -S -T ... the Upload handler does not know what the name, nor the type, of the file being uploaded is, you will have to generate a random name, or a timestamp name, or whatever in order to be able to create and store more than just one file.

You could also pass some of the file info (name, type) in the query parameters but i'm not sure if that satisfies your requirements. E.g.

curl -f -s -S -T /path/to/file/foo.tar.gz http://localhost:8080/upload?name=foo.tar.gz