Reading http.Response Body stream - go

I'm sending a http request and getting a response in text/xml format. I don't need to parse the XML, just trying to return it as a string for testing. But it seems pretty slow.
The XML response is 278kb, but it can take about 1.7 but sometimes spike to 4.5seconds just to read the response body. I can rerun this over and over and it can vary wildly. I'm not sure what's causing it as i'm new to Go.
I feel like the ioutil.ReadAll() function is letting me down. But i've also tried bufio.NewReader and reader.ReadBytes. All seem to be just as slow.
this is what my function looks like:
client := &http.Client{}
req, err := http.NewRequest("POST", url, strings.NewReader(requestBody))
if err != nil {
fmt.Println("New Request Error", err)
}
// Set Headers
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
req.Header.Set("Content-Encoding", "gzip")
req.Header.Set("Accept-Encoding", "gzip, deflate")
defer req.Body.Close()
// Get Response
startRes := time.Now()
response, err := client.Do(req)
if response != nil {
defer response.Body.Close()
}
if err != nil {
fmt.Println("POST Error", err)
}
endRes := time.Now()
fmt.Println("Response took", endRes.Sub(startRes))
// Read Response Body
startRead := time.Now()
body, readErr := ioutil.ReadAll(response.Body)
if readErr != nil {
fmt.Println("body error:", readErr)
return nil, readErr
}
endRead := time.Now()
fmt.Println("Read response took:", endRead.Sub(startRead))
The output of these println's look like this:
> go run main.go
Response took 5.230523s
Read response took: 1.7551755s
Is there a way to make this code more efficient? I'm assuming the 1.7s read time for a 278kb file is due to network lag, right?

I recommend allocating a byte.Buffer and reading the body yourself. The code at ioutil.ReadAll creates a byte.Buffer at an initial capacity of Byte.MinRead (512 bytes).
At 278KB, Buffer needs to expand, reallocate and recopy the returned byte array 10 times which is most processing time is being spent (for things you can control).
Untested code to try below:
buf := bytes.NewBuffer(make([]byte, 0, response.ContentLength))
_, readErr = buf.ReadFrom(response.Body)
body := buf.Bytes()

Related

How to call Body.Close() when using require()?

API test has below boiler plate code.
I want to call resp.Body.Close() even when require.Equal(ru.ts.T(), tp.expectedStatus, resp.StatusCode) fails.
Currently i have script like below. Are there better ways to write this?
I want to avoid condition if tp.expectedStatus != resp.StatusCode and call resp.Body.Close() when require.Equal(ru.ts.T(), tp.expectedStatus, resp.StatusCode) fails.
func Invoke(ip RestParams, es int) *http.Response {
client := &http.Client{}
// Initialize req using ip
resp, err := client.Do(req)
...
if tp.expectedStatus != resp.StatusCode {
resp.Body.Close()
require.Equal(ru.ts.T(), tp.expectedStatus, resp.StatusCode)
return nil
}
return resp
}
You should almost always do
resp, err := client.Do(req)
if err != nil {...} // Or require.NoError(err)
defer resp.Body.Close()
http package garanties a non nil body that should be closed as soon as the error is nil.
As a side note, I think you should avoid returning the http.Response. Unmarshal it here and return a struct model so you can handle all your technical http layer in this functio.

golang hangs when using multipart/form-data

I want to make an empty post request to telegram.
The problem is if i close multipart once, it hangs forever:
func main() {
var requestBody bytes.Buffer
multiPartWriter := multipart.NewWriter(&requestBody)
multiPartWriter.Close() // closing once
req, _ := http.NewRequest("POST", "https://api.telegram.org/bot<telegram token>/getme", &requestBody)
req.Header.Set("Content-Type", multiPartWriter.FormDataContentType())
client := &http.Client{}
client.Do(req)
}
But if i close the multipart twice it works.
Can anyone explain why this happens?
I just checked the Telegram API.
I guess the general problem is, that you use a buffer that is not initialized.
You don't need the buffer, you don't need any payload in the request. You can just pass nil as request data. Like this:
func main() {
req, err := http.NewRequest("POST", "https://api.telegram.org/bot<token>/getme", nil)
if err != nil {
panic(err)
}
client := &http.Client{}
resp, err := client.Do(req)
if err != nil {
panic(err)
}
result, err := ioutil.ReadAll(resp.Body)
if err != nil {
panic(err)
}
println(string(result))
}
I also recommend, that you check out the docs here this documentation lets you interactively try out the API, it can also generate the code for each request.
In order to generate Go code examples, you can click on the button at the upper right corner and chose your Go.

Converting string from HTTP request to data struct in Go

