Http文件上传下载

HTTP文件上传

简单来说,文件上传可分为三步:

  1. 组织请求体
  2. 设置Content-Type
  3. 发送Post请求

组织请求体

Request Body(请求体)常用于Post请求上,Get请求也可以有,只是约定俗成的服务端一般会忽略调GET的请求体

设置Content-Type

用于定义网络文件的类型和网页的编码,决定浏览器将以什么形式、什么编码读取这个文件
常见的Content-Type有如下几种:

  1. application/x-www-form-urlencoded:默认的表单提交的格式,是将表单中的数据按照urlencoded方式返送,类型于get请求中所带的参数
//模拟表单参数
data := make(url.Values)
data.Set("username", "aaa")
data.Set("password", "123")
fmt.Println(data.Encode()) //password=123456&username=poloxue

  1. application/json:以json的格式提交数据
  2. text/xml:以xml的格式提交数据
  3. text/plain:纯文本的数据提交方式
  4. application/octet-stream:二进制流的方式提交数据

文件上传

文件上传可以看成是表单提交的一种特殊的形式,表单提交默认的是使用Content-Type:application/x-www-form-urlencoded

如下是html表单提交的方式:

如下是go模拟表单提交的方式:

data := make(url.Values)
data.Set("username", "poloxue")
data.Set("password", "123456")

// 按 urlencoded 组织数据
body, _ := data.Encode()

//发送post请求
http.Post(
    "http://httpbin.org/post",
    "application/x-www-form-urlencoded",
    bytes.NewReader(body),
)

默认的表单提交方式是不支持文件上传的,因为文件需要通过二进制的方式进行传送,而表单提交的Content-Type:application/x-www-form-urlencoded并不是二进制方式发送数据的,
当然也可以通过body中二进制流的方式实现,但这种不能发送多个文件

RFC 1867添加了对文件上传的支持,它主要的内容有:

  • input标签的类型增加一个file选项
  • form表单的encrypt 增加multipart/form-data

如下是一个支持文件上传的form表单:

在Get中查询参数之间是通过&来区分的,所以当通过Post上传多个文件时是通过boundary来区分的,boundary是即时
生成的字符串,用以确保整个分隔符不会在文件或表单项的内容中出现,之所以使用boundary区分,而不是使用&,是因为防止与文件内容冲突

文件上传的实现

客户端代码:

func main() {
    buf := new(bytes.Buffer)
    writer := multipart.NewWriter(buf)
    //fieldname:服务器通过这个参数接收文件,filename:接收到文件后的文件名
    formFile, err := writer.CreateFormFile("file", "license")
    if err != nil {
        log.Fatalf("Create form file failed: %s\n", err)
    }

    // 打开要上传的文件
    srcFile, err := os.Open("D:\\temp\\license.txt")
    if err != nil {
        log.Fatalf("%Open source file failed: s\n", err)
    }
    defer srcFile.Close()

    //将文件内容写入
    _, err = io.Copy(formFile, srcFile)
    if err != nil {
        log.Fatalf("Write to form file falied: %s\n", err)
    }

    //设置发送的Content-Type
    contentType := writer.FormDataContentType()
    writer.Close()

    //发送post请求上传文件
    _, err = http.Post("http://localhost:8080/file", contentType, buf)
    if err != nil {
        log.Fatalf("Post failed: %s\n", err)
    }
}

服务器端:

func main() {
    http.HandleFunc("/file", uploadHandle)
    http.ListenAndServe(":8080", nil)
}

func uploadHandle(w http.ResponseWriter, r *http.Request) {
    // 获取文件
    formFile, header, err := r.FormFile("file")
    if err != nil {
        log.Printf("Get form file failed: %s\n", err)
        return
    }
    defer formFile.Close()

    //此处的header.Filename就是客户端的filename
    destFile, err := os.Create(header.Filename)
    if err != nil {
        log.Printf("Create failed: %s\n", err)
        return
    }
    defer destFile.Close()
    
    _, err = io.Copy(destFile, formFile)
    if err != nil {
        log.Printf("Write file failed: %s\n", err)
        return
    }
}

Http文件下载

func downloadFile(url string,filepath string) error {
    resp,err := http.Get(url)
    if err != nil{
        log.Printf("download file err:%v",err)
        return err
    }

    if resp == nil || resp.Body == nil{
        return fmt.Errorf("file is null")
    }
    defer resp.Body.Close()

    //create file
    f,err := os.Create(filepath)
    if err != nil{
        log.Printf("create file err:%v",err)
        return err
    }
    
    _,err = io.Copy(f,resp.Body)
    if err != nil{
        return err
    }
    
    return nil
}

你可能感兴趣的:(Http文件上传下载)