github.com/IBM-Cloud/bluemix-go@v0.0.0-20240423071914-9e96525baef4/rest/client.go (about)

     1  // Package rest provides a simple REST client for creating and sending
     2  // API requests.
     3  
     4  // Examples:
     5  // Creating request
     6  // 		// GET request
     7  // 		GetRequest("http://www.example.com").
     8  // 			Set("Accept", "application/json").
     9  // 			Query("foo1", "bar1").
    10  // 			Query("foo2", "bar2")
    11  //
    12  // 		// JSON body
    13  // 		foo = Foo{Bar: "val"}
    14  // 		PostRequest("http://www.example.com").
    15  // 			Body(foo)
    16  
    17  // 		// String body
    18  // 		PostRequest("http://www.example.com").
    19  // 			Body("{\"bar\": \"val\"}")
    20  
    21  // 		// Stream body
    22  // 		PostRequest("http://www.example.com").
    23  // 			Body(strings.NewReader("abcde"))
    24  
    25  // 		// Multipart POST request
    26  // 		var f *os.File
    27  // 		PostRequest("http://www.example.com").
    28  // 			Field("foo", "bar").
    29  // 			File("file1", File{Name: f.Name(), Content: f}).
    30  // 			File("file2", File{Name: "1.txt", Content: []byte("abcde"), Type: "text/plain")
    31  
    32  // 		// Build to an HTTP request
    33  // 		GetRequest("http://www.example.com").Build()
    34  
    35  // Sending request:
    36  // 		client := NewClient()
    37  // 		var foo = struct {
    38  // 			Bar string
    39  // 		}{}
    40  // 		var apiErr = struct {
    41  // 			Message string
    42  // 		}{}
    43  // 		resp, err := client.Do(request, &foo, &apiErr)
    44  package rest
    45  
    46  import (
    47  	"encoding/json"
    48  	"fmt"
    49  	"io"
    50  	"io/ioutil"
    51  	"net/http"
    52  	"reflect"
    53  
    54  	"github.com/IBM-Cloud/bluemix-go/bmxerror"
    55  )
    56  
    57  const (
    58  	//ErrCodeEmptyResponse ...
    59  	ErrCodeEmptyResponse = "EmptyResponseBody"
    60  )
    61  
    62  //ErrEmptyResponseBody ...
    63  var ErrEmptyResponseBody = bmxerror.New(ErrCodeEmptyResponse, "empty response body")
    64  
    65  // Client is a REST client. It's recommend that a client be created with the
    66  // NewClient() method.
    67  type Client struct {
    68  	// The HTTP client to be used. Default is HTTP's defaultClient.
    69  	HTTPClient *http.Client
    70  	// Defaualt header for all outgoing HTTP requests.
    71  	DefaultHeader http.Header
    72  }
    73  
    74  // NewClient creates a new REST client.
    75  func NewClient() *Client {
    76  	return &Client{
    77  		HTTPClient: http.DefaultClient,
    78  	}
    79  }
    80  
    81  // Do sends an request and returns an HTTP response. The resp.Body will be
    82  // consumed and closed in the method.
    83  //
    84  // For 2XX response, it will be JSON decoded into the value pointed to by
    85  // respv.
    86  //
    87  // For non-2XX response, an attempt will be made to unmarshal the response
    88  // into the value pointed to by errV. If unmarshal failed, an ErrorResponse
    89  // error with status code and response text is returned.
    90  func (c *Client) Do(r *Request, respV interface{}, errV interface{}) (*http.Response, error) {
    91  	req, err := c.makeRequest(r)
    92  	if err != nil {
    93  		return nil, err
    94  	}
    95  
    96  	client := c.HTTPClient
    97  	if client == nil {
    98  		client = http.DefaultClient
    99  	}
   100  
   101  	resp, err := client.Do(req)
   102  	if err != nil {
   103  		return resp, err
   104  	}
   105  	defer resp.Body.Close()
   106  
   107  	if resp.StatusCode < 200 || resp.StatusCode > 299 {
   108  		raw, err := ioutil.ReadAll(resp.Body)
   109  		if err != nil {
   110  			return resp, fmt.Errorf("Error reading response: %v", err)
   111  		}
   112  
   113  		if len(raw) > 0 && errV != nil {
   114  			if json.Unmarshal(raw, errV) == nil {
   115  				return resp, nil
   116  			}
   117  		}
   118  
   119  		return resp, bmxerror.NewRequestFailure("ServerErrorResponse", string(raw), resp.StatusCode)
   120  	}
   121  
   122  	if respV != nil {
   123  		// Callback function with execpted JSON type
   124  		if funcType := reflect.TypeOf(respV); funcType.Kind() == reflect.Func {
   125  			if funcType.NumIn() != 1 || funcType.NumOut() != 1 {
   126  				err = fmt.Errorf("Callback funcion not expected signature: func(interface{}) bool")
   127  			}
   128  			paramType := funcType.In(0)
   129  			dc := json.NewDecoder(resp.Body)
   130  			dc.UseNumber()
   131  			for {
   132  				typedInterface := reflect.New(paramType).Interface()
   133  				if err = dc.Decode(typedInterface); err == io.EOF {
   134  					err = nil
   135  					break
   136  				} else if err != nil {
   137  					break
   138  				}
   139  				resv := reflect.ValueOf(respV).Call([]reflect.Value{reflect.ValueOf(typedInterface).Elem()})[0]
   140  				if !resv.Bool() {
   141  					break
   142  				}
   143  			}
   144  		} else {
   145  			switch respV.(type) {
   146  			case io.Writer:
   147  				_, err = io.Copy(respV.(io.Writer), resp.Body)
   148  			default:
   149  				dc := json.NewDecoder(resp.Body)
   150  				dc.UseNumber()
   151  				err = dc.Decode(respV)
   152  				if err == io.EOF {
   153  					err = ErrEmptyResponseBody
   154  				}
   155  			}
   156  		}
   157  	}
   158  
   159  	return resp, err
   160  }
   161  
   162  func (c *Client) makeRequest(r *Request) (*http.Request, error) {
   163  	req, err := r.Build()
   164  	if err != nil {
   165  		return nil, err
   166  	}
   167  
   168  	c.applyDefaultHeader(req)
   169  
   170  	if req.Header.Get("Accept") == "" {
   171  		req.Header.Set("Accept", "application/json")
   172  	}
   173  	if req.Header.Get("Content-Type") == "" {
   174  		req.Header.Set("Content-Type", "application/json")
   175  	}
   176  	if req.Header.Get("Accept-Language") == "" {
   177  		req.Header.Set("Accept-Language", "en")
   178  	}
   179  
   180  	return req, nil
   181  }
   182  
   183  func (c *Client) applyDefaultHeader(req *http.Request) {
   184  	for k, vs := range c.DefaultHeader {
   185  		if req.Header.Get(k) != "" {
   186  			continue
   187  		}
   188  		for _, v := range vs {
   189  			req.Header.Add(k, v)
   190  		}
   191  	}
   192  }