Go - Mock http.Response body with a file - go

I'm trying to test a Go function which performs a call to an external service. Here's the function:
func (gs *EuGameService) retrieveGames(client model.HTTPClient) (model.EuGamesResponse, error) {
req, err := http.NewRequest(http.MethodGet, gs.getGamesEndpoint, nil)
if err != nil {
log.Fatal("Error while creating request ", err)
return nil, err
}
resp, err := client.Do(req)
if err != nil {
log.Fatal("Error while retrieving EU games", err)
return nil, err
}
var euGames model.EuGamesResponse
decoder := json.NewDecoder(resp.Body)
decoder.Decode(&euGames)
return euGames, nil
}
to properly test it, I'm trying to inject a mock client.
type HTTPClient interface {
Do(req *http.Request) (*http.Response, error)
}
type mockClient struct{}
func (mc *mockClient) Do(req *http.Request) (*http.Response, error) {
mock, _ := os.Open("../stubs/eugames.json")
defer mock.Close()
r := ioutil.NopCloser(bufio.NewReader(mock))
return &http.Response{
Status: string(http.StatusOK),
StatusCode: http.StatusOK,
Body: r,
}, nil
}
the file eugames.json contains a couple of games. But for some reason, the body is always empty! What am I missing here? I tried to use a constant with the file content and it works, games are decoded correctly. So I'm assuming there's a problem with my use of the file.

Related

How to show progress during upload asynchronously with WASM

