Home > Software design >  Implement the io.Reader interface to cancel the form upload on the client side
Implement the io.Reader interface to cancel the form upload on the client side

Time:07-01

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())
    }

}

running

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.

  • Related