Go http client setup for multiple endpoints? - go

I reuse the http client connection to make external calls to a single endpoint. An excerpt of the program is shown below:
var AppCon MyApp
func New(user, pass string, platformURL *url.URL, restContext string) (*MyApp, error) {
if AppCon == (MyApp{}) {
AppCon = MyApp{
user: user,
password: pass,
URL: platformURL,
Client: &http.Client{Timeout: 30 * time.Second},
RESTContext: restContext,
}
cj, err := cookiejar.New(nil)
if err != nil {
return &AppCon, err
}
AppCon.cookie = cj
}
return &AppCon, nil
}
// This is an example only. There are many more functions which accept *MyApp as a pointer.
func(ma *MyApp) GetUser(name string) (string, error){
// Return user
}
func main(){
for {
// Get messages from a queue
// The message returned from the queue provide info on which methods to call
// 'm' is a struct with message metadata
c, err := New(m.un, m.pass, m.url)
go func(){
// Do something i.e c.GetUser("123456")
}()
}
}
I now have the requirement to set up a client connections with different endpoints/credentials received via queue messages.
The problem I foresee is I can't just simply modify AppCon with the new endpoint details since a pointer to MyApp is returned, resulting in resetting c. This can impact a goroutine making a HTTP call to an unintended endpoint. To make matters non trivial, the program is not meant to have awareness of the endpoints (I was considering using a switch statement) but rather receive what it needs via queue messages.
Given the issues I've called out are correct, are there any recommendations on how to solve it?
EDIT 1
Based on the feedback provided, I am inclined to believe this will solve my problem:
Remove the use of a Singleton of MyApp
Decouple the http client from MyApp which will enable it for reuse
var httpClient *http.Client
func New(user, pass string, platformURL *url.URL, restContext string) (*MyApp, error) {
AppCon = MyApp{
user: user,
password: pass,
URL: platformURL,
Client: func() *http.Client {
if httpClient == nil {
httpClient = &http.Client{Timeout: 30 * time.Second}
}
return httpClient
}()
RESTContext: restContext,
}
return &AppCon, nil
}
// This is an example only. There are many more functions which accept *MyApp as a pointer.
func(ma *MyApp) GetUser(name string) (string, error){
// Return user
}
func main(){
for {
// Get messages from a queue
// The message returned from the queue provide info on which methods to call
// 'm' is a struct with message metadata
c, err := New(m.un, m.pass, m.url)
// Must pass a reference
go func(c *MyApp){
// Do something i.e c.GetUser("123456")
}(c)
}
}

Disclaimer: this is not a direct answer to your question but rather an attempt to direct you to a proper way of solving your problem.
Try to avoid a singleton pattern for you MyApp. In addition, New is misleading, it doesn't actually create a new object every time. Instead you could be creating a new instance every time, while preserving the http client connection.
Don't use constructions like this: AppCon == (MyApp{}), one day you will shoot in your leg doing this. Use instead a pointer and compare it to nil.
Avoid race conditions. In your code you start a goroutine and immediately proceed to the new iteration of the for loop. Considering you re-use the whole MyApp instance, you essentially introduce a race condition.
Using cookies, you make your connection kinda stateful, but your task seems to require stateless connections. There might be something wrong in such an approach.

Related

Using `Context` to implement timeout

Assuming that I have a function that sends web requests to an API endpoint, I would like to add a timeout to the client so that if the call is taking too long, the operation breaks either by returning an error or panicing the current thread.
Another assumption is that, the client function (the function that sends web requests) comes from a library and it has been implemented in a synchronous way.
Let's have a look at the client function's signature:
func Send(params map[string]string) (*http.Response, error)
I would like to write a wrapper around this function to add a timeout mechanism. To do that, I can do:
func SendWithTimeout(ctx context.Context, params map[string]string) (*http.Response, error) {
completed := make(chan bool)
go func() {
res, err := Send(params)
_ = res
_ = err
completed <- true
}()
for {
select {
case <-ctx.Done():
{
return nil, errors.New("Cancelled")
}
case <-completed:
{
return nil, nil // just to test how this method works
}
}
}
}
Now when I call the new function and pass a cancellable context, I successfully get a cancellation error, but the goroutine that is running the original Send function keeps on running to the end.
Since, the function makes an API call meaning that establishing socket/TCP connections are actually involved in the background, it is not a good practice to leave a long-running API behind the scene.
Is there any standard way to interrupt the original Send function when the context.Done() is hit?
This is a "poor" design choice to add context support to an existing API / implementation that did not support it earlier. Context support should be added to the existing Send() implementation that uses it / monitors it, renaming it to SendWithTimeout(), and provide a new Send() function that takes no context, and calls SendWithTimeout() with context.TODO() or context.Background().
For example if your Send() function makes an outgoing HTTP call, that may be achieved by using http.NewRequest() followed by Client.Do(). In the new, context-aware version use http.NewRequestWithContext().
If you have a Send() function which you cannot change, then you're "out of luck". The function itself has to support the context or cancellation. You can't abort it from the outside.
See related:
Terminating function execution if a context is cancelled
Is it possible to cancel unfinished goroutines?
Stopping running function using context timeout in Golang
cancel a blocking operation in Go

