实现io.Reader接口以在客户端上取消表单上传。

huangapple go评论96阅读模式
英文:

Implement the io.Reader interface to cancel the form upload on the client side

问题

我以前使用过time.Sleep(n)来实现客户端手动取消上传,但是n的值并不确定,而且这种方法也不够优雅。现在我想通过实现io.Reader接口来手动调用cancel()函数。

以下是客户端的代码:

package main

import (
	"bytes"
	"context"
	"io"
	"log"
	"math/rand"
	"mime/multipart"
	"net/http"
	"os"
	"time"
)

type CancelReader struct {
	cancel func()
	offset int32
	size   int32
	flag   bool
	stop   int32
}

func (c CancelReader) Read(p []byte) (n int, err error) {
	if c.flag {
		return 0, nil
	}
	log.Printf("Read n %d size %d stop %d offset %d \n", n, c.size, c.stop, c.offset)
	c.flag = true

	if c.offset >= c.size {
		return 0, io.EOF
	}
	n = len(p)
	log.Println("N :", n)
	str := "0123456789abcdefg"
	b := []byte(str)
	var res []byte
	r := rand.New(rand.NewSource(time.Now().Unix()))
	for i := 0; i < n; i++ {
		res = append(res, b[r.Intn(len(b))])
	}
	n = copy(p, res)
	c.offset += int32(n)
	if c.offset >= c.stop {
		c.cancel()
	}
	c.flag = false
	time.Sleep(time.Millisecond * 100)
	return n, nil
}

const (
	filename  = "/Users/jimyag/Downloads/aDrive.dmg"
	targetUrl = "http://localhost:9999/upload"
)

func main() {
	bodyBuf := &bytes.Buffer{}
	bodyWriter := multipart.NewWriter(bodyBuf)

	fileWriter, err := bodyWriter.CreateFormFile("uploadfile", filename)
	if err != nil {
		log.Println("error writing to buffer")
	}

	fh, err := os.Open(filename)
	if err != nil {
		log.Println("error opening file")
	}
	defer fh.Close()
	_, err = io.Copy(fileWriter, fh)
	if err != nil {
		log.Print(err)
	}

	contentType := bodyWriter.FormDataContentType()
	bodyWriter.Close()

	cx, cancel := context.WithCancel(context.Background())

	cancelReader := CancelReader{
		cancel: cancel,
		offset: 0,
		size:   1024 * 256,
		flag:   false,
		stop:   1024 * 50,
	}
	req, _ := http.NewRequest(http.MethodPost, targetUrl, cancelReader)
	req.Header.Set("Content-Type", contentType)
	req = req.WithContext(cx)
	_, err = http.DefaultClient.Do(req)
	log.Println(err)

}

服务器端的代码如下:

package main

import (
	"errors"
	"io"
	"log"
	"net/http"
)

func main() {

	http.HandleFunc("/upload", func(writer http.ResponseWriter, request *http.Request) {
		file, _, err := request.FormFile("uploadfile")
		if err != nil {
			if request.Context().Err() != nil {
				log.Printf("context err %+v", request.Context().Err())
			}
			log.Printf("err %+v", err)
			return
		}
		p := make([]byte, 10)
		defer file.Close()
		size := 0
		for {
			n, err := file.Read(p)
			if err != nil {
				if errors.Is(err, io.EOF) {
					log.Printf("read end")
				} else {
					log.Printf("read unknow err %+v", err)
				}
				break
			}
			size += n

		}
		log.Printf("read end size %d \n", size)
	})
	err := http.ListenAndServe(":9999", nil)

	if err != nil {
		log.Printf(err.Error())
	}

}

你可以参考这个链接中的讨论来了解更多关于如何在Golang中提前关闭或中止HTTP客户端POST请求的信息:https://stackoverflow.com/questions/29197685/how-to-close-abort-a-golang-http-client-post-prematurely

英文:

I've used time.Sleep(n) before to accomplish client-side manual cancellation of uploads, but the value of n is not well determined and this approach is not very elegant. I now want to manually call cancel() by implementing the io.Reader interface.
Below is the client code

package main

import (
	&quot;bytes&quot;
	&quot;context&quot;
	&quot;io&quot;
	&quot;log&quot;
	&quot;math/rand&quot;
	&quot;mime/multipart&quot;
	&quot;net/http&quot;
	&quot;os&quot;
	&quot;time&quot;
)