I am currently using Go WASM to upload a file to a server. During the upload it shall emit a call to update the upload progress in the UI.
I am currently using the following struct to have an indication of the progress:
type progressReporter struct {
r io.Reader
fileSizeEncrypted int64
sent int64
file js.Value
}
func (pr *progressReporter) Read(p []byte) (int, error) {
n, err := pr.r.Read(p)
pr.sent = pr.sent + int64(n)
pr.report()
return n, err
}
func (pr *progressReporter) report() {
go js.Global().Get("dropzoneObject").Call("emit", "uploadprogress", pr.file, pr.sent*100/pr.fileSizeEncrypted, pr.sent)
}
The upload happens in a promise:
func UploadChunk(this js.Value, args []js.Value) interface{} {
[...]
handler := js.FuncOf(func(this js.Value, args []js.Value) interface{} {
resolve := args[0]
reject := args[1]
go func() {
[...]
body := new(bytes.Buffer)
writer := multipart.NewWriter(body)
part, err := writer.CreateFormFile("file", "encrypted.file")
if err != nil {
return err
}
_, err = part.Write(*data)
if err != nil {
return err
}
err = writer.Close()
if err != nil {
return err
}
pReporter := progressReporter{
r: body,
fileSizeEncrypted: fileSize,
sent: offset,
file: jsFile,
}
r, err := http.NewRequest("POST", "./uploadChunk", &pReporter)
if err != nil {
return err
}
r.Header.Set("Content-Type", writer.FormDataContentType())
client := &http.Client{}
resp, err := client.Do(r)
if err != nil {
return err
}
[...]
}
}
Although the code works fine, all emit calls to update the UI are sent after the POST request is finished. Is there any way to have this call asynchronously?
The full source code can be found here

How can I override content type header of every responses for http.Client?

I've got a http.Client in go and I want it to update every content type for every response to application/json (even though it might not be the case) even before it process the response.
Which attribute shall I override?
Context: the underlying issue there's a bug in the third party API where the real content type is application/json but it's set to the other thing (incorrectly).
Code snippet:
...
requestURL := fmt.Sprintf("http://localhost:%d", serverPort)
req, err := http.NewRequest(http.MethodGet, requestURL, nil)
if err != nil {
fmt.Printf("client: could not create request: %s\n", err)
os.Exit(1)
}
res, err := http.DefaultClient.Do(req)
if err != nil {
fmt.Printf("client: error making http request: %s\n", err)
os.Exit(1)
}
fmt.Printf("client: got response!\n")
fmt.Printf("client: status code: %d\n", res.StatusCode)
resBody, err := ioutil.ReadAll(res.Body)
if err != nil {
fmt.Printf("client: could not read response body: %s\n", err)
os.Exit(1)
}
fmt.Printf("client: response body: %s\n", resBody)
}
package main
import (
"fmt"
"net/http"
)
type MyRoundTripper struct {
httprt http.RoundTripper
}
func (rt MyRoundTripper) RoundTrip(req *http.Request) (*http.Response, error) {
res, err := rt.httprt.RoundTrip(req)
if err != nil {
fmt.Printf("Error: %v", err)
} else {
res.Header.Set("Content-Type", "application/json")
}
return res, err
}
func main() {
client := &http.Client{Transport: MyRoundTripper{http.DefaultTransport}}
resp, err := client.Get("https://example.com")
if err != nil {
// handle error
}
fmt.Printf("%+v\n", resp.Header)
}

How can I orginize code in the right way in my case? Golang

There are almost two identical functions that do approximately the same thing. What would be the right way to organize the code to avoid repetition in this case? The httpGetter() function accesses cloud platform API and gets JSON response, which I then parsed in another function and based on it I form terraform manifest from the template. The getToken() function does almost the same thing, just gets a token, which is then used in the httpGetter() function.
var accessToken = getToken()
func httpGetter(method, url string) (*http.Response, []byte) {
client := &http.Client{}
req, err := http.NewRequest(method, url, nil)
if err != nil {
fmt.Println(err)
}
req.Header.Add("Accept", "application/json;version=35.0")
req.Header.Add("Authorization", "Bearer "+accessToken)
res, err := client.Do(req)
if err != nil {
fmt.Println(err)
}
defer res.Body.Close()
body, err := ioutil.ReadAll(res.Body)
if err != nil {
fmt.Println(err)
}
return res, body
}
func getToken() string {
url := "https://cloud-platform-api.com/api/sessions"
method := "POST"
client := &http.Client{}
req, err := http.NewRequest(method, url, nil)
if err != nil {
fmt.Println(err)
}
req.Header.Add("Accept", "application/*+xml;version=35.0")
req.Header.Add("Authorization", "Basic <auth-hash>")
res, err := client.Do(req)
if err != nil {
fmt.Println(err)
}
defer res.Body.Close()
if err != nil {
fmt.Println(err)
}
accessToken := res.Header.Get("x-vmware-vcloud-access-token")
return accessToken
}
First thing first if you know the method is a getter then you don't need to pass the method param so the signature would become something like below. Also, you are already returning a *http.Response back to the caller now it will be the callers decision on what to do with the response + the caller should decide what to do in case of if the HTTP call fails so return error and let the caller decide.
func HttpGet(url string) (*http.Response, error)
Now you also want POST method with body (in some cases) so have another function
func HttpPost(URL string, body []byte) (*http.Response, error)
Now to manage both signature and have a common code you could have a private method that will be just used in this file or you could also expose that method (it is up to you)
type Headers map[string]string
func http(method, URL string, body []byte, headers Headers) (*http.Response, error) { // we pass headers here so that the caller can pass custom headers for request
client := &http.Client{}
req, err := http.NewRequest(method, url, body)
if err != nil {
return nil, err
}
req.Header.Add("Accept", "application/json;version=35.0") // common static header you can keep as it is
for key, value := range headers {
req.Header.Add(key, value)
}
return client.Do(req)
}
Using this your call from the two methods would look like
func HttpGet(url string, headers Headers) (*http.Response, error) {
return http(http.MethodGet, URL, nil, headers)
}
func HttpPost(url string, body []byte, headers Headers) (*http.Response, error) {
return http(http.MethodPost, url, body, headers)
}
Now you can use this to pass the auth token from the caller like:
func getToken() {
res, err := httpPost("https://cloud-platform-api.com/api/sessions", nil,
map[string]string{
"Authorization": "Basic <auth-hash>",
}
if err != nil {
// do something with error
}
if res.StatusCode == http.StatusCreated {
// do what you want with the success response like unmarshalling to JSON
}
}
and for cases where you don't need to pass header, you can do
res, err := httpGet("some-url", nil) // you pass header as nil

I am trying to test an REST Api client in Golang using mux and httptest

Here I am trying to write a test for a REST client, by writing the apiOutput into the http.ResponseWriter but I always receive {nil nil} as apiResponse.
Can someone help me in pointing out the error ?
func Test_Do(t *testing.T) {
mux := http.NewServeMux()
server := httptest.NewServer(mux)
t.Cleanup(server.Close)
config := NewClientConfig()
config.BaseURL = server.URL
client, err := NewClient(config)
if err != nil {
t.Fatal(err)
}
apiResponse := struct {
Id string `json:"id"`
Error error `json:"error"`
Data interface{} `json:"data"`
}{}
apiOutput := []byte(`{
"id":"1",
"error":nil,
"data":[{"IP": "1.2.2.3"}]}`)
mux.HandleFunc("/api/v1/hosts", func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
w.Write(apiOutput)
})
t.Run("Get Host Details", func(t *testing.T) {
req, err := client.MakeGetRequest(http.MethodGet, "/api/v1/hosts", nil)
if err != nil {
t.Fatal(err)
}
resp, err1 := client.Do(req, &apiResponse)
if err1 != nil {
t.Fatalf("failed with statusCode: %d, error: %s", resp.StatusCode, err1)
}
if apiResponse.Id != "1" || apiResponse.Error != nil {
t.Errorf("Client.Do() problem unmarshaling problem: got %#v", apiResponse)
}
fmt.Printf("%v", apiResponse)
})
}
func (c *Client) Do(req *http.Request, v interface{}) (*http.Response, error) {
resp, err := c.HTTPClient.Do(req)
if err != nil {
return nil, err
}
defer resp.Body.Close()
if errorResponse := c.CheckResponse(resp); errorResponse != nil {
return resp, errorResponse
}
if v != nil {
if w, ok := v.(io.Writer); ok {
io.Copy(w, resp.Body)
} else {
decErr := json.NewDecoder(resp.Body).Decode(v)
if decErr == io.EOF {
decErr = nil // ignore EOF errors caused by empty response body
}
if decErr != nil {
err = decErr
}
}
}
return resp, err
}
Output:
Test_Do/Get_Host_Details: clients_test.go:269: Client.Do() problem unmarshaling problem: got struct { Id string "json:\"id\""; Error error "json:\"error\""; Data interface {} "json:\"data\"" }{Id:"", Error:error(nil), Data:interface {}(nil)}
{ <nil> <nil>}

Go lang RPC return EOF error

I'm using http to call RPC with code below
func (c *CallClient) Wallet(method string, req, rep interface{}) error {
client := &http.Client{}
data, _ := EncodeClientRequest(method, req)
reqest, _ := http.NewRequest("POST", c.endpoint, bytes.NewBuffer(data))
resp, err := client.Do(reqest)
if err != nil {
return err
}
defer resp.Body.Close()
io.Copy(ioutil.Discard, resp.Body)
return DecodeClientResponse(resp.Body, rep)
}
with EncodeClientRquest && DecodeClientResponse
// EncodeClientRequest encodes parameters for a JSON-RPC client request.
func EncodeClientRequest(method string, args interface{}) ([]byte, error) {
c := &clientRequest{
Version: "2.0",
Method: method,
Params: [1]interface{}{args},
Id: uint64(rand.Int63()),
}
return json.Marshal(c)
}
// DecodeClientResponse decodes the response body of a client request into
// the interface reply.
func DecodeClientResponse(r io.Reader, reply interface{}) error {
var c clientResponse
if err := json.NewDecoder(r).Decode(&c); err != nil {
return err
}
if c.Error != nil {
return fmt.Errorf("%v", c.Error)
}
if c.Result == nil {
return errors.New("result is null")
}
return json.Unmarshal(*c.Result, reply)
}
And I got error EOF.
This line:
io.Copy(ioutil.Discard, resp.Body)
reads the whole resp.Body, leaving the reader with no more bytes to be read. Therefore any successive calls to resp.Body.Read will return EOF and the json.Decoder.Decode method does use the io.Reader.Read method when decoding the given reader's content, so...
And since resp.Body is an io.ReadCloser, which is an interface that does not support "rewinding", and you want to read the body content more than once (ioutil.Discard and json.Decode), you'll have to read the body into a variable that you can re-read afterwards. It's up to you how you do that, a slice of bytes, or bytes.Reader, or something else.
Example using bytes.Reader:
func (c *CallClient) Wallet(method string, req, rep interface{}) error {
client := &http.Client{}
data, err := EncodeClientRequest(method, req)
if err != nil {
return err
}
reqest, err := http.NewRequest("POST", c.endpoint, bytes.NewBuffer(data))
if err != nil {
return err
}
resp, err := client.Do(reqest)
if err != nil {
return err
}
defer resp.Body.Close()
// get a reader that can be "rewound"
buf := bytes.NewBuffer(nil)
if _, err := io.Copy(buf, resp.Body); err != nil {
return err
}
br := bytes.NewReader(buf.Bytes())
if _, err := io.Copy(ioutil.Discard, br); err != nil {
return err
}
// rewind
if _, err := br.Seek(0, 0); err != nil {
return err
}
return DecodeClientResponse(br, rep)
}

Resources