cancel a web request and handle errors inside the ReverseProxy Director function

I am wondering if it would be possible to cancel a web request or send an internal response to the client inside the ReverseProxy.Director function.
Suppose we do something that throws an error, or we have other reason to not forward the request.
proxy := &httputil.ReverseProxy{
Director: func(r *http.Request) {
err := somethingThatThrows()
},
}
http.Handle("/", proxy)
A solution to this might be the below, but it's not as neat as the above way to use the proxy. I am also not sure to which degree the request should be modified that way. The director seems to be the place to do that.
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
err := somethingThatThrows()
if err != nil {
w.WriteHeader(http.StatusInternalServerError)
return
}
proxy.ServeHTTP(w, r)
})
if it would be possible to cancel a web request [...]
You can cancel the request that is passed to the Director function, BUT there are some details to consider:
the correct way to cancel a request is to cancel its context
you can not cancel contexts where you didn't set a (deadline|timeout|cancelfunc) yourself → i.e. you must have access to the cancel function → i.e. you can't cancel parent contexts created by someone else.
the *http.Request passed to Director function is a clone of the original request
Based on the points above, you can replace the request in the Director with another one that has a cancellable context. It may look like the following:
proxy := &httputil.ReverseProxy{
Director: func(req *http.Request) {
// create a cancellable context, and re-set the request
ctx, cancel := context.WithCancel(req.Context())
*req = *req.WithContext(ctx)
err := somethingThatThrows()
if err != nil {
cancel()
return
}
},
}
Then the code above doesn't do anything else by itself. What should happen is that the httputil.ReverseProxy.Transport function, which implements http.RoundTripper checks whether the request context is cancelled, before actually send anything to the upstream service.
The documentation of Director states:
Director must be a function which modifies the request into a new request to be sent using Transport.
When the Transport is not provided, it will fall back to http.DefaultTransport, which aborts the request when the context is cancelled. The current code (Go 1.17.5) looks like:
select {
case <-ctx.Done():
req.closeBody()
return nil, ctx.Err()
default:
}
If you provide your own implementation of http.RoundTripper you may want to implement that behavior yourself. Remember also that the context done channel is nil if it's not cancellable, so you have to set a cancel func and call cancel() in order to have that select run the "done" case.
or send an internal response to the client inside the ReverseProxy.Director
Based on the same quote above docs, you should not write to the http.ResponseWriter from within the Director function — assuming you are even closing around it. As you can see the Director itself doesn't get the http.ResponseWriter as an argument, and this should already be a self-explanatory detail.
If you want to specify some other behavior in case the request can't be forwarded, and assuming that whatever implementation of http.RoundTripper is returning error when the req context is cancelled, you can provide your ReverseProxy.ErrorHandler function:
proxy.ErrorHandler = func(writer http.ResponseWriter, request *http.Request, err error) {
// inspect err
// write to writer
}
The ErrorHandler will be invoked when Transport returns error, including when the error comes from a cancelled request, and it does have http.ResponseWriter as an argument.

Does WithContext method need to panic if context is nil?

