Multiple go http clients based on http client

Patrik Tomášik
4 min readDec 13, 2018

Hi again,

in last topic i was talking about graceful golang application exit, today we will talk about multiple http clients with simple abstraction and preprocessing methods.

First of all, we will have to define request factory, it's cleaner and more transparent for other people which will be working on your project and much more portable, you can simply config request factory based on environment you are without changing any lines of your code, which is good right? :-)

So request factory, i'll pass really simplified, only for better idea about how it looks, what it does, etc.

Simple request factory

I've to simplified, so you can see only the main parts which are necessary, but what you can see?

There is struct with client config, with divided url to base components, like schema, host, base path, version(when you use versioned apis) everything is as standalone config item and it's for good reason, because you always change on environments only hostnames or schema maximum, when you work on low cost project or if its not important to have a https certificate on developer machines. Also you can see there is some simple auth with bearer token, when you will work on bigger project it will be good to replace it with some auth interface, with auth type(oauth, bearer, etc.), which will implement the auth mechanism. And that's, i'll talk about create and build method later with concrete client implementation.

Next you have to define some abstract client, because there's no reason why write everything again and again with every new client, we're smart developers so we keep KISS and DRY principle beware.

Simple abstract client

There's my simple implementation of http client i used for latest project, including simple retry mechanism for cases when your target host is unreachable due to network issues, or application deploy.

First it starts with PrepareRequest() method, which is wrapper on requestFactory .Create() method, it'll return the http.Request so you have all power of base http library utils, tools, etc.

Then you'll pass the request to the DoRequest() method with number of maximum retries, so you can specify on every request. There i see a space to refactoring or customizing the method, when the maximum retries will be optional param or it can be wrapped in some option struct. The doRequest mechanism uses internal http client instance, which you can pass to factory method, when you create the client, so again it has all of the features. It will be nice, if you have a builder for http client, you know what i mean, something like:

client.WithTimeout(5).WithSslCheck().Build()

or something like that, but back to client, after the client receive response it'll pass the the response with simple preprocess mechanism, which will do some standard checks for you and eventually wrap the error with concrete error type which are predefined or just return when it's standard acceptable code. Important is also the preprocess is relevant for the retry mechanism which will react on them.

For example, if you receive 404 — you don't want to retry because it always be 404, or any other of 403 codes except 409(duplicate) and im sure we can name a lot more, but it's not important right now. But there are codes which you want to handle with retry 503, 504, with sleeps of course, you have to give some time to the platform you communicate with, to regenerate, or finish the queued tasks.

Then you receive response and wrapped error and you can simply read or do what ever you want. I didn't do any special logic with response, some wrap or reading before i returned(except the error case), because it's always better to handle the response in concrete method, because you dont always need the response value, for example if you receive response for delete, you dont want to read it, if you push some task to process, you receive 201 and again you don't need value, etc.. so it's better to handle in every single case on your own.

Ok, the best part, concrete client implementation over base:

As you can see, it's simple layer, but you can really know what you do, for example create transport use will look really simple:

resp, err := client.CreateTransport(Transport{someOptions..})

it looks nice, work nice no.

Summary:

It's few line code, but it's powerful, you dont have to mix EP call uris by your own, just specify the final resource you're calling, everything in one place, simple to write it, simple to use it.

And you can change any part of the chain, requestFactory, client, etc.. or override the methods. Also you can write test on requestFactory or client — the preprocess part, because you can mock input and, so it's testable.

I hope you'll enjoyed and see you in next few days, what will be the next topic? I dont know now :-D, you can write me a hint to the commentary.

Cheers

--

--