精华内容
下载资源
问答
  • 我们经常要将自己文件发送给朋友,但是总会因为太大,用网盘、U盘都不是很方便,这个时候我们就可以用”内网穿透“的方式 “内网穿透”可以理解为,在自己电脑上创建一个共享文件夹,只不过这个文件夹可以通过网页的...

    内网穿透原理:

    内网穿透:将自己的局域网映射到公网上

    我们经常要将自己文件发送给朋友,但是总会因为太大,用网盘、U盘都不是很方便,这个时候我们就可以用”内网穿透“的方式

    “内网穿透”可以理解为,在自己电脑上创建一个共享文件夹,只不过这个文件夹可以通过网页的方式在公网访问

    这种方式没有限速,网速多块,就有多快(也要看用的什么下载工具,用迅雷下会有限速,建议用IDM)

    怎么用

    下载utools:https://www.u.tools/download.html
    (utools是一款插件集合工具,除了内网穿透,其他工具也很好用)

    下载安装好后,打开就是一个搜索框,点击搜索框最右侧的头像
    然后可以看到"插件中心",下载自己想要的插件
    我们下载“内网穿透”

    安装好后,回到搜索框,把“插件中心”的标签删掉,搜索“内网穿透”
    在这里插入图片描述
    在外网域名中起一个自己喜欢的域名

    然后“本地目录选择”选择自己要分享的文件
    这种方法是共享的一个文件夹,所以要把自己需要分享的文件,视频等都放在一个文件夹下

    然后点击右下脚的连接
    在这里插入图片描述
    复制连接,分享给朋友就好了

    在这里插入图片描述
    打开后的样子
    双击蓝色链接,就下载了
    开始用,看着肯定不习惯

    必须保证分享资源的电脑,开机有网。

    展开全文
  • 因为PDF问价传输的方便性,所以办公中收到的很多文件都是以PDF文件格式呈现的,那么如果我们需要将PDF转换成HTML,我们应该如何来实现呢?下面就来给大家分享一个可以在线实现PDF转HTML的教程来帮助大家解决这个问题...

    PDF怎么转HTML呢?因为PDF问价传输的方便性,所以办公中收到的很多文件都是以PDF文件格式呈现的,那么如果我们需要将PDF转换成HTML,我们应该如何来实现呢?下面就来给大家分享一个可以在线实现PDF转HTML的教程来帮助大家解决这个问题吧。
    步骤一:在电脑上准备一份PDF文件同时还需要通过电脑浏览器搜索迅捷PDF在线转换,然后进入如下页面。
    PDF怎么转HTML,在线将PDF转HTML的教程
    步骤二:转入PDF在线转换页面后,在页面上面一排的工具栏中选择文件转换中的PDF转HTML功能。
    PDF怎么转HTML,在线将PDF转HTML的教程
    步骤三:进入PDF转HTML功能页面后,就可以通过“点击选择文件”按钮,将PDF文件添加进来了。
    PDF怎么转HTML,在线将PDF转HTML的教程
    步骤四PDF文件添加进来后,在文件下方可以对一些自定义文件选项进行修改。
    PDF怎么转HTML,在线将PDF转HTML的教程
    步骤五:上面的选项修改好后,就可以通过“开始转换”按钮对文件进行转换了,转换的时间会受文件的大小不同而有所影响。
    PDF怎么转HTML,在线将PDF转HTML的教程
    步骤六:文件转换好后,就可以将转换后的HTML文件下载到电脑上了。
    PDF怎么转HTML,在线将PDF转HTML的教程
    在线将PDF转HTML的教程,上面已经为大家做了详细的讲解了,操作简单,有需要的小伙伴就赶紧去试试吧。

    转载于:https://blog.51cto.com/14028290/2316422

    展开全文
  • 一个Form不仅包含正常的文本内容、标记等,还包含被称为控件的特殊元素。用户通常通过修改控件(比如:输入文本、选择菜单项等)来“完成”表单,然后将表单数据...其实,http通常作为html传输的承载体,打个比方,...

    45e841f4483ff776450d310b0022eaa5.png

    1. Form简介

    Form(中文译为表单),是HTML标记语言中的重要语法元素。一个Form不仅包含正常的文本内容、标记等,还包含被称为控件的特殊元素。用户通常通过修改控件(比如:输入文本、选择菜单项等)来“完成”表单,然后将表单数据以HTTP Get或Post请求的形式提交(submit)给Web服务器。

    很多初学者总是混淆HTML和HTTP。其实,http通常作为html传输的承载体,打个比方,html就像乘客,http就像出租车,将乘客从一个地方运输到另外一个地方。但显然http这辆出租车可不仅仅只拉html这一个乘客,很多格式均可作为http这辆出租车的乘客,比如json(over http)、xml(over http)。

    在一个HTML文档中,一个表单的标准格式如下:

    <form action="http://localhost:8080/repositories" method="get">
       <input type="text" name="language" value="go" />
       <input type="text" name="since" value="monthly" />
       <input type="submit" />               
    </form> 
    

    这样的一个Form被加载到浏览器中后会呈现为一个表单的样式,当在两个文本框中分别输入文本(或以默认的文本作为输入)后,点击“提交(submit)”,浏览器会向http://localhost:8080发出一个HTTP请求,由于Form的method属性为get,因此该HTTP请求会将表单的输入文本作为查询字符串参数(Query String Parameter,在这里即是**?language=go&since=monthly**)。服务器端处理完该请求后,会返回一个HTTP承载的应答,该应答被浏览器接收后会按特定样式呈现在浏览器窗口中。上述这个过程可以用总结为下面这幅示意图:

    a3d25dd6ce4a854e586baa64e9080136.png

    Form中的method也可以使用post,就像下面这样:

    <form action="http://localhost:8080/repositories" method="post">
       <input type="text" name="language" value="go" />
       <input type="text" name="since" value="monthly" />
       <input type="submit" />
    </form>
    

    改为post的Form表单在点击提交后发出的http请求与method=get时的请求有何不同呢?不同之处就在于在method=post的情况下,表单的参数不会再以查询字符串参数的形式放在请求的URL中,而是会被写入HTTP的BODY中。我们也将这一过程用一幅示意图的形式总结一下:

    d5c5389b5b6ce1c5ac9b4c9d4124d8a8.png

    由于表单参数被放置在HTTP Body中传输(body中的数据为:language=go&since=monthly),因此在该HTTP请求的headers中我们会发现新增一个header字段:Content-Type,在这里例子中,它的值为application/x-www-form-urlencoded。我们可以在Form中使用enctype属性改变Form传输数据的内容编码类型,该属性的默认值就是application/x-www-form-urlencoded(即key1=value1&key2=value2&...的形式)。enctype的其它可选值还包括:

    • text/plain
    • multipart/form-data

    采用method=get的Form的表单参数以查询字符串参数的形式放入http请求,这使得其应用场景相对局限,比如:

    • 当参数值很多,参数值很长时,可能会超出URL最大长度限制;
    • 传递敏感数据时,参数值以明文放在HTTP请求头是不安全的;
    • 无法胜任传递二进制数据(比如一个文件内容)的情形。

    因此,在面对上述这些情形时,method=post的表单更有优势。当enctype为不同值时,method=post的表单在http Body中传输的数据形式如下图:

    85e14816f022cb9e6c7740fa892465b8.png

    我们看到:enctype=application/x-www-urlencoded时,Body中的数据呈现为key1=value1&key2=value2&...的形式,好似URL的查询字符串参数的组合呈现形式;当enctype=text/plain时,这种编码格式也称为raw,即将数据内容原封不动的放入Body中传输,保持数据的原先的编码方式(通常为utf-8);而当enctype=multipart/form-data时,HTTP Body中的数据以多段(part)的形式呈现,段与段之间使用指定的随机字符串分隔,该随机字符串也会随着HTTP Post请求一并传给服务端(放在Header中的Content-Type的值中,与multipart/form-data使用分号相隔),如:

    Content-Type: multipart/form-data; boundary=--------------------------399501358433894470769897
    

    我们来看一个稍微复杂些的enctype=multipart/form-data的例子的示意图:

    1a06dc120761f28037000a88b52d33f8.png

    我们用Postman模拟了一个包含5个分段(part)的Post请求,其中包含两个文本分段(text)和三个文件分段,并且这三个文件是不同格式的文件,分别是txt,png和json。针对文件分段,Postman使用每个分段中的Content-Type来指明这个分段的数据内容类型。当服务端接收到这些数据时,根据分段Content-Type的指示,便可以有针对性的对分段数据进行解析了。文件分段的默认Content-Type为text/plain;对于无法识别的文件类型(比如:没有扩展名),文件分段的Content-Type通常会设置为application/octet-stream

    通过Form上传文件是RFC1867规范赋予html的一种能力,并且该能力已被证明非常有用,并被广泛使用,甚至我们可以直接将multipart/form-data作为HTTP Post body的一种数据承载协议在两个端之间传输文件数据。

    2. 支持以multipart/form-data格式上传文件的Go服务器

    http.Request提供了ParseMultipartForm的方法对以multipart/form-data格式传输的数据进行解析,解析即是将数据映射为Request结构的MultipartForm字段的过程:

    // $GOROOT/src/net/http/request.go
    
    type Request struct {
        ... ...
        // MultipartForm is the parsed multipart form, including file uploads.
        // This field is only available after ParseMultipartForm is called.
        // The HTTP client ignores MultipartForm and uses Body instead.
        MultipartForm *multipart.Form
        ... ...
    }
    

    multipart.Form代表了一个解析后的multipart/form-data的Body,其结构如下:

    // $GOROOT/src/mime/multipart/formdata.go
    
    // Form is a parsed multipart form.
    // Its File parts are stored either in memory or on disk,
    // and are accessible via the *FileHeader's Open method.
    // Its Value parts are stored as strings.
    // Both are keyed by field name.
    type Form struct {
            Value map[string][]string
            File  map[string][]*FileHeader
    }
    

    我们看到这个Form结构由两个map组成,一个map中存放了所有的value part(就像前面的name、age),另外一个map存放了所有的file part(就像前面的part1.txt、part2.png和part3.json)。value part集合没什么可说的,map的key就是每个值分段中的"name"; 我们的重点在file part上。每个file part对应一组FileHeader,FileHeader的结构如下:

    // $GOROOT/src/mime/multipart/formdata.go
    type FileHeader struct {
            Filename string
            Header   textproto.MIMEHeader
            Size     int64
    
            content []byte
            tmpfile string
    }
    

    每个file part的FileHeader包含五个字段:

    • Filename - 上传文件的原始文件名
    • Size - 上传文件的大小(单位:字节)
    • content - 内存中存储的上传文件的(部分或全部)数据内容
    • tmpfile - 在服务器本地的临时文件中存储的部分上传文件的数据内容(如果上传的文件大小大于传给ParseMultipartForm的参数maxMemory,剩余部分存储在临时文件中)
    • Header - file part的header内容,它亦是一个map,其结构如下:
    // $GOROOT/src/net/textproto/header.go
    
    // A MIMEHeader represents a MIME-style header mapping
    // keys to sets of values.
    type MIMEHeader map[string][]string
    

    我们可以将ParseMultipartForm方法实现的数据映射过程表述为下面这张示意图,这样看起来更为直观:

    2e04c3cadae64c8d9ced1be27fb57c11.png

    有了上述对通过multipart/form-data格式上传文件的原理的拆解,我们就可以很容易地利用Go http包实现一个简单的支持以multipart/form-data格式上传文件的Go服务器:

    // github.com/bigwhite/experiments/multipart-formdata/server/file_server1.go
    package main
    
    import (
     "fmt"
     "io"
     "net/http"
     "os"
    )
    
    const uploadPath = "./upload"
    
    func handleUploadFile(w http.ResponseWriter, r *http.Request) {
     r.ParseMultipartForm(100)
     mForm := r.MultipartForm
    
     for k, _ := range mForm.File {
      // k is the key of file part
      file, fileHeader, err := r.FormFile(k)
      if err != nil {
       fmt.Println("inovke FormFile error:", err)
       return
      }
      defer file.Close()
      fmt.Printf("the uploaded file: name[%s], size[%d], header[%#v]n",
       fileHeader.Filename, fileHeader.Size, fileHeader.Header)
    
      // store uploaded file into local path
      localFileName := uploadPath + "/" + fileHeader.Filename
      out, err := os.Create(localFileName)
      if err != nil {
       fmt.Printf("failed to open the file %s for writing", localFileName)
       return
      }
      defer out.Close()
      _, err = io.Copy(out, file)
      if err != nil {
       fmt.Printf("copy file err:%sn", err)
       return
      }
      fmt.Printf("file %s uploaded okn", fileHeader.Filename)
     }
    }
    
    func main() {
     http.HandleFunc("/upload", handleUploadFile)
     http.ListenAndServe(":8080", nil)
    }
    

    我们可以用Postman或下面curl命令向上述文件服务器同时上传两个文件part1.txt和part3.json:

    curl --location --request POST ':8080/upload' 
    --form 'name="tony bai"' 
    --form 'age="23"' 
    --form 'file1=@"/your_local_path/part1.txt"' 
    --form 'file3=@"/your_local_path/part3.json"'
    

    文件上传服务器的运行输出日志如下:

    $go run file_server1.go
    the uploaded file: name[part3.json], size[130], header[textproto.MIMEHeader{"Content-Disposition":[]string{"form-data; name="file3"; filename="part3.json""}, "Content-Type":[]string{"application/json"}}]
    file part3.json uploaded ok
    the uploaded file: name[part1.txt], size[15], header[textproto.MIMEHeader{"Content-Disposition":[]string{"form-data; name="file1"; filename="part1.txt""}, "Content-Type":[]string{"text/plain"}}]
    file part1.txt uploaded ok
    

    之后我们可以看到:文件上传服务器成功地将接收到的part1.txt和part3.json存储到了当前路径下的upload目录中了!

    3. 支持以multipart/form-data格式上传文件的Go客户端

    前面进行文件上传的客户端要么是浏览器,要么是Postman,要么是curl,如果我们自己构要造一个支持以multipart/form-data格式上传文件的客户端,应该如何做呢?我们需要按照multipart/form-data的格式构造HTTP请求的包体(Body),还好通过Go标准库提供的mime/multipart包,我们可以很容易地构建出满足要求的包体:

    // github.com/bigwhite/experiments/multipart-formdata/client/client1.go
    
    ... ...
    var (
     filePath string
     addr     string
    )
    
    func init() {
     flag.StringVar(&filePath, "file", "", "the file to upload")
     flag.StringVar(&addr, "addr", "localhost:8080", "the addr of file server")
     flag.Parse()
    }
    
    func main() {
     if filePath == "" {
      fmt.Println("file must not be empty")
      return
     }
    
     err := doUpload(addr, filePath)
     if err != nil {
      fmt.Printf("upload file [%s] error: %s", filePath, err)
      return
     }
     fmt.Printf("upload file [%s] okn", filePath)
    }
    
    func createReqBody(filePath string) (string, io.Reader, error) {
     var err error
    
     buf := new(bytes.Buffer)
     bw := multipart.NewWriter(buf) // body writer
    
     f, err := os.Open(filePath)
     if err != nil {
      return "", nil, err
     }
     defer f.Close()
    
     // text part1
     p1w, _ := bw.CreateFormField("name")
     p1w.Write([]byte("Tony Bai"))
    
     // text part2
     p2w, _ := bw.CreateFormField("age")
     p2w.Write([]byte("15"))
    
     // file part1
     _, fileName := filepath.Split(filePath)
     fw1, _ := bw.CreateFormFile("file1", fileName)
     io.Copy(fw1, f)
    
     bw.Close() //write the tail boundry
     return bw.FormDataContentType(), buf, nil
    }
    
    func doUpload(addr, filePath string) error {
     // create body
     contType, reader, err := createReqBody(filePath)
     if err != nil {
      return err
     }
    
     url := fmt.Sprintf("http://%s/upload", addr)
     req, err := http.NewRequest("POST", url, reader)
    
     // add headers
     req.Header.Add("Content-Type", contType)
    
     client := &http.Client{}
     resp, err := client.Do(req)
     if err != nil {
      fmt.Println("request send error:", err)
      return err
     }
     resp.Body.Close()
     return nil
    }
    

    显然上面这个client端的代码的核心是createReqBody函数:

    • 该client在body中创建了三个分段,前两个分段仅仅是我为了演示如何创建text part而故意加入的,真正的上传文件客户端是不需要创建这两个分段(part)的;
    • createReqBody使用bytes.Buffer作为http body的临时存储;
    • 构建完body内容后,不要忘记调用multipart.Writer的Close方法以写入结尾的boundary标记。

    我们使用这个客户端向前面的支持以multipart/form-data格式上传文件的服务器上传一个文件:

    // 客户端
    $go run client1.go -file hello.txt
    upload file [hello.txt] ok
    
    // 服务端
    $go run file_server1.go
    
    http request: http.Request{Method:"POST", URL:(*url.URL)(0xc00016e100), Proto:"HTTP/1.1", ProtoMajor:1, ProtoMinor:1, Header:http.Header{"Accept-Encoding":[]string{"gzip"}, "Content-Length":[]string{"492"}, "Content-Type":[]string{"multipart/form-data; boundary=b55090594eaa1aaac1abad1d89a77ae689130d79d6f66af82590036bd8ba"}, "User-Agent":[]string{"Go-http-client/1.1"}}, Body:(*http.body)(0xc000146380), GetBody:(func() (io.ReadCloser, error))(nil), ContentLength:492, TransferEncoding:[]string(nil), Close:false, Host:"localhost:8080", Form:url.Values{"age":[]string{"15"}, "name":[]string{"Tony Bai"}}, PostForm:url.Values{"age":[]string{"15"}, "name":[]string{"Tony Bai"}}, MultipartForm:(*multipart.Form)(0xc000110d50), Trailer:http.Header(nil), RemoteAddr:"[::1]:58569", RequestURI:"/upload", TLS:(*tls.ConnectionState)(nil), Cancel:(<-chan struct {})(nil), Response:(*http.Response)(nil), ctx:(*context.cancelCtx)(0xc0001463c0)}
    the uploaded file: name[hello.txt], size[15], header[textproto.MIMEHeader{"Content-Disposition":[]string{"form-data; name="file1"; filename="hello.txt""}, "Content-Type":[]string{"application/octet-stream"}}]
    file hello.txt uploaded ok
    

    我们看到hello.txt这个文本文件被成功上传!

    4. 自定义file分段中的header

    从上面file_server1的输出来看,client1这个客户端上传文件时在file分段(part)中设置的Content-Type为默认的application/octet-stream。有时候,服务端可能会需要根据这个Content-Type做分类处理,需要客户端给出准确的值。上面的client1实现中,我们使用了multipart.Writer.CreateFormFile这个方法来创建file part:

    // file part1
    _, fileName := filepath.Split(filePath)
    fw1, _ := bw.CreateFormFile("file1", fileName)
    io.Copy(fw1, f)
    

    下面是标准库中CreateFormFile方法的实现代码:

    // $GOROOT/mime/multipart/writer.go
    func (w *Writer) CreateFormFile(fieldname, filename string) (io.Writer, error) {
            h := make(textproto.MIMEHeader)
            h.Set("Content-Disposition",
                    fmt.Sprintf(`form-data; name="%s"; filename="%s"`,
                            escapeQuotes(fieldname), escapeQuotes(filename)))
            h.Set("Content-Type", "application/octet-stream")
            return w.CreatePart(h)
    }
    

    我们看到无论待上传的文件是什么类型,CreateFormFile均将Content-Type置为application/octet-stream这一默认值。如果我们要自定义file part中Header字段Content-Type的值,我们就不能直接使用CreateFormFile,不过我们可以参考其实现:

    // github.com/bigwhite/experiments/multipart-formdata/client/client2.go
    
    var quoteEscaper = strings.NewReplacer("", "", `"`, """)
    
    func escapeQuotes(s string) string {
     return quoteEscaper.Replace(s)
    }
    
    func createReqBody(filePath string) (string, io.Reader, error) {
     var err error
    
     buf := new(bytes.Buffer)
     bw := multipart.NewWriter(buf) // body writer
    
     f, err := os.Open(filePath)
     if err != nil {
      return "", nil, err
     }
     defer f.Close()
    
     // text part1
     p1w, _ := bw.CreateFormField("name")
     p1w.Write([]byte("Tony Bai"))
    
     // text part2
     p2w, _ := bw.CreateFormField("age")
     p2w.Write([]byte("15"))
    
     // file part1
     _, fileName := filepath.Split(filePath)
     h := make(textproto.MIMEHeader)
     h.Set("Content-Disposition",
      fmt.Sprintf(`form-data; name="%s"; filename="%s"`,
       escapeQuotes("file1"), escapeQuotes(fileName)))
     h.Set("Content-Type", "text/plain")
     fw1, _ := bw.CreatePart(h)
     io.Copy(fw1, f)
    
     bw.Close() //write the tail boundry
     return bw.FormDataContentType(), buf, nil
    }
    

    我们通过textproto.MIMEHeader实例来自定义file part的header部分,然后基于该实例调用CreatePart创建file part,之后将hello.txt的文件内容写到该part的header后面。

    我们运行client2来上传hello.txt文件,在file_server侧,我们就能看到如下日志:

    the uploaded file: name[hello.txt], size[15], header[textproto.MIMEHeader{"Content-Disposition":[]string{"form-data; name="file1"; filename="hello.txt""}, "Content-Type":[]string{"text/plain"}}]
    file hello.txt uploaded ok
    

    我们看到file part的Content-Type的值已经变为我们设定的text/plain了。

    5. 解决上传大文件的问题

    在上面的客户端中存在一个问题,那就是我们在构建http body的时候,使用了一个bytes.Buffer加载了待上传文件的所有内容,这样一来,如果待上传的文件很大的话,内存空间消耗势必过大。那么如何将每次上传内存文件时对内存的使用限制在一个适当的范围,或者说上传文件所消耗的内存空间不因待传文件的变大而变大呢?我们来看下面的这个解决方案:

    // github.com/bigwhite/experiments/multipart-formdata/client/client3.go
    ... ...
    func createReqBody(filePath string) (string, io.Reader, error) {
     var err error
     pr, pw := io.Pipe()
     bw := multipart.NewWriter(pw) // body writer
     f, err := os.Open(filePath)
     if err != nil {
      return "", nil, err
     }
    
     go func() {
      defer f.Close()
      // text part1
      p1w, _ := bw.CreateFormField("name")
      p1w.Write([]byte("Tony Bai"))
    
      // text part2
      p2w, _ := bw.CreateFormField("age")
      p2w.Write([]byte("15"))
    
      // file part1
      _, fileName := filepath.Split(filePath)
      h := make(textproto.MIMEHeader)
      h.Set("Content-Disposition",
       fmt.Sprintf(`form-data; name="%s"; filename="%s"`,
        escapeQuotes("file1"), escapeQuotes(fileName)))
      h.Set("Content-Type", "application/pdf")
      fw1, _ := bw.CreatePart(h)
      cnt, _ := io.Copy(fw1, f)
      log.Printf("copy %d bytes from file %s in totaln", cnt, fileName)
      bw.Close() //write the tail boundry
      pw.Close()
     }()
     return bw.FormDataContentType(), pr, nil
    }
    
    func doUpload(addr, filePath string) error {
     // create body
     contType, reader, err := createReqBody(filePath)
     if err != nil {
      return err
     }
    
     log.Printf("createReqBody okn")
     url := fmt.Sprintf("http://%s/upload", addr)
     req, err := http.NewRequest("POST", url, reader)
    
     //add headers
     req.Header.Add("Content-Type", contType)
    
     client := &http.Client{}
     log.Printf("upload %s...n", filePath)
     resp, err := client.Do(req)
     if err != nil {
      fmt.Println("request send error:", err)
      return err
     }
     resp.Body.Close()
     log.Printf("upload %s okn", filePath)
     return nil
    }
    

    在这个方案中,我们通过io.Pipe函数创建了一个读写管道,其写端作为io.Writer实例传给multipart.NewWriter,读端返回给调用者,用于构建http request时使用。io.Pipe基于channel实现,其内部不维护任何内存缓存:

    // $GOROOT/src/io/pipe.go
    func Pipe() (*PipeReader, *PipeWriter) {
            p := &pipe{
                    wrCh: make(chan []byte),
                    rdCh: make(chan int),
                    done: make(chan struct{}),
            }
            return &PipeReader{p}, &PipeWriter{p}
    }
    

    通过Pipe返回的读端读取管道中数据时,如果尚未有数据写入管道,那么读端会像读取channel那样阻塞在那里。由于http request在被发送时(client.Do(req))才会真正基于构建req时传入的reader对Body数据进行读取,因此client会阻塞在对管道的read上。显然我们不能将读写两端的操作放在一个goroutine中,那样会因所有goroutine都挂起而导致panic。在上面的client3.go代码中,函数createReqBody内部创建了一个新goroutine,将真正构建multipart/form-data body的工作放在了新goroutine中。新goroutine最终会将待上传文件的数据通过管道写端写入管道:

    cnt, _ := io.Copy(fw1, f)
    

    而这些数据也会被client读取并通过网络连接传输出去。io.Copy的实现如下:

    // $GOROOT/src/io/io.go
    func Copy(dst Writer, src Reader) (written int64, err error) {
            return copyBuffer(dst, src, nil)
    }
    

    io.copyBuffer内部维护了一个默认32k的小buffer,它每次从src尝试最大读取32k的数据,并写入到dst中,直到读完为止。这样无论待上传的文件有多大,我们实际上每次上传所分配的内存仅有32k。

    下面就是我们用client3.go上传一个大小为252M的文件的日志:

    $go run client3.go -file /Users/tonybai/Downloads/ICME-2019-Tutorial-final.pdf
    2021/01/10 12:56:45 createReqBody ok
    2021/01/10 12:56:45 upload /Users/tonybai/Downloads/ICME-2019-Tutorial-final.pdf...
    2021/01/10 12:56:46 copy 264517032 bytes from file ICME-2019-Tutorial-final.pdf in total
    2021/01/10 12:56:46 upload /Users/tonybai/Downloads/ICME-2019-Tutorial-final.pdf ok
    upload file [/Users/tonybai/Downloads/ICME-2019-Tutorial-final.pdf] ok
    
    $go run file_server1.go
    http request: http.Request{Method:"POST", URL:(*url.URL)(0xc000078200), Proto:"HTTP/1.1", ProtoMajor:1, ProtoMinor:1, Header:http.Header{"Accept-Encoding":[]string{"gzip"}, "Content-Type":[]string{"multipart/form-data; boundary=4470ba3867218f1130878713da88b5bd79f33dfbed65566e4fd76a1ae58d"}, "User-Agent":[]string{"Go-http-client/1.1"}}, Body:(*http.body)(0xc000026240), GetBody:(func() (io.ReadCloser, error))(nil), ContentLength:-1, TransferEncoding:[]string{"chunked"}, Close:false, Host:"localhost:8080", Form:url.Values{"age":[]string{"15"}, "name":[]string{"Tony Bai"}}, PostForm:url.Values{"age":[]string{"15"}, "name":[]string{"Tony Bai"}}, MultipartForm:(*multipart.Form)(0xc0000122a0), Trailer:http.Header(nil), RemoteAddr:"[::1]:54899", RequestURI:"/upload", TLS:(*tls.ConnectionState)(nil), Cancel:(<-chan struct {})(nil), Response:(*http.Response)(nil), ctx:(*context.cancelCtx)(0xc000026280)}
    the uploaded file: name[ICME-2019-Tutorial-final.pdf], size[264517032], header[textproto.MIMEHeader{"Content-Disposition":[]string{"form-data; name="file1"; filename="ICME-2019-Tutorial-final.pdf""}, "Content-Type":[]string{"application/pdf"}}]
    file ICME-2019-Tutorial-final.pdf uploaded ok
    
    $ls -l upload
    -rw-r--r--  1 tonybai  staff  264517032  1 14 12:56 ICME-2019-Tutorial-final.pdf
    

    如果你觉得32k仍然很大,每次上传要使用更小的buffer,你可以用io.CopyBuffer替代io.Copy:

    // github.com/bigwhite/experiments/multipart-formdata/client/client4.go
    
    func createReqBody(filePath string) (string, io.Reader, error) {
     var err error
     pr, pw := io.Pipe()
     bw := multipart.NewWriter(pw) // body writer
     f, err := os.Open(filePath)
     if err != nil {
      return "", nil, err
     }
    
     go func() {
      defer f.Close()
      // text part1
      p1w, _ := bw.CreateFormField("name")
      p1w.Write([]byte("Tony Bai"))
    
      // text part2
      p2w, _ := bw.CreateFormField("age")
      p2w.Write([]byte("15"))
    
      // file part1
      _, fileName := filepath.Split(filePath)
      h := make(textproto.MIMEHeader)
      h.Set("Content-Disposition",
       fmt.Sprintf(`form-data; name="%s"; filename="%s"`,
        escapeQuotes("file1"), escapeQuotes(fileName)))
      h.Set("Content-Type", "application/pdf")
      fw1, _ := bw.CreatePart(h)
      var buf = make([]byte, 1024)
      cnt, _ := io.CopyBuffer(fw1, f, buf)
      log.Printf("copy %d bytes from file %s in totaln", cnt, fileName)
      bw.Close() //write the tail boundry
      pw.Close()
     }()
     return bw.FormDataContentType(), pr, nil
    }
    

    运行这个client4:

    $go run client4.go -file /Users/tonybai/Downloads/ICME-2019-Tutorial-final.pdf
    2021/01/10 13:39:06 createReqBody ok
    2021/01/10 13:39:06 upload /Users/tonybai/Downloads/ICME-2019-Tutorial-final.pdf...
    2021/01/10 13:39:09 copy 264517032 bytes from file ICME-2019-Tutorial-final.pdf in total
    2021/01/10 13:39:09 upload /Users/tonybai/Downloads/ICME-2019-Tutorial-final.pdf ok
    upload file [/Users/tonybai/Downloads/ICME-2019-Tutorial-final.pdf] ok
    

    你会看到虽然上传成功了,但由于每次read仅能读1k数据,对于大文件来说,其上传的时间消耗增加了不少。

    6. 下载文件

    客户端基于multipart/form-data下载文件的过程的原理与上面的file_server1接收客户端上传文件的原理是一样的,这里就将这个功能的Go实现作为“作业”留给各位读者了:)。

    7. 参考资料

    • Form-based File Upload in HTML
    • Returning Values from Forms: multipart/form-data
    • 《Go Web Programming》
    • Hypertext Transfer Protocol (HTTP/1.1): Range Requests

    本文中涉及的源码可以在这里(https://github.com/bigwhite/experiments/tree/master/multipart-formdata)下载。


    Go技术进阶专栏“改善Go语⾔编程质量的50个有效实践”主要满足广大gopher关于Go语言进阶的需求,围绕如何写出地道且高质量Go代码给出50条有效实践建议,欢迎大家订阅!

    Gopher Daily(Gopher每日新闻)归档仓库 - https://github.com/bigwhite/gopherdaily

    我的联系方式:

    • 微博:https://weibo.com/bigwhite20xx
    • 微信公众号:iamtonybai
    • 博客:http://tonybai.com
    • github: https://github.com/bigwhite
    • “Gopher部落”知识星球:https://public.zsxq.com/groups/51284458844544
    展开全文
  • Node.js实现文件上传

    2020-12-23 04:45:17
    在工作中碰到了这样的需求,需要用nodejs 来上传文件,之前也只是知道怎么通过浏览器来上传文件, 用nodejs的话, 相当于模拟浏览器的行为。 google 了一番之后, 明白了浏览器无非就是利用http协议来给服务器传输...
  • node.js 上传文件

    2016-07-05 10:58:00
    在工作中碰到了这样的需求,需要用nodejs 来上传文件,之前也只是知道怎么通过浏览器来上传文件, 用nodejs的话, 相当于模拟浏览器的行为。 google 了一番之后, 明白了浏览器无非就是利用http协议来给服务器传输...

    在工作中碰到了这样的需求,需要用nodejs 来上传文件,之前也只是知道怎么通过浏览器来上传文件,  用nodejs的话,  相当于模拟浏览器的行为。 google 了一番之后,  明白了浏览器无非就是利用http协议来给服务器传输数据, 具体协议就是《RFC 1867 - Form-based File Upload in HTML》, 在浏览器上通过form 表单来上传文件就是通过这个协议,我们可以先看看浏览器给服务端发送了什么数据, 就可以依葫芦画瓢的把上传功能实现出来。说起form 表单上传文件的话, 大家应该很熟悉:

    <form action="http://www.qq.com/" method="post">
        <input type="text" name="text1" /><br />
        <input type="text" name="text2" /><br />
        <input type="submit" />
    </form>

     提交时, 用fiddler 抓包可以看到向服务端发出这样的数据:

    POST http://www.qq.com/ HTTP/1.1
    Host: www.qq.com
    Content-Length: 23
    Content-Type: application/x-www-form-urlencoded; charset=UTF-8
    
    text1=hello&text2=world

    值得注意的是Content-Type默认为application/x-www-form-urlencoded,所以消息会经过URL编码。比如“你好”会编码为 %E4%BD%A0%E5%A5%BD。

    接下来我们看一下通过form 表单是怎么上传的。大家应该也不陌生:

    <form action="http://www.qq.com"  method="post" enctype="multipart/form-data">
        <input type="file" name="myfile" />
        <input type="submit" value="submit" />
    </form>

    然后新建一个只有hello world字样的upload.txt文本文件上传上去,我们再吃用fiddler 来抓下包, 可以发现发送过去的数据稍微复杂了一些(已经去掉了很多的其它没关系的请求行,比如缓存控制和cookie之类的):

    POST http://www.qq.com/ HTTP/1.1
    Host: www.qq.com
    Content-Length: 199
    Content-Type: multipart/form-data; boundary=----WebKitFormBoundarywr3X7sXBYQQ4ZF5G
    
    ------WebKitFormBoundarywr3X7sXBYQQ4ZF5G
    Content-Disposition: form-data; name="myfile"; filename="upload.txt"
    Content-Type: text/plain
    
    hello world
    
    ------WebKitFormBoundarywr3X7sXBYQQ4ZF5G--

    根据RFC 1867的定义,我们需要生成一段边界数据,这个数据不能在内容的其它地方出现,这个可以自己定义, 在每个浏览器的生成算法可能都不一样, 上面的boundary就是分隔数据,生成了分隔数据之后, 就可以把分隔数据放在头部的Content-Type里面传送给服务端, 也就是上文的 Content-Type: multipart/form-data; boundary=----WebKitFormBoundarywr3X7sXBYQQ4ZF5G, 另外,上传的内容,需要用分隔数据来分隔成若干个段,然后每段数据里面都有文件的文件名,还有上传时候的name,服务端就是用这个name来接收文件,还有文件的类型Content-Type,在这个例子里是 text/plain,如果上传的是png图片就是image/png。文件类型的一个空行后就是所上传的文件的内容,在这个例子里也是为了容易理解所以上传的是文本文件所以内容直接就能够显示出来,如果上传的是图片文件, 因为是二进制文件,fiddler 就显示的是乱码。 文件的内容结束之后就是一个空行再加上边界数据。 

    了解了发送格式的细节之后, 下一步就是使用nodejs来编程实现,简单来讲, 就是按照格式把数据发送给服务端就行了。

    const http = require('http');
    const fs = require('fs');
    
    //post地址为本地服务的一个php,用于测试上传是否成功
    var options = {
        hostname: 'localhost',
        port: 80,
        path: '/get.php',
        method: 'POST'
    }
    
    //生成分隔数据
    var boundaryKey = '----WebKitFormBoundaryjLVkbqXtIi0YGpaB'; 
    
    //读取需要上传的文件内容
    fs.readFile('./upload.txt', function (err, data) {
        //拼装分隔数据段
        var payload = '--' + boundaryKey + '\r\n' + 'Content-Disposition:form-data; name="myfile"; filename="upload.txt"\r\n' + 'Content-Type:text/plain\r\n\r\n';
        payload += data;
        payload += '\r\n--' + boundaryKey + '--';
        
        //发送请求
        var req = http.request(options, function (res) {
            res.setEncoding('utf8');
            res.on('data', function (chunk) {
                console.log('body:' + chunk);
            });
    
        });
        
        req.on('error', function(e) {
            console.error("error:"+e);
        });
        //把boundary、要发送的数据大小以及数据本身写进请求
        req.setHeader('Content-Type', 'multipart/form-data; boundary='+boundaryKey+'');
        req.setHeader('Content-Length', Buffer.byteLength(payload, 'utf8'));
        req.write(payload);
        req.end();
    });

    本文重点在于了解协议并且用代码实现出来, 代码组织上面还有很多优化的地方。 

     

    最后在本地apache,简单写一个php来保存上传的文件来用作测试:

    <?php
    $filePath = './upload.txt';
    move_uploaded_file($_FILES['myfile']['tmp_name'] , $filePath);
    echo "ok";
    ?>

    另外,根据RFC 1867 还可以实现一次上传多个文件的功能,  这个在这里就不详述,  需要的话可以详细参考RFC 1867来实现。

    转载于:https://www.cnblogs.com/king_domain/p/5630665.html

    展开全文
  • 但是我的页面是动态的模板页面,是thymeleaf传输数据的,这个我就整不明白了,所以在这问一下有没有做过类似功能的,能分享一下方案吗。(我刚入职俩月,纯纯新手,不到万不得已,真的不想张口就问,拜托了)
  • 今天在做d3.js的项目时,发现写好的html文件怎么打开后什么也不显示,然后看了下代码,发现里面需要用到json数据,但是我的json就和html在同意文件夹下啊,怎么会不能显示呢?后来查了下资料才知道,由于D3.js读取...
  • 通过这个实验,目前为止,我们可以得出结论,上网的时候,是有真实的、物理的文件传输的! ​ 所以我们经常感觉第二次打开网页,比第一次快,这是因为第一次打开网页的时候,所有的图片都已经存过来了。 ​ ...
  • 我们平时将文件放到C盘、D盘,正常情况下,别人是不能直接通过网络访问我们的文件的,那浏览器怎么通过网络访问我们的HTML文件呢, 必须由软件通过socket+协议的方式对文件进行网络传输,就像你用QQ向别人发文件...
  • Web的应用层协议是超文本传输协议(HyperText Transfer ...HTTP由一个客户程序和一个服务器程序组成,两者通过交换报文进行会话。...多数Web页面由一个HTML基本文件加n个引用对象组成,HTML基本文件通过对象的URL
  • nodejs服务2

    2020-10-10 22:03:04
    实现动态网站 我们刚刚了解了nodejs实现静态服务器的功能,那么问题就随之而来了,怎么实现动态的网站呢? 答案是 我们需要知道数据的获取和传输。 ...创建login.html文件 创建一个js文件 ...
  • 它只是将http POST数据通过管道传输到任何可执行文件的STDIN中,反之亦然。 这个怎么运作! 查看更多信息, 示例函数 如何创建函数? 在计算机(或VM)上 下载Jerverless并解压缩存档 创建jerverless.yml # use ...
  • 传输和存储数据 怎么干? 需要自行定义标签。  XML 独立于硬件、软件以及应用程序  通常。建立完xml文件后首要的任务是:引入约束文件! 二、XML简介:可扩展标记语言,都是标记语言,通过标签来操作  具有...
  • python 解析xml

    2019-11-22 17:56:53
    1.先讲讲什么是xml xml 是一种可扩展性的标记语言,是被用来传输保存数据的,反正记住就是一种标记语言,很像html. ...2)sax 通过sax触发机制,去调用处理机制(用户定义的回调函数处理xml文件)...
  • 与陷入困境的客户共享屏幕或传输文件; -保健咨询; -在线销售; - 房地产经纪人; -汽车经销商; 快速入门指南 确保您的Web服务器上具有启用SSL的证书的HTTPS; 对于后端,您需要PHP 5.6+或Node.js 4.0+; ...
  • Android 上百实例源码分析以及开源分析 集合打包4

    千次下载 热门讨论 2012-07-10 21:54:03
    在Jamendo中,主要是通过再定义一个SeparatedListAdapter来进行这个工作,我们来看看它是怎么实现的:我理解的Adapter过程,首先通过调用getCount()来获得总Row数目,然后对一行调用getView进行绘制,因此要实现在...
  • 部分更高级的开发者可能会细心分析 <a href="https://github.com/webpack-contrib/webpack-bundle-analyzer">Webpack bundles</a> 来帮助确定不必要的依赖.</li><li>通过缓存来优化网络传输.</strong> 通过 <code>...
  • x-scan-v3.3-cn

    2013-09-23 21:36:56
    从结果中看到,这台服务器公开放了七个端口,主要有21端口用于文件传输、80端口用于网页浏览、还有110端口用于pop3电子邮件,如此一来,我们就可以进行有关服务的漏洞扫描了。(关于端口的详细解释会在后续给出) 然后...
  • 通过RTCPeerConnection传输流媒体视频 WebRTC基础实践 - 6. 通过RTCDataChannel传输数据 WebRTC基础实践 - 7. 配置信令服务 WebRTC基础实践 - 8. 集成对等通信和信令服务 WebRTC基础实践 - 9. 拍照并传给对方 WebRTC...
  • 先在数据中创建名为c2c的数据库,使用Navicat for MySQL运行 c2c.sql文件创建表和导入数据 将图片解压到任意一个盘,然后配置Tomcat,将图片路径引用到本地配置的图片路径下。 一、设计概要  本次设计的是一个...
  • DTD是"有效XML文档"的必须文件,我们通过DTD文件来定义文档中元素和标识的规则及相互关系。如何建立一个DTD文件呢?让我们一起来学习: 1.设置元素 元素是XML文档的基本组成部分。你要在DTD中定义一个元素,然后...
  • asp.net知识库

    2015-06-18 08:45:45
    从SQL中的一个表中导出HTML文件表格 获取數据库表的前N条记录 几段SQL Server语句和存储过程 生成表中的数据的脚本 最详细的SQL注入相关的命令整理 Oracle Oracle中PL/SQL单行函数和组函数详解 mssql+oracle Oracle...
  • VB网络编程实例

    千次下载 热门讨论 2007-05-29 15:46:04
    ◆ 01.htm 1、怎么用mscomm控件检测modem是否与计算机联接正确?2、如何用mscomm挂断modem与别的电话机间的连接?(已接通) ◆ 02.htm CWinInetConnection---一个封装了WinInet API函数的类 ◆ 03....
  • 它的最大传输距离达6.5 公里,背部为用户提供了内建无线AP,支持802.11b/g,速率可达54Mbps。支持64bit,128bi。贝尔RG100A-AA主芯片为最新的BCM6358,交换芯片为BCM5325,无线芯片为BCM4318,FLASH容量为16M,内存...
  • java 面试题 总结

    2009-09-16 08:45:34
    与cgi的区别在于servlet处于服务器进程中,它通过多线程方式运行其service方法,一个实例可以服务于多个请求,并且其实例一般不会销毁,而CGI对每个请求都产生新的进程,服务完成后就销毁,所以效率上低于servlet。...
  • 分别两个文件目录下安装依赖npm install,在server文件夹下node app.js,在blogPhone下npm run dev,然后打开localhost:8081就可以了 分析总结 socket.io 引入socket. io 服务端: let serve = app.listen(3001...
  • Node.js关于Stream的理解

    2020-12-08 19:35:53
    拷贝一个文件的内容到另外一个文件中。我们可能会这么做 <pre><code> javascript var source = fs.readFileSync('/path/to/source', {encoding: 'utf8'}); fs.writeFileSync('/path/to/dest...
  • <div><p>这是很久很久之前想写的东西,拖了五六个月,没有动笔,现今补齐,内容有些多,对初学者有用,错误之处,望指出。 理解作用域 理解作用域链是Js编程中一个必须要...
  • Apache Hive: 是基于Hadoop的一个数据仓库工具,可以将结构化的数据文件映射为一张数据库表,通过类SQL语句快速实现简单的MapReduce统计,不必开发专门的MapReduce应用,十分适合数据仓库的统计分析 笔记 Hive篇 ...
  • ExtAspNet_v2.3.2_dll

    2010-09-29 14:37:08
    -增加土耳其语言资料文件(feedback:abdullaharslan)。 -Grid的BoundField增加NullDisplayText属性,用于处理数据库中的null值,如果没有设置则默认为空字符串。 -修正DatePicker中的一个bug(31/01/2010将会返回...

空空如也

空空如也

1 2
收藏数 31
精华内容 12
关键字:

怎么通过html传输文件