Home > Mobile >  Using io.Pipe to get output from exec.command to make http post
Using io.Pipe to get output from exec.command to make http post

Time:10-11

"-resize"Was reading up on the use of io.Pipe to reduce allocation (no need to assign cmd.Stdout to bytes.Buffer). I couldn't get the following to work and appreciate if greatly on how can i use io.Pipe to get output from exec.command to make http mutliform post request.

func main() {
    cmd := exec.Command("convert", "test.png", "-resize" "30x30", "png:-")
    bodyReader, bodyWriter := io.Pipe()
    cmd.Stdout = bodyWriter
    formWriter := multipart.NewWriter(bodyWriter)
    go func() {
        cmd.Run()
        partWriter, err := formWriter.CreateFormFile("file", "file")
        if err != nil {
            fmt.Println("form writer create error")
            return
        }
        _, err = io.Copy(partWriter, bodyReader)
        if err != nil {
            fmt.Println("io copy error")
            return
        }
        formWriter.Close()
        bodyWriter.Close()
    }()

    url := "http://example.com/upload"
    req, _ := http.NewRequest(http.MethodPost, url, bodyReader)
    req.Header.Set("Content-Type", formWriter.FormDataContentType())
    resp, err := http.DefaultClient.Do(req)
    if err != nil {
        fmt.Println("htttp do error")
        return
    }
    fmt.Println(resp.StatusCode)
    respBody, err := ioutil.ReadAll(resp.Body)
    if err != nil {
        fmt.Println("read error")
        return
    }
    fmt.Println("Result:", string(respBody))
}

Using debugging tool, it just just hang at resp, err := http.DefaultClient.Do(req) and eventually the following output.

panic: runtime error: invalid memory address or nil pointer dereference
[signal SIGSEGV: segmentation violation code=0x1 addr=0x10 pc=0x1218c3b]

goroutine 1 [running]:
main.main()

CodePudding user response:

Fix by setting the command's standard output to the part writer.

func main() {
    bodyReader, bodyWriter := io.Pipe()
    formWriter := multipart.NewWriter(bodyWriter)
    go func() (err error) {
        defer func() {
            bodyWriter.CloseWithError(err)
        }()
    
        partWriter, err := formWriter.CreateFormFile("file", "file")
        if err != nil {
            return err
        }
    
        cmd := exec.Command("convert", "test.png", "30x30", "png:-")
        cmd.Stdout = partWriter
    
        if err := cmd.Run(); err != nil {
            return err
        }
    
        formWriter.Close()
        return nil
    }()

    ...

}

Run an example on the playground.

Bonus improvement: Use CloseWithError to propagate errors from the goroutine to the main function.

CodePudding user response:

You need to add another Pipe for multipart.Writer

func main() {
    cmd := exec.Command("convert", "./test.jpg", "-resize", "30x30", "jpg:-")
    bodyReader, bodyWriter := io.Pipe()
    cmd.Stdout = bodyWriter
    cmd.Stderr = os.Stderr
    formReader, formWriter := io.Pipe()
    multiWriter := multipart.NewWriter(formWriter)

    go func() {
        if err := cmd.Run(); err != nil {
            bodyWriter.CloseWithError(err)
        } else {
            bodyWriter.Close()
        }
    }()

    go func() {
        partWriter, err := multiWriter.CreateFormFile("file", "file")
        if err != nil {
            formWriter.CloseWithError(err)
            return
        }

        _, err = io.Copy(partWriter, bodyReader)
        if err != nil {
            formWriter.CloseWithError(err)
            return
        }

        multiWriter.Close()
        formWriter.Close()
    }()

    url := "http://example.com/upload"
    req, _ := http.NewRequest(http.MethodPost, url, formReader)
    fmt.Println(multiWriter.FormDataContentType())
    req.Header.Set("Content-Type", multiWriter.FormDataContentType())
    resp, err := http.DefaultClient.Do(req)
    if err != nil {
        fmt.Println("htttp do error")
        return
    }
    fmt.Println(resp.StatusCode)
    respBody, err := ioutil.ReadAll(resp.Body)
    if err != nil {
        fmt.Println("read error")
        return
    }
    fmt.Println("Result:", string(respBody))
}
  • Related