Sending form-data in a POST request in golang - http

I'm trying to send form-data by making a post request. The api works fine (I've tested on postman), but I'm not sure why I'm having trouble to do it in golang. The form-data contains a task field and a file field. But if I do the following I get Bad Request. Any ideas why I might be getting this? Thanks in advance.
// Create new buffer and writer
buf := new(bytes.Buffer)
w := multipart.NewWriter(buf)
// read data from file
var fdata []byte
if fd, e := os.Open(pdf); e != nil {
log.Fatal(e)
} else {
fd.Read(fdata)
}
// create file field and write
part, err := w.CreateFormFile("file", pdf)
if err != nil {
log.Fatal(err)
}
part.Write(fdata)
// create the task field and write
part, err = w.CreateFormField("task")
if err != nil {
log.Fatal(err)
}
part.Write([]byte(os.Getenv("task")))
w.Close()
// Create a new request
req, err := http.NewRequest("POST", fmt.Sprintf("https://%v/v1/upload",os.Getenv("server")), buf)
// Set content type header
req.Header.Add("Content-Type", "multipart/form-data")
// Send the request
client := &http.Client{}
resp, err := client.Do(req)
if err != nil {
log.Fatal(err)
}
defer resp.Body.Close()
// other stuff

Related

MissingContentLength response from Minio on PUT to presigned URL

When trying to perform a PUT request to a pre-signed Minio URL using the golang httpClient library the following error is returned:
<Error><Code>MissingContentLength</Code><Message>You must provide the Content-Length HTTP header.</Message><Key>obj</Key><BucketName>bucket</BucketName><Resource>/bucket/obj</Resource><RequestId>REMOVED</RequestId><HostId>REMOVED</HostId></Error>
I'm trying to upload a file to the URL created by running the following on a connected minioClient:
minioClient.PresignedPutObject(context.Background(), "bucket", "obj", time.Second*60)
The code which is erroring is:
url := "http://pre-signed-url-to-bucket-obj"
fileName := "test.txt"
file, err := os.Open(fileName)
if err != nil {
log.Fatal(err)
}
defer file.Close()
request, err := http.NewRequest(http.MethodPut, url, file)
if err != nil {
log.Fatal("Error creating request:", err)
}
// Tried including and excluding explicit Content-Length add, doesn't change response
// fStat, err := file.Stat()
// if err != nil {
// log.Fatal("Error getting file info:", err)
// }
// request.Header.Set("Content-Length", strconv.FormatInt(fStat.Size(), 10))
client := &http.Client{}
resp, err := client.Do(request)
if err != nil {
log.Fatal("Error performing request:", err)
}
defer resp.Body.Close()
content, err := ioutil.ReadAll(resp.Body)
if err != nil {
log.Fatal("Error reading response:", err)
}
log.Println(string(content))
I've checked the Request and from what I'm able to tell Content-Length is being added.
A curl call with the --upload-file option specified will work:
curl -X PUT 'http://pre-signed-url-to-bucket-obj' --upload-file test.txt
I'm able to verify Content-Length is correctly added.
I would like to avoid a form as it does weird stuff to the obj on Minio's end.
Any help is much appreciated!
Do this to explicit content-length explicitly:
request.ContentLength = fStat.Size()
I verified that the above code works with this fix

Go http request redirect

I am writing an API whichs has to redirect incoming requests to another service, the response must be forwarded to the original requester.
I figured a simple function like below should do the trick, but I was wrong.
I receive the data from my redirected response, however when I send it back to the initial request I receive this response without any data Could not get response. Error: socket hang up
If I try to execute the very same request using postman straight to the redirect URL it works perfectly fine.
func initialAssetsHandler(w http.ResponseWriter, r *http.Request) {
body, err := ioutil.ReadAll(r.Body)
if err != nil {
fmt.Println(err)
return
}
resp, err := http.Post(conf.redirectURL, "application/json", bytes.NewReader(body))
if err != nil {
log.Error(err)
}
defer resp.Body.Close()
buf := new(bytes.Buffer)
buf.ReadFrom(resp.Body)
log.Info(string(buf.Bytes()))
var data json.RawMessage
if err = json.NewDecoder(resp.Body).Decode(&data); err != nil {
fmt.Println(err)
return
}
helper.SendJsonRaw(w, 200, data)
}
Here is the SendJsonRaw function:
func SendJsonRaw(w http.ResponseWriter, status int, r json.RawMessage) error {
w.Header().Set(HeaderContentType, MimeApplicationJSON)
w.WriteHeader(status)
_, err := w.Write(r)
return err
}
The r.Body is read by the json decoder up to EOF, then when you pass it to the redirect request it looks empty to the http.Client and therefore it sends no body. You need to retain the content of the body.
For example you can do the following:
func initialAssetsHandler(w http.ResponseWriter, r *http.Request) {
body, err := ioutil.ReadAll(r.Body)
if err != nil {
fmt.Println(err)
return
}
var initialAssets TagAssets
if err := json.Unmarshal(&initialAssets, body); err != nil {
if !strings.Contains(err.Error(), "json: invalid use of ,string struct tag, trying to unmarshal") {
helper.SendJsonError(w, http.StatusBadRequest, err)
return
}
}
resp, err := http.Post(conf.redirectURL, "application/json", bytes.NewReader(body))
if err != nil {
log.Error(err)
}
defer resp.Body.Close()
log.Info(resp)
var data json.RawMessage
if err := json.NewDecoder(resp.Body).Decode(&data); err != nil {
fmt.Println(err)
return
}
helper.SendJsonOk(w, data)
}