I've got an HTTP Post method, which successfully posts data to an external third party API and returns a response.
I then need data returned from this response to post to my database.
The response contains a few piece of data, but I only need the 'access_token' and 'refresh_token' from it.
As a result, what I'm attempting to do is convert the response from a string into individual components in a new data struct I've created - to then pass to my database.
However, the data is showing as blank, despite it successfully being written to my browser. I'm obviously doing something fundamentally wrong, but not sure what..
Here's my code:
type data struct {
Access_token string `json:"access_token"`
Refresh_token string `json:"refresh_token"`
}
func Fetch(w http.ResponseWriter, r *http.Request) {
client := &http.Client{}
q := url.Values{}
q.Add("grant_type", "authorization_code")
q.Add("client_id", os.Getenv("ID"))
q.Add("client_secret", os.Getenv("SECRET"))
q.Add("redirect_uri", "https://callback-url.com")
q.Add("query", r.URL.Query().Get("query"))
req, err := http.NewRequest("POST", "https://auth.truelayer-sandbox.com/connect/token", strings.NewReader(q.Encode()))
if err != nil {
log.Print(err)
fmt.Println("Error was not equal to nil at first stage.")
os.Exit(1)
}
req.Header.Add("Content-Type", "application/x-www-form-urlencoded")
resp, err := client.Do(req)
if err != nil {
fmt.Println("Error sending request to server")
os.Exit(1)
}
respBody, _ := ioutil.ReadAll(resp.Body)
d := data{}
err = json.NewDecoder(resp.Body).Decode(&d)
if err != nil {
fmt.Println(err)
}
fmt.Println(d.Access_token)
fmt.Println(d.Refresh_token)
w.WriteHeader(resp.StatusCode)
w.Write(respBody)
}
With ioutil.ReadAll you read the body, already. The second time you pass to NewDecoder(resp.Body) the stream was consumed.
You can use instead json.Unmarshal(respBody, &d).
One more advice, don't ignore the error on ioutil.ReadAll

Returning data back to client from Go HTTP request

I've written a simple Fetch Go function which calls an API, and generates a response.
When called, it successfully logs the data to the console which is pulled from the API.
What I want to do though is take the final 'respBody' variable generated from reading the response body, and then return it back to my frontend client - but I can't figure out how.
All the examples just use Println, and I've searched the docs but can't find anything.
Can anyone tell me how to change my code so I can return the respBody back to the client?
Here's my function:
func Fetch(w http.ResponseWriter, r *http.Request) {
client := &http.Client{}
req, err := http.NewRequest("GET", "https://pro-api.coinmarketcap.com/v1/cryptocurrency/listings/latest", nil)
if err != nil {
log.Print(err)
os.Exit(1)
}
resp, err := client.Do(req)
if err != nil {
fmt.Println("Error sending request to server")
os.Exit(1)
}
respBody, _ := ioutil.ReadAll(resp.Body)
fmt.Println(string(respBody)) // This is the final bit where I want to send this back to the client.
}
Your function is a HandlerFunc, which contains the ResponseWriter interface, in your case it's w.
So, you can write data using http.ResponseWriter:
func Fetch(w http.ResponseWriter, r *http.Request) {
client := &http.Client{}
req, err := http.NewRequest("GET", "https://pro-api.coinmarketcap.com/v1/cryptocurrency/listings/latest", nil)
if err != nil {
log.Print(err)
os.Exit(1)
}
resp, err := client.Do(req)
if err != nil {
fmt.Println("Error sending request to server")
os.Exit(1)
}
respBody, _ := ioutil.ReadAll(resp.Body)
// Here:
w.WriteHeader(resp.StatusCode)
w.Write(respBody)
}
You can use use io.Copy(w, resp.Body) instead, remember to close body using defer resp.Body.Close().
You can simply copy the contents of the response body to the response writer:
io.Copy(w,resp.Body)
Since you can only read the body once, the solution above will not allow you to get the body. If you also want to log it, or process it somehow, you can read it and then write it to the response writer.
respBody, _ := ioutil.ReadAll(resp.Body)
fmt.Println(string(respBody))
w.Write(respBody)

Using protobuf with golang and handling []byte HTTP response body

I am using the Golang protobuf package and try to write some tests to ensure my API works properly.
I construct an Object on the server-side with a generated .pb.go file.
And return it with
data, err := proto.Marshal(p)
fmt.Fprint(w, data)
And in my test I do
func TestGetProduct(t *testing.T) {
log.Println("Starting server")
go startAPITestServer()
time.Sleep(0 * time.Second)
log.Println("Server started")
//rq, err := http.NewRequest("GET", "localhost:8181/product/1", nil)
client := &http.Client{}
log.Println("Starting Request")
resp, err := client.Get("http://localhost:8181/product/1")
log.Println("Finished Request")
if err != nil {
t.Log(err)
}
defer resp.Body.Close()
log.Println("Reading Request")
data, err := ioutil.ReadAll(resp.Body)
log.Println("Reading finished")
if err != nil {
t.Log(err)
}
log.Println("HTTP Resp", data)
p := &Product{}
proto.UnmarshalText(string(data), p)
proto.Unmarshal(data, p2)
}
The Problem is that the HTTP Request is correct and displays the []byte correctly, but if I do ioutil.ReadAll it interprets the HTTP Response as a string and converts it to a []byte.
For example the response is
[12 3 2 14 41]
Then ioutil.ReadAll interprets this as a string and not as a []byte.
The problem was: I tried to write binary data to the output stream with fmt.Fprint missing the important fact, that the fmt package converts (everything?) input to a "read-able" format (ie strings).
The correct way of writting data into the output of your HTTP Response is using the responsewriter directly like this:
k, err := w.Write(data)

Resources