type CancelReader struct {
	cancel func()
	offset int32
	size   int32
	flag   bool
	stop   int32
}

func (c CancelReader) Read(p []byte) (n int, err error) {
	if c.flag {
		return 0, nil
	}
	log.Printf(&quot;Read n %d size %d stop %d offset %d \n&quot;, n, c.size, c.stop, c.offset)
	c.flag = true

	if c.offset &gt;= c.size {
		return 0, io.EOF
	}
	n = len(p)
	log.Println(&quot;N :&quot;, n)
	str := &quot;0123456789abcdefg&quot;
	b := []byte(str)
	var res []byte
	r := rand.New(rand.NewSource(time.Now().Unix()))
	for i := 0; i &lt; n; i++ {
		res = append(res, b[r.Intn(len(b))])
	}
	n = copy(p, res)
	c.offset += int32(n)
	if c.offset &gt;= c.stop {
		c.cancel()
	}
	c.flag = false
	time.Sleep(time.Millisecond * 100)
	return n, nil
}

const (
	filename  = &quot;/Users/jimyag/Downloads/aDrive.dmg&quot;
	targetUrl = &quot;http://localhost:9999/upload&quot;
)

func main() {
	bodyBuf := &amp;bytes.Buffer{}
	bodyWriter := multipart.NewWriter(bodyBuf)
	
	fileWriter, err := bodyWriter.CreateFormFile(&quot;uploadfile&quot;, filename)
	if err != nil {
		log.Println(&quot;error writing to buffer&quot;)
	}
	
	fh, err := os.Open(filename)
	if err != nil {
		log.Println(&quot;error opening file&quot;)
	}
	defer fh.Close()
	_, err = io.Copy(fileWriter, fh)
	if err != nil {
		log.Print(err)
	}

	contentType := bodyWriter.FormDataContentType()
	bodyWriter.Close()

	cx, cancel := context.WithCancel(context.Background())

	cancelReader := CancelReader{
		cancel: cancel,
		offset: 0,
		size:   1024 * 256,
		flag:   false,
		stop:   1024 * 50,
	}
	req, _ := http.NewRequest(http.MethodPost, targetUrl, cancelReader)
	req.Header.Set(&quot;Content-Type&quot;, contentType)
	req = req.WithContext(cx)
	_, err = http.DefaultClient.Do(req)
	log.Println(err)

}


server

package main

import (
	&quot;errors&quot;
	&quot;io&quot;
	&quot;log&quot;
	&quot;net/http&quot;
)

func main() {

	http.HandleFunc(&quot;/upload&quot;, func(writer http.ResponseWriter, request *http.Request) {
		file, _, err := request.FormFile(&quot;uploadfile&quot;)
		if err != nil {
			if request.Context().Err() != nil {
				log.Printf(&quot;context err %+v&quot;, request.Context().Err())
			}
			log.Printf(&quot;err %+v&quot;, err)
			return
		}
		p := make([]byte, 10)
		defer file.Close()
		size := 0
		for {
			n, err := file.Read(p)
			if err != nil {
				if errors.Is(err, io.EOF) {
					log.Printf(&quot;read end&quot;)
				} else {
					log.Printf(&quot;read unknow err %+v&quot;, err)
				}
				break
			}
			size += n

		}
		log.Printf(&quot;read end size %d \n&quot;, size)
	})
	err := http.ListenAndServe(&quot;:9999&quot;, nil)

	if err != nil {
		log.Printf(err.Error())
	}

}

running

https://stackoverflow.com/questions/29197685/how-to-close-abort-a-golang-http-client-post-prematurely

答案1

得分: 4

标准库testing包中有一个鲜为人知的宝石:iotest

特别是,HalfReader可能是你所需要的(或者你可以使用HalfReader并修改它以在不同的位置停止)。

英文:

The stdlib testing package has a little-known gem: iotest.

In particular, HalfReader might be what you need (or you could take HalfReader and modify it to stop at a different point.

huangapple
  • 本文由 发表于 2022年7月1日 00:24:33
  • 转载请务必保留本文链接:https://go.coder-hub.com/72819095.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定