How to send post form data through a http client?

I have a problem with post request, needed to send simple form data through http client.
http.PostForm() is not ok because I need to set my own user agent and other headers.
Here is the sample
func main() {
formData := url.Values{
"form1": {"value1"},
"form2": {"value2"},
}
client := &http.Client{}
//Not working, the post data is not a form
req, err := http.NewRequest("POST", "http://test.local/api.php", strings.NewReader(formData.Encode()))
if err != nil {
log.Fatalln(err)
}
req.Header.Set("User-Agent", "Golang_Super_Bot/0.1")
resp, err := client.Do(req)
if err != nil {
log.Fatalln(err)
}
defer resp.Body.Close()
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
log.Fatalln(err)
}
log.Println(string(body))
}
You also need to set the content type to application/x-www-form-urlencoded which corresponds to the encoding used by Value.Encode().
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
This is mentioned as one of the things done by Client.PostForm.

Unable to retrieve facebook live comments in real-time

I want to retrieve facebook live comments in real time. I have read this documentation
This is my implementation:
func getLiveComments(liveId, token string) {
url := fmt.Sprintf("https://streaming-graph.facebook.com/%s/live_comments?access_token=%s&comment_rate=one_per_two_seconds&fields=from{name,id},message",
liveId, url.QueryEscape(token))
resp, err := http.Get(url)
if err != nil {
fmt.Printf("Get: %s\n", err)
return
}
defer resp.Body.Close()
reader := bufio.NewReader(resp.Body)
for {
// got stuck here
line, err := reader.ReadBytes('\n')
if err != nil {
break
}
log.Println(string(line))
}
}
But it got stuck at line, err := reader.ReadBytes('\n').
I can use the liveId and token to get comments from facebook Graph API

Forwarding a file upload

I'm working on an api endpoint in go that will accept an upload and then immediately forward to another API. I don't want to write the file to disk anywhere, but I'm not sure storing the file temporarily in memory the way I have is correct either. All the examples that I can find deal with saving the file to disk. I've posted what I'm doing below. The response I get back from the second API is that I failed to post a file, but I can see that it is receiving the "userID" field. Can someone please point out what I'm doing wrong as well as possibly advise if this is the best way to go about this?
Route Handler
func (r *Routes) forwardFile(w http.ResponseWriter, req *http.Request){
parameters := mux.Vars(req)
userID := parameters["userID"]
const maxFileSize = 1 * 1024 * 1024 // 1MB
// pull in the uploaded file into memory
req.ParseMultipartForm(maxFileSize)
file, fileHeader, err := req.FormFile("fileUpload")
if err != nil {
encodeResponse(w, req, response{obj: nil, err: err})
return
}
defer file.Close()
success, err := service.DoForwardFile(userID, file, fileHeader)
encodeResponse(w, req, response{obj: success, err: err})
}
Service Handler
func (b *base) DoForwardFile(userID int, file multipart.File, fileHeader *multipart.FileHeader) (FileForwardedResponse, error) {
// start building our request to forward the file
var resp *http.Response
defer func() {
if resp != nil {
resp.Body.Close()
}
reportStat.Complete(0)
}()
// build a form body
body := &bytes.Buffer{}
bodyWriter := multipart.NewWriter(body)
// add form fields
bodyWriter.WriteField("userID", userID)
// add a form file to the body
fileWriter, err := bodyWriter.CreateFormFile("fileUpload", fileHeader.Filename)
if err != nil {
return FileForwardedResponse{}, err
}
// copy the file into the fileWriter
_, err = io.Copy(fileWriter, file)
if err != nil {
return FileForwardedResponse{}, err
}
// Close the body writer
bodyWriter.Close()
// build request url
apiURL := fmt.Sprintf("%s/v2/users/%d/files", config.APIURL, userID)
// send request
client := &http.Client{Timeout: time.Second * 10}
req, err := http.NewRequest("POST", apiURL, body)
resp, err = client.Do(req)
...
}
You're not setting the Content-Type for the request. Even if the header gets set automatically to multipart/form-data, it's missing the data boundary.
req, err := http.NewRequest("POST", uri, body)
if err != nil {
return FileForwardedResponse{}, err
}
req.Header.Set("Content-Type", bodyWriter.FormDataContentType())
...

Resources