Go的http庫處理multipart的兩個問題解決

猛禽大叔發表於2018-07-17

問題說明

最近因為專案需要搞了一些golang的開發,碰到不少問題,除了之前踩的那個關於ActiveMQ的坑以外,最近又在用Gin進行Web開發踩到一個http庫裡的坑。

Gin本身只是一個簡單的包裝,大部分功能還是基於net/http這個標準庫,其中就包括了Request。

這次碰到的兩個問題都是由於一個原因引起的:那就是我在處理POST請求接收檔案上傳的時候,有時會丟失請求引數(上傳檔案可以收到,但一起傳過來的Post引數為空)。

經過返復測試,發現用python的requests發過來的請求是可以正常處理的,但是用Java發過來的請求就出錯。

為了除錯這個問題,我想把請求內容打出日誌來,於是碰到了第一個問題:

Request.Body是一個ReaderCloser緩衝,只能讀一次,如果我讀出來打日誌,那麼後面的引數肯定都為空,如果取了引數,則這個緩衝也為空。

當然,第二個問題就是那個POST請求丟失的問題,其原因後面說。

無損讀取Request.Body的方法

按官方文件和Stackoverflow上找來的各自方法都嘗試過,比如讀完再寫回,或者是用Bind,都不能正常取到內容,最後是在一個外國人的BLOG的評論裡看到有人提到一個方法:

httputil.DumpRequest

最終解決了這個問題,寫了一個除錯專用的方法:

func req_body(c *gin.Context) string {
    body, err := httputil.DumpRequest(c.Request, true)
    if err != nil {
        println(err)
    }
    return string(body[:1024])
}
複製程式碼

POST引數丟失問題

有了上面這個方法,我終於找到問題的所在了。下面分別是python和Java的請求內容,注意其中的區別:

python:

Connection: close
Accept: */*
Accept-Encoding: gzip, deflate
Connection: close
Content-Length: xxx
Content-Type: multipart/form-data; boundary=5b9ca15e3fd14316b6a4b03cb4ee4de2
User-Agent: python-requests/2.12.3
X-Forwarded-For: 120.36.xx
X-Real-Ip: 120.36.xx

--5b9ca15e3fd14316b6a4b03cb4ee4de2
Content-Disposition: form-data; name="index"

1
--5b9ca15e3fd14316b6a4b03cb4ee4de2
Content-Disposition: form-data; name="checksum"

testchecksum
複製程式碼

Java:

Connection: close
Connection: close
Content-Length: xxx
Content-Type: multipart/form-data; boundary=JqsRkZ2MjzUUb4YOaDn-FvuI_vsjn0sXhhZZ
d
User-Agent: Apache-HttpClient/4.5.2 (Java/1.7.0_79)
X-Forwarded-For: 1.192.xx
X-Real-Ip: 1.192.xx

--JqsRkZ2MjzUUb4YOaDn-FvuI_vsjn0sXhhZZd
Content-Disposition: form-data; name="index"
Content-Type: text/plain; charset=ISO-8859-1
Content-Transfer-Encoding: 8bit

2
--JqsRkZ2MjzUUb4YOaDn-FvuI_vsjn0sXhhZZd
Content-Disposition: form-data; name="checksum"
Content-Type: text/plain; charset=ISO-8859-1
Content-Transfer-Encoding: 8bit

testchecksum
複製程式碼

顯然,Java版的每個part頭裡多了一些東西,應該就是這些東西導致了http對multipart的解析失敗。

但是我反覆試了PostForm和MultipartForm,都無法取得POST引數——PostForm取到的都是空,而MultipartForm取得的form.Value也是空。

最後是我偶然想到試試看MultipartForm的form.File,結果發現原來引數都被解析到這裡了……真不知道這個http標準庫是怎麼設計的,難道這麼多Go使用者都沒有發現嗎?還是說Go的使用者實際上並沒有那麼多?

總之為了解決這個問題,只好自己寫了一點程式碼來把File裡的引數轉到Value裡去:

func req_getpart(v *multipart.FileHeader) (string, error) {
    f, err := v.Open()
    if err != nil {
        println("Open fail")
        return "", err
    }
    buf, err := ioutil.ReadAll(f)
    defer f.Close()
    if err != nil {
        println("Read fail")
        return "", err
    }
    return string(buf), nil
}

func req_multipart(c *gin.Context) *multipart.Form {
    form, _ := c.MultipartForm()
    if len(form.Value) == 0 && len(form.File) > 0 {
        for k, v := range form.File {
            if len(v) > 0 {
                buf, err := req_getpart(v[0])
                if err != nil {
                    continue
                }
                form.Value[k] = append(form.Value[k], buf)
            }
        }
        for k, _ := range form.Value {
            delete(form.File, k)
        }
    }
    return form
}
複製程式碼

相關文章