I want to write a WithContext method for a struct and am taking inspiration from net/http's Request.WithContext.
My question is: why does Request.WithContext panic if the context is nil:
func (r *Request) WithContext(ctx context.Context) *Request {
if ctx == nil {
panic("nil context")
}
...
}
And should mine as well?
For more context on why I want to create a WithContext method: I am implementing an interface that does not provide a context parameter in its signature but believe the implementation requires it.
More specifically, I am writing a Redis backend for gorilla/session using the official Redis client for Go, where the Get and Set methods take context.Context.
The idea is that my redis store will be shallow copied with the new context object, when needed, and then used:
type redisStore struct {
codecs []securecookie.Codec
backend Backend // custom interface for Redis client
options *sessions.Options
ctx context.Context
}
func (s *redisStore) WithContext(ctx context.Context) *redisStore {
if ctx == nil {
panic("nil context")
}
s2 := new(redisStore)
*s2 = *s
s2.ctx = ctx
return s2
}
// Backend
type Backend interface {
Set(context.Context, string, interface{}) error
Get(context.Context, string) (string, error)
Del(context.Context, string) error
}
The purpose of panicking is to "fail fast" and reject a nil context without changing the function signature.
If the function does not panic then it must return error in order to reject a bad input:
func (r *Request) WithContext(ctx context.Context) (*Request, error) {
if ctx == nil {
return nil, errors.New("nil ctx")
}
...
}
And then who calls this function must handle the error to avoid using an invalid request:
request, err = request.WithContext(nil)
if err != nil {
}
By handling the error you are introducing a control flow branch, and you lose method chaining. You also cannot immediately use WithContext return value into a function parameter:
// cannot do, because WithContext returns an error too
data, err := fetchDataWithContext(request.WithContext(ctx), otherParam)
Also it would create an error instance that will be eventually garbage collected. This all is cumbersome, poor usability and unnecessary alloc simply for saying "don't give me a nil context".
About creating a redis store with a context, the context documentation is clear:
Package context defines the Context type, which carries deadlines, cancellation signals, and other request-scoped values across API boundaries and between processes.
The important detail is request-scoped. So setting a context in the redis client itself is contrary to this recommendation. You should pass context values at each get/set call.
The context of an HTTP request is canceled if the client closes the connection. When the context is canceled, all its child contexts are also canceled, so a nil context would panic then. Because of this, you cannot pass a nil context to WithContext.
Whether or not your redis store should panic depends on how you are going to use that context. It is usually not a good idea to include a context in a struct. One acceptable way of doing that is if the struct itself is a context. Contexts should be created for each call, should live for the duration of that call, and then thrown away.

How to gracefully handle errors in apache kafka producer

I have an ecommerce app where I'm sending a message to a kafka server every time a user adds something to a cart. I can send the message and consume it from a client, however, I am curious about error handling. Once in a while, my Go server fails because of a network error or some other reasons. Adding to cart functionality will be an essential part of the app, so I don't want kafka producer to fail that functionality or become dependent on it. I tried separate them by creating a separate function for kafka Producer and I think the kafka.Produce() function is non-blocking, so even if that fails user still should be able to add items to a cart. Here's a sample code (I put the full code for kafka part, but I trimmed the implementation of adding to cart for readability). Is there a way to quit from kafka function if something goes wrong or if it is longer than couple of seconds-timeout? So, the adding to cart functionality wouldn't hang or cause the server to fail. I'm not very experienced with channels and concurrency in Go, so I can't really tell if this could become an issue with this current design.
ADD TO CART
func addToCart(c *context.Context, rw web.ResponseWriter, req *web.Request) {
cartID := req.PathParams["id"]
var items []map[string]interface{}
if err := json.NewDecoder(req.Body).Decode(&items); err != nil {
errors.Write(rw, 400, "Unable to parse request body JSON or invalid data format.")
return
}
//MAKE SOME OPERATIONS AND SAVE IT TO DATABASE
cart, jsonErr := saveToDB(c, cartID, items)
if jsonErr != nil {
jsonErr.Write(rw)
return
}
webLib.Write204(rw)
deliveryChan := make(chan kafka.Event)
kafkaMessage("cart_topic", "sample-cart-event-message", deliveryChan, rw, rq)
return
}
KAFKA
func kafkaMessage(topic string, message []byte, deliveryChan chan kafka.Event, rw web.ResponseWriter, req *web.Request) {
err := c.KafkaProducer.Produce(&kafka.Message{
TopicPartition: kafka.TopicPartition{Topic: &topic, Partition: kafka.PartitionAny},
Value: message,
}, deliveryChan)
if err != nil {
c.Log("error:%s", err)
return
}
e, ok := <-deliveryChan
if !ok{
c.Log("Channel is closed for kafka producer")
return
}
m, ok := e.(*kafka.Message)
if !ok{
c.Log("There has been an error obtaining the kafka message")
return
}
if m.TopicPartition.Error != nil {
fmt.Printf("Delivery failed: %v\n", m.TopicPartition.Error)
} else {
c.Log("Delivered message to topic %s [%d] at offset %v\n",
*m.TopicPartition.Topic, m.TopicPartition.Partition, m.TopicPartition.Offset)
}
}
So the send to kafka is async but you're, in effect, turning it into a sync function by waiting for a "success" message.
A couple quick options.
1: You can totally disregard the status of the async message by passing a nil channel as deliveryChan. Then you really get a "fire and forget" async model. It sounds like this may be what you are looking for.
2: You can run kafkaMessage in a goroutine by simply changing to
deliveryChan := make(chan kafka.Event)
go kafkaMessage("cart_topic", "sample-cart-event-message", deliveryChan, rw, rq)
return
Then you can keep your waiting for a message, logging, etc in that function. You can even add retries if you want! Be aware that in this case you can get a backlog of goroutines waiting on response messages / retrying / etc since you're essentially queuing up operations as goroutines. For most applications this won't be a problem as well as you're not continually falling behind in processing, but still - something to keep an eye on with monitoring!
There are lots of other patterns to follow here, but these are fairly low lift and give you a few options.

