用go处理文件上传

时间:2015-11-18 03:11:47

标签: javascript html http go multipartform-data

我最近刚开始玩,所以我仍然是个菜鸟,抱歉,如果我犯了太多错误。我一直试图解决这个问题很长一段时间,但我只是不明白发生了什么。在我的main.go文件中,我有一个主要功能:

func main() {
    http.HandleFunc("/", handler)
    http.HandleFunc("/submit/", submit)
    log.Fatal(http.ListenAndServe(":8080", nil))
}

处理程序函数如下所示:

func handler(w http.ResponseWriter, r *http.Request) {
    data, _ := ioutil.ReadFile("web/index.html")
    w.Write(data)
}

我知道这不是服务网站的最佳方式 提交函数如下所示:

func submit(w http.ResponseWriter, r *http.Request) {
    log.Println("METHOD IS " + r.Method + " AND CONTENT-TYPE IS " + r.Header.Get("Content-Type"))
    r.ParseMultipartForm(32 << 20)
    file, header, err := r.FormFile("uploadFile")
    if err != nil {
        json.NewEncoder(w).Encode(Response{err.Error(), true})
        return
    }
    defer file.Close()

    out, err := os.Create("/tmp/file_" + time.Now().String() + ".png")
    if err != nil {
        json.NewEncoder(w).Encode(Response{err.Error(), true})
        return
    }
    defer out.Close()

    _, err = io.Copy(out, file)
    if err != nil {
        json.NewEncoder(w).Encode(Response{err.Error(), true})
        return
    }

    json.NewEncoder(w).Encode(Response{"File '" + header.Filename + "' submited successfully", false})
}

问题是当执行提交函数时,r.MethodGETr.Header.Get("Content-Type")是一个空字符串,然后它继续直到第一个如果r.FormFile返回以下错误: request Content-Type isn't multipart/form-data 我不明白为什么r.Method总是GET而且没有Content-Type。我试图以许多不同的方式做index.html,但r.Method总是GET,而Content-Type是空的。这是index.html中上传文件的函数:

function upload() {
    var formData = new FormData();
    formData.append('uploadFile', document.querySelector('#file-input').files[0]);
    fetch('/submit', {
        method: 'post',
        headers: {
          "Content-Type": "multipart/form-data"
        },
        body: formData
    }).then(function json(response) {
        return response.json()
    }).then(function(data) {
        window.console.log('Request succeeded with JSON response', data);
    }).catch(function(error) {
        window.console.log('Request failed', error);
    });
}

这是HTML:

<input id="file-input" type="file" name="uploadFile" />

请注意,标记不在标记内,我认为这可能是问题所以我将函数和HTML都改为这样:

function upload() {
    fetch('/submit', {
        method: 'post',
        headers: {
          "Content-Type": "multipart/form-data"
        },
        body: new FormData(document.querySelector('#form')
    }).then(function json(response) {
        return response.json()
    }).then(function(data) {
        window.console.log('Request succeeded with JSON response', data);
    }).catch(function(error) {
        window.console.log('Request failed', error);
    });
}

<form id="form" method="post" enctype="multipart/form-data" action="/submit"><input id="file-input" type="file" name="uploadFile" /></form>

但那也不起作用。我用Google搜索了如何使用fetch()以及如何从go上接收文件,我发现它们与我的相似,我不知道我做错了什么。

更新 使用curl -v -F 'uploadFile=@\"C:/Users/raul-/Desktop/test.png\"' http://localhost:8080/submit后,我得到以下输出:

* Trying ::1...
* Connected to localhost (::1) port 8080 (#0)
> POST /submit HTTP/1.1
> Host: localhost:8080
> User-Agent: curl/7.45.0
> Accept: */*
> Content-Length: 522
> Expect: 100-continue
> Content-Type: multipart/form-data; boundary=---------------------------a17d4e54fcec53f8
>
< HTTP/1.1 301 Moved Permanently
< Location: /submit/
< Date: Wed, 18 Nov 2015 14:48:38 GMT
< Content-Length: 0
< Content-Type: text/plain; charset=utf-8
* HTTP error before end of send, stop sending
<
* Closing connection 0

我正在运行的控制台go run main.go在使用curl时没有输出任何内容。

2 个答案:

答案 0 :(得分:7)

我设法解决了我的问题,所以这是以防其他人需要它。并且感谢@JiangYD使用curl测试服务器的提示。

TL; DR

  • 我写了http.HandleFunc("/submit/", submit)但是我正在向/submit发出POST请求(请注意丢失的斜杠)&lt;&lt;这很重要,因为重定向
  • 不要自己指定内容类型,浏览器会为您执行

LONG ANSWER

我做@JiangYD说并使用 curl来测试服务器,我用响应更新了我的回答。我发现奇怪的是有一个301重定向,因为我没有把它放在那里,我决定使用以下curl命令

curl -v -F 'uploadFile=@\"C:/Users/raul-/Desktop/test.png\"' -L http://localhost:8080/submit

(注意 -L )那样curl跟着重定向,虽然它再次失败,因为,当重定向时,curl从POST切换到GET但是通过该响应我发现了对{的请求{1}}被重定向到/submit,我记得我是如何在/submit/函数中编写的。

在确定它仍然失败后,响应为main并且通过查看http: no such file代码我发现它意味着该字段不存在,所以我做了一个快速测试迭代获得的所有字段名称:

net/http

我得到for k, _ := range r.MultipartForm.File { log.Println(k) } 作为字段名称,我删除了curl命令中的单引号,现在它完全上传了文件

但它并没有结束,我现在知道服务器工作正常,因为我可以使用'uploadFile上传文件但是当我尝试通过托管网页上传时,我收到了一个错误:{ {1}}。

所以我发现我假设在标题中包含边界,我将fetch改为这样的东西:

curl

我像这样计算边界:

no multipart boundary param in Content-Type

但我仍然遇到错误:fetch('/submit', { method: 'post', headers: { "Content-Type": "multipart/form-data; boundary=------------------------" + boundary }, body: formData}) 那么你如何计算边界?我读了规范https://html.spec.whatwg.org/multipage/forms.html#multipart/form-data-encoding-algorithm并发现边界是由编码文件的算法计算的,在我的例子中是FormData,FormData API没有暴露出获得边界的方法,但我发现了如果你没有指定它,浏览器会自动添加带有var boundary = Math.random().toString().substr(2); 的内容类型和边界,所以我从multipart: NextPart: EOF调用中删除了header对象,现在它终于可以工作了!

答案 1 :(得分:0)

完全删除标头实际上是可行的。特别是通过fetch或axios发送请求时。

// customHook
const useLoadData = (startLoading, userId, hasError) => {
  const [loadDone, setLoadDone] = useState(false);
  const loadWebsite = async(userId) => {
     await apiService.call(...);
     console.log('service call is completed');
     dispatch(someAction);
     setLoadDone(true);
  }

  useEffect(() => {
    // define async function inside useEffect
    const loadData = async () => {
       if (!hasError) {
          await loadWebsite();
       }
    }

    // call the above function based on flag
    if (startLoading) {
       await loadData();
       // setLoadDone(true); doesn't work here
    }
  }, [startLoading]);

  return loadDone;
}