Kody Doherty
Kody Doherty

Reputation: 263

How do you parse both a file and JSON data out of one HTTP request with Go?

I am stuck trying to figure out how to parse both a PDF document and JSON data out of one http request form from an Angularjs front end. The request payload is

HTTP Request

Content-Disposition: form-data; name="file"; filename="Parent Handbook.pdf" Content-Type: application/pdf

Content-Disposition: form-data; name="doc"

{"title":"test","cat":"test cat","date":20142323}

The “file” is the pdf and “doc” is the json data I want to parse.

My handler can parse and save the file just fine but fails to get anything out of the Json. Any ideas?

func (s *Server) PostFileHandler(w http.ResponseWriter, r *http.Request) {
    const _24K = (1 << 20) * 24
    err := r.ParseMultipartForm(_24K)
    if err != nil {
        http.Error(w, err.Error(), http.StatusInternalServerError)
        return
    }
    doc := Doc{}
    jsonDecoder := json.NewDecoder(r.Body)
    fmt.Println(r.Body)
    err = jsonDecoder.Decode(&doc)
    if err != nil {
        fmt.Println(err.Error())
    }
    fmt.Println(doc.Title, doc.Url, doc.Cat, doc.Date)
    doc.Id = len(docs) + 1
    err = s.db.Insert(&doc)
    checkErr(err, "Insert failed")

    // files := m.File["myFile"]
    for _, fheaders := range r.MultipartForm.File {
        for _, hdr := range fheaders {
            var infile multipart.File
            infile, err = hdr.Open()
            // defer infile.Close()
            if err != nil {
                http.Error(w, err.Error(), http.StatusInternalServerError)
                return
            }
            doc.Url = hdr.Filename
            fmt.Println(hdr.Filename)
            var outfile *os.File
            outfile, err = os.Create("./docs/" + hdr.Filename)
            // defer outfile.Close()
            if err != nil {
                http.Error(w, err.Error(), http.StatusInternalServerError)
                return
            }
            _, err = io.Copy(outfile, infile)
            if err != nil {
                http.Error(w, err.Error(), http.StatusInternalServerError)
                return
            }

        }
    }
    s.Ren.JSON(w, http.StatusOK, &doc)
    // http.Error(w, "hit file server", http.StatusOK)
}

Upvotes: 5

Views: 4232

Answers (1)

divan
divan

Reputation: 2817

In your example you're trying to read r.Body as if it was stripped out of PDF part of the request, but it isn't. You need to process separately both parts, PDF and JSON. Use http.(*Request).MultipartReader() for that.

r.MultipartReader() will return you mime/multipart.Reader object, so you can iterate over parts with r.NextPart() function and process each part separately.

So your handler function should be like this:

func (s *Server) PostFileHandler(w http.ResponseWriter, r *http.Request) {
    mr, err := r.MultipartReader()
    if err != nil {
        http.Error(w, err.Error(), http.StatusInternalServerError)
        return
    }

    doc := Doc{}
    for {
        part, err := mr.NextPart()

        // This is OK, no more parts
        if err == io.EOF {
            break
        }

        // Some error
        if err != nil {
            http.Error(w, err.Error(), http.StatusInternalServerError)
            return
        }

        // PDF 'file' part
        if part.FormName() == "file" {
            doc.Url = part.FileName()
            fmt.Println("URL:", part.FileName())
            outfile, err := os.Create("./docs/" + part.FileName())
            if err != nil {
                http.Error(w, err.Error(), http.StatusInternalServerError)
                return
            }
            defer outfile.Close()

            _, err = io.Copy(outfile, part)
            if err != nil {
                http.Error(w, err.Error(), http.StatusInternalServerError)
                return
            }
        }

        // JSON 'doc' part
        if part.FormName() == "doc" {
            jsonDecoder := json.NewDecoder(part)
            err = jsonDecoder.Decode(&doc)
            if err != nil {
                http.Error(w, err.Error(), http.StatusInternalServerError)
                return
            }
            fmt.Println(doc.Title, doc.Url, doc.Cat, doc.Date)
        }
    }

    doc.Id = len(docs) + 1
    err = s.db.Insert(&doc)
    checkErr(err, "Insert failed")

    s.Ren.JSON(w, http.StatusOK, &doc)
}

Upvotes: 8

Related Questions