A Handy Go Package to Call Internal HTTP APIs with Builder Pattern
go get github.com/ysyesilyurt/go-restclient/restclient
Essentially go-restclient
is a handy builder
wrapper around net/http
for all the pretty standard
stuff
that needs to be repeated in all HTTP Request/Response cycles. The purpose is to save you from the pain of massive code
duplication those ordinary cycles may lead.
As the number of distinct Internal HTTP requests you are making grows, the need for such a wrapper package also
exponentially grows. In that sense handiness level of go-restclient
is directly proportional to your need for such
request/responses 🙂
After a Build()
call on restclient.RequestBuilder()
upon one or many builder method calls:
- Constructs URL by escaping path components using provided components
- Sets queryParams if exists
- Validates that resulting URI is valid
- Sets custom and universal headers
- Sets authentication strategy to be used
- Sets Authorization header by applying provided authentication strategy
- Sets provided RequestBody if exists (or marshals then sets if given as JSON Body)
- Builds the request object
- Sets request context timeout and defer its cancellation
- Does Request (Times and Logs it if needed)
- Handles Response Status Code and any errors that can be returned from client's call (returns a
detailed
restclient.RequestError
) - Reads the body and unmarshal it into given variable's reference
- First build your HTTP Request using
restclient.RequestBuilder()
constructor and with all the builder methods you need.
// https://ysyesilyurt.com/tasks/1?tenantId=d90c3101-53bc-4c54-94db-21582bab8e17&vectorId=1
req, reqErr := restclient.RequestBuilder().
Scheme("https").
Host("ysyesilyurt.com").
PathElements([]string{"tasks", "1"}).
QueryParams(&queryParams).
Header(&headers).
Auth(testAuth).
ResponseReference(&response).
LoggingEnabled(true).
Timeout(30 * time.Second).
Build()
if reqErr != nil {
return errors.Wrap(reqErr, "Failed to construct HTTP request")
}
- Then make your call...
reqErr = req.Get()
if reqErr != nil {
return errors.Wrap(reqErr, "Failed to perform HTTP GET request")
}
Get() RequestError
Post() RequestError
Put() RequestError
Patch() RequestError
Delete() RequestError
Scheme(scheme string)
-> Sets scheme field of your URL. Example:
req, reqErr := restclient.RequestBuilder().
Scheme("https").
Build()
Host(host string)
-> Sets host field of your URL. Example:
req, reqErr := restclient.RequestBuilder().
Scheme("https").
Host("ysyesilyurt.com").
Build()
PathElements(pe []string)
-> Sets elements that reside in your URL's Path (not the query params) in the order given in the array. Example:
req, reqErr := restclient.RequestBuilder().
Scheme("https").
Host("ysyesilyurt.com").
PathElements([]string{"tasks", "1"}).
Build()
QueryParams(qp *url.Values)
-> Sets the Query Parameters that reside in your URL in the order given in the array. Example:
req, reqErr := restclient.RequestBuilder().
Scheme("https").
Host("ysyesilyurt.com").
PathElements([]string{"tasks", "1"}).
QueryParams(&url.Values{"tenantId": []string{"d90c3101-53bc-4c54-94db-21582bab8e17"}, "vectorId": []string{"1"}}).
Build()
RawUrl(rawUrl string)
-> Use this one if you do not want to parse your URL into several builder methods above and see your URL as a whole. This one will automatically parse your URL and set the fields accordingly. Example:
req, reqErr := restclient.RequestBuilder().
RawUrl("https://ysyesilyurt.com/tasks/1?tenantId=d90c3101-53bc-4c54-94db-21582bab8e17&vectorId=1").
Build()
Header(header *http.Header)
-> Sets the headers that you want to include in your request. Example:
req, reqErr := restclient.RequestBuilder().
RawUrl("https://ysyesilyurt.com/tasks/1?tenantId=d90c3101-53bc-4c54-94db-21582bab8e17&vectorId=1").
Header(&http.Header{"Content-Type": []string{"application/json"}, "Cookie": []string{"test-1234"}}).
Build()
Body(body io.Reader)
-> Sets the request body in the form ofio.Reader
for your request, if you want to include a body for your request of course. Example:
req, reqErr := restclient.RequestBuilder().
RawUrl("https://ysyesilyurt.com/tasks/1?tenantId=d90c3101-53bc-4c54-94db-21582bab8e17&vectorId=1").
Header(&http.Header{"Content-Type": []string{"application/json"}, "Cookie": []string{"test-1234"}}).
Body(bytes.NewBufferString(query)).
Build()
BodyJson(bodyJson interface{})
-> Use this one instead ofBody(body io.Reader)
if your request body is in JSON form. You can pass your struct object directly to this builder method, it is going to marshal your object and set the body asio.Reader
. Example:
requestBody := dummyBodyDto{
Id: 123,
Name: "1234",
}
req, reqErr := restclient.RequestBuilder().
RawUrl("https://ysyesilyurt.com/tasks/1?tenantId=d90c3101-53bc-4c54-94db-21582bab8e17&vectorId=1").
Header(&http.Header{"Content-Type": []string{"application/json"}, "Cookie": []string{"test-1234"}}).
BodyJson(requestBody).
Build()
Auth(auth Authenticator)
-> Sets the Authentication Strategy for your request. Implementrestclient.Authenticator
to create your ownAuthenticator
, an exampleBasic Auth
implementation can be found inbasic_authenticator.go
. Example:
basicAuth := newBasicAuthenticator("ysyesilyurt", "0123")
req, reqErr := restclient.RequestBuilder().
RawUrl("https://ysyesilyurt.com/tasks/1?tenantId=d90c3101-53bc-4c54-94db-21582bab8e17&vectorId=1").
Header(&http.Header{"Content-Type": []string{"application/json"}, "Cookie": []string{"test-1234"}}).
Auth(basicAuth).
Build()
ResponseReference(respRef interface{})
-> Sets the reference of the variable to map response object returned from request. Example:
var response dummyHttpResponse
req, reqErr := restclient.RequestBuilder().
RawUrl("https://ysyesilyurt.com/tasks/1?tenantId=d90c3101-53bc-4c54-94db-21582bab8e17&vectorId=1").
Header(&http.Header{"Content-Type": []string{"application/json"}, "Cookie": []string{"test-1234"}}).
ResponseReference(&response).
Build()
Request(req *http.Request)
-> If you happen to have a pre-prepared validhttp.Request
object and want to build a new request upon this request then you can simply use this builder method. Example:
var response dummyHttpResponse
req, reqErr := restclient.RequestBuilder().
Request("https://ysyesilyurt.com/tasks/1?tenantId=d90c3101-53bc-4c54-94db-21582bab8e17&vectorId=1").
Header(&http.Header{"Content-Type": []string{"application/json"}, "Cookie": []string{"test-1234"}}).
ResponseReference(&response).
Build()
Timeout(timeout time.Duration)
-> Sets the timeout value that should be used for the request. Default is 60 seconds. Example:
req, reqErr := restclient.RequestBuilder().
RawUrl("https://ysyesilyurt.com/tasks/1?tenantId=d90c3101-53bc-4c54-94db-21582bab8e17&vectorId=1").
Timeout(30 * time.Second)
Build()
LoggingEnabled(enabled bool)
-> Decides whether responses should be logged or not. Default is false. Example:
req, reqErr := restclient.RequestBuilder().
RawUrl("https://ysyesilyurt.com/tasks/1?tenantId=d90c3101-53bc-4c54-94db-21582bab8e17&vectorId=1").
LoggingEnabled(true)
Build()
go-restclient
defines restclient.RequestError
interface to cover all the errors that can be returned
from restclient.RequestBuilder.Build()
and result of the HTTP calls. The top level errors returned by the client can
be seen
in errors.go
. The methods that can be used within the boundaries of restclient.RequestError
are as follows:
type RequestError interface {
Error() string
GetTopLevelError() error // GetTopLevelError returns top level error that originates the title
GetUnderlyingError() error // GetUnderlyingError returns underlying error that originates the message
GetTitle() string // GetTitle returns top level error 'title' referring to message
GetMessage() string // GetMessage returns detailed error message for the request
GetStatusCode() int // GetStatusCode returns status code for the request. '0' means request failed for some reason and can be checked using methods below
Timeout() bool // Timeout returns if request failed due to a timeout
ConnectionError() bool // ConnectionError returns if request failed due to a connection error (Failed to get response for some reason)
ResponseParseError() bool // ResponseParseError returns if response of the request could not be parsed into given response reference variable
RequestBuildError() bool // RequestBuildError returns if request could not be built due to some reason
}
You can also use the legacy version which is located
on legacy
branch if you want to use go-restclient
using
traditional ways (with constructing the helper objects and using those altogether blah blah...) or if you want to use a
version that allows reusing the HTTP clients that's being used internally for sending the requests (Thanks to separation
of these objects, this legacy version of go-restclient
gives you the slightly extended feature set like separation of
client timeouts and request-specific timeouts and etc.). You can find more information about the version itself and its
usage from here. But all in all, I think the builder version is more handy and elegant so I just wanted to keep
builder version on the master branch.
Unit tests are implemented for the codebase but all the edge cases might not be covered, weird bugs may appear in such cases. Feel free to open an issue if you spot a bug. In addition if you have an improvement idea you can request it in the form of an issue (with providing a clear and brief description of the feature) or you can develop the improvement and open a PR with again a brief description of the reasoning.