2

I'm using Goa v3 to design an endpoint that allows me to upload files (more precisely, images) with a multipart/form-data POST request. I have declared the following Service:

var _ = Service("images", func() {
    HTTP(func() {
        Path("/images")
    })

    Method("upload", func() {  
        HTTP(func() {
            POST("/")
            MultipartRequest()
        })

        Payload(func() {
            Description("Multipart request Payload")
            Attribute("File", Bytes, "File")
        })

        Result(ImageList)
    })
})

I run the goa gen and the goa example commands to generate the boilerplate code. Apart from the cmd directory, the example code generates the images.go main file and a multipart.go file to declare the encoder and decoder logic, e.g.:

func ImagesUploadDecoderFunc(mr *multipart.Reader, p **images.UploadPayload) error {
    // Add multipart request decoder logic here
    return nil
}

I can use the mr.NextPart() and obtain a reference to the image file apparently, but I'm still not sure how should I map this to the Bytes field in the images.UploadPayload type (or maybe I should declare another type of field to handle Files??).

I can't find any example in the Goa documentation.

1 Answer 1

2

Ok, I finally understood how the multipart.Reader works, and I came up with a solution.

First let's clarify that differently from how Goa usually works (mapping 'automatically' the requests params with the Payload fields), with MultipartRequest(), I have to make the mapping on my own, so the Payload can actually have any structure.

In my case, I re-defined my Payload structure as follows:

// ImageUpload single image upload element
var ImageUpload = Type("ImageUpload", func() {
    Description("A single Image Upload type")
    Attribute("type", String)
    Attribute("bytes", Bytes)
    Attribute("name", String)
})

// ImageUploadPayload is a list of files
var ImageUploadPayload = Type("ImageUploadPayload", func() {
    Description("Image Upload Payload")

    Attribute("Files", ArrayOf(ImageUpload), "Collection of uploaded files")
})

In a nutshell, I want to support uploading several files, each with its mime-type, filename and data.

To achieve this, I implemented the multipart.go decoder function like this:

func ImagesUploadDecoderFunc(mr *multipart.Reader, p **images.ImageUploadPayload) error {
    res := images.ImageUploadPayload{}

    for {
        p, err := mr.NextPart()
        if err == io.EOF {
            break
        }

        if err != nil {
            fmt.Fprintln(os.Stderr, err)
            return err
        }

        _, params, err := mime.ParseMediaType(p.Header.Get("Content-Disposition"))
        if err != nil {
            // can't process this entry, it probably isn't an image
            continue
        }

        disposition, _, err := mime.ParseMediaType(p.Header.Get("Content-Type"))
        // the disposition can be, for example 'image/jpeg' or 'video/mp4'
        // I want to support only image files!
        if err != nil || !strings.HasPrefix(disposition, "image/") {
            // can't process this entry, it probably isn't an image
            continue
        }

        if params["name"] == "file" {
            bytes, err := ioutil.ReadAll(p)
            if err != nil {
                // can't process this entry, for some reason
                fmt.Fprintln(os.Stderr, err)
                continue
            }
            filename := params["filename"]
            imageUpload := images.ImageUpload{
                Type:  &disposition,
                Bytes: bytes,
                Name:  &filename,
            }
            res.Files = append(res.Files, &imageUpload)
        }
    }
    *p = &res
    return nil
}
Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.