41

Golang编写客户端、服务端并实现文件传输

 5 years ago
source link: https://studygolang.com/articles/18234?amp%3Butm_medium=referral
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

建立Server

server := &http.Server{
        Addr:    ":" + port,
        Handler: mux}

注意mux是一个实现了路由的对象。其带有ServeHttp方法。其实也可以是一般的HandlerFunc,只要是实现了ServeHTTP的对象都行。

handler函数原型为:

func HandlerDownload(w http.ResponseWriter, r *http.Request)

通过http.HandleFunc()函数,可将其改变为带有ServeHTTP方法的对象。

建立client request

client := &http.Client{}//建立客户端
    req, _ := http.NewRequest("GET", "http://baidu.com", nil)//建立request对象,第三个参数是body内容,建立之后可以设置head参数等。
    httputil.DumpRequest(req, false)//dump出远程服务器返回的信息
    resp, _ := client.Do(req)//客户端发出request请求
    body, _ := ioutil.ReadAll(resp.Body)
    defer resp.Body.Close()
    log.Println(string(body))

客户端与服务端信息传输,content-type主要为二进制或字符串。字符串可以自定义编码,二进制就是文件或图片等。不同的content-type含义:

x-www-form-urlencoded:客户端提交表格时候的编码方式,类似于url编码

form-data:也是表格,但是支持文件,不同key-value之间要用boundary分隔

binary:文件,只能是单个文件

json/html/plain text/xml等:都是字符串,交由浏览器解析

测试Handler

测试server的Handler函数,首先需要构建request对象,然后构建responseRecorder对象,记录handler的返回值。

func testHandler(t *testing.T) {
    param := url.Values{}
    param["page"] = []string{"1"}
    param["page_size"] = []string{"100"}
    //创建 request
    req, _ := http.NewRequest("GET", "/"+param.Encode(), nil)
    req.Header.Set("Authorization", "Token abc123")
    //创建 response
    rr := httptest.NewRecorder()
    //只测试函数,跳过mux路由
    HandlerTest(rr, req)

    log.Println(rr.Body.String())
    log.Println(rr.Result())
    log.Println(rr.Code)

}

文件上传

func testUpload(t *testing.T) {
    var client *http.Client
    var remoteURL string

        //构建server 对象
        ts := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
            b, err := httputil.DumpRequest(r, true)
            if err != nil {
                panic(err)
            }
            fmt.Printf("%s", b)
        }))
        defer ts.Close()
        client = ts.Client()
        remoteURL = ts.URL

    //模拟客户端提交表格
    values := map[string]io.Reader{
        "file":   mustOpen("123.txt"), // 123.txt是本地文件
        "key":  strings.NewReader("hello world!"),
        "key2": strings.NewReader("hello world2!"),
    }
    err := Upload(client, remoteURL, values)
    if err != nil {
        panic(err)
    }
}
func Upload(client *http.Client, url string, values map[string]io.Reader) (err error) {
    // 构建multipart,然后post给服务端
    var b bytes.Buffer
    w := multipart.NewWriter(&b)
    for key, r := range values {
        var fw io.Writer
        if x, ok := r.(io.Closer); ok {
            defer x.Close()
        }
        // 添加文件
        if x, ok := r.(*os.File); ok {
            if fw, err = w.CreateFormFile(key, x.Name()); err != nil {
                return
            }
        } else {//添加字符串
            // Add other fields
            if fw, err = w.CreateFormField(key); err != nil {
                return
            }
        }
        if _, err = io.Copy(fw, r); err != nil {
            return err
        }

    }
    // close动作会在末端写入 boundary.
    w.Close()

    req, err := http.NewRequest("POST", url, &b)
    if err != nil {
        return
    }
    // form-data格式,且自动生成分隔符.
//例Content-Type: multipart/form-data; boundary=d76....d29
    req.Header.Set("Content-Type", w.FormDataContentType())

    // 提交 request
    res, err := client.Do(req)
    if err != nil {
        return
    }

    // 检查返回状态吗
    if res.StatusCode != http.StatusOK {
        err = fmt.Errorf("bad status: %s", res.Status)
    }
    return
}

func mustOpen(f string) *os.File {
    r, err := os.Open(f)
    if err != nil {
        panic(err)
    }
    return r
}

form-data的body样例:

//文件部分

--d6...9

Content-Disposition: form-data; name="file"; filename="123.txt"

Content-Type: application/octet-stream

//key value部分:w.CreateFormField(key);

--d6.....9

Content-Disposition: form-data; name="file"

//最末端还有分隔符

--d6.....9--

server端处理文件

保存客户端上传的文件

Request.FormFile("FormFile")得到file对象和file头

然后用os.File创建本地文件

最后用io.Copy将request文件保存到目标文件。

向客户端推送文件

os.File打开文件,

用file.Stat()获取文件属性

设置文件头

w.Header().Set("Content-Type", "application/x-zip-compressed")

//也可用http.DetectContentType获得文件类型:application/zip

w.Header().Set("Content-Disposition", "attachment; filename="+fileSta.Name())

w.Header().Set("Content-Length", strconv.FormatInt(fileSta.Size(), 10))

推送文件

io.Copy(ResponseWriter,file)


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK