英文:
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 (
"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)
}
server
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())
}
}
答案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.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论