Go- authentication logic pattern in web app

I want to determine a simple and useful pattern for user authentication in a web app being written in golang.
I have come up with two patterns. First one is enabling the programmer to have his functions separate form the authentication logic, and has cleaner HandleFunc parts in main() that one can see only by loking main() to see what parts are under authentication control.
Second one is making programmer include a decision in every function deal with authentication required urls. An if statement checks by a authp() function defined else where.
Which one is better pattern for such necessity?
What are the better patterns for this job?
Is it even possible to pass a function to http.HandleFunc that has signature other than func urlFunc (ResponseWriter, *Request) bu like func urlFunc (successFunc, failFunc) or func urlFunc (ResponseWriter, *Request, successFunc, failFunc) as in authenticationGateKeeper function of First Way below, if not a suitable workaround for that?
//First Way
package main
func authGateKeeper(successFunc, failFunc) {
if (authp()) {
successFunc
} else {
failFunc
}
}
func authp() boolean {
//authentication logic, db query, or session check etc.
}
//usage in main
http.HandleFunc("/", authGateKeeper)
//Second Way; other alternative, in each function check pattern
func f(w, r) {
if (authp()) {
//function's processes
} else {
//the fail case function or processes
}
}
func authp() boolean {
//authentication logic, db query, or session check etc.
}
//usage in main
http.HandleFunc("/", f)
There are many ways to spin this, and it's arguable whether one is outright "better". I'd strongly suggest writing some middleware that wraps your routes and enforces the check, calling the wrapped handler only on success.
Note that I'm going to make a few assumptions here as you haven't told us how you're managing sessions (cookies? server-side?) and/or what kind of authorization you might need on top of authentication.
// Middleware - a function that sits in the 'middle' of your request processing.
func RequireAuth(h http.Handler) http.Handler) {
fn := func(w http.ResponseWriter, r *http.Request) {
// Assuming gorilla/sessions
session, err := store.Get("name", r)
if err != nil {
// Raise HTTP 500
return
}
// We'll assume you're storing the userID in the cookie|server session
// upon login elsewhere.
id := session.Values["userID"]
// Probably returns a *yourapp.User
user, err := db.GetUser(id)
if err != nil {
// Raise HTTP 500
return
}
if user == nil {
http.Error(w, http.StatusText(http.StatusUnauthorized), http.StatusUnauthorized)
// Don't forget these 'naked' returns - if you miss one, your
// handler will keep processing beyond the error and result in
// unintended side effects
return
}
// Further checks here - i.e. checking user.Active == true, etc.
// The userID matches one in the DB, so let's proceed
h.ServeHTTP(w, r)
}
return http.HandlerFunc(fn)
}
// And in your router - assuming just vanilla net/http
http.Handle("/", RequireAuth(yourHandlerFunc))
http.Handle("/", RequireAuth(someOtherHandler))
// Note that using gorilla/mux or goji can help give you "subrouters" so you
// don't have to wrap every single route with your middleware (messy, error prone)
I'd also suggest some reading on Go middleware1 composition2 which will help you in the future.
If you want to call a custom error page, just write a handler - e.g. UnauthorizedHandler that satisfies http.Handler and just call UnauthorizedHandler.ServeHTTP(w, r) instead of http.Error along the way.

Resources