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())
}
}
how to close/abort a Golang http.Client POST prematurely
CodePudding user response:
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.