Suppose I have a file that modifies database, should the functions share a single context or should each function have their own context?
Sharing context
var (
ctx = context.Background()
)
func test1() {
res, err := Collection.InsertOne(ctx, data)
}
func test2() {
res, err := Collection.InsertOne(ctx, data)
}
Or should it be like this?
func test1() {
res, err := Collection.InsertOne(context.Background(), data)
}
func test2() {
res, err := Collection.InsertOne(context.Background(), data)
}
Neither. Your context should be request-scoped (for whatever "request" means in your application) and passed down the call chain to each function.
func test1(ctx context.Context) {
res, err := Collection.InsertOne(ctx, data)
}
func test2(ctx context.Context) {
res, err := Collection.InsertOne(ctx, data)
}
If you're building a web server, as indicated in comments, you'll usually get your context from the HTTP request in your handler:
func myHandler(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
// now pass ctx to any fuction that needs it. This way, if the HTTP
// client/browser cancels the request, your downstream functions will
// be able to abort immediately, without wasting time finishing their
// work.
}
You should not use the first approach. Context is something to be passed down to functions, it should not be declared as a global variable.
The second approach can be used at times, especially if there is no request context. However, if multiple calls are handled within a single server context, you should pass down the context for that call to all the other calls getting a context, so when the context is canceled or expired, all calls would fail.
Related
In program bellow I have two routers. One is working at localhost:3000 and acts like a public access point. It also may send requests with data to another local address which is localhost:8000 where data is being processed. Second router is working at localhost:8000 and handles processing requests for the first router.
Problem
The first router sends a request with context to the second using http.NewRequestWithContext() function. The value is being added to the context and the context is added to request. When request arrives to the second router it does not have value that was added previously.
Some things like error handling are not being written to not post a wall of code here.
package main
import (
"bytes"
"context"
"net/http"
"github.com/go-chi/chi"
"github.com/go-chi/chi/middleware"
)
func main() {
go func() {
err := http.ListenAndServe(
"localhost:3000",
GetDataAndSolve(),
)
if err != nil {
panic(err)
}
}()
go func() {
err := http.ListenAndServe( // in GetDataAndSolve() we send requests
"localhost:8000", // with data for processing
InternalService(),
)
if err != nil {
panic(err)
}
}()
// interrupt := make(chan os.Signal, 1)
// signal.Notify(interrupt, syscall.SIGTERM, syscall.SIGINT)
// <-interrupt // just a cool way to close the program, uncomment if you need it
}
func GetDataAndSolve() http.Handler {
r := chi.NewRouter()
r.Use(middleware.Logger)
r.Get("/tasks/str", func(rw http.ResponseWriter, r *http.Request) {
// receiving data for processing...
taskCtx := context.WithValue(r.Context(), "str", "strVar") // the value is being
postReq, err := http.NewRequestWithContext( // stored to context
taskCtx, // context is being given to request
"POST",
"http://localhost:8000/tasks/solution",
bytes.NewBuffer([]byte("something")),
)
postReq.Header.Set("Content-Type", "application/json") // specifying for endpoint
if err != nil { // what we are sending
return
}
resp, err := http.DefaultClient.Do(postReq) // running actual request
// pls, proceed to Solver()
// do stuff to resp
// also despite arriving to middleware without right context
// here resp contains a request with correct context
})
return r
}
func Solver(next http.Handler) http.Handler { // here we end up after sending postReq
return http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
if r.Context().Value("str").(string) == "" {
return // the request arrive without "str" in its context
}
ctxWithResult := context.WithValue(r.Context(), "result", mockFunc(r.Context()))
next.ServeHTTP(rw, r.Clone(ctxWithResult))
})
}
func InternalService() http.Handler {
r := chi.NewRouter()
r.Use(middleware.Logger)
r.With(Solver).Post("/tasks/solution", emptyHandlerFunc)
return r
}
Your understanding of context is not correct.
Context (simplifying to an extent and in reference to NewRequestWithContext API), is just an in-memory object using which you can control the lifetime of the request (Handling/Triggering cancellations).
However your code is making a HTTP call, which goes over the wire (marshaled) using HTTP protocol. This protocol doesn't understand golang's context or its values.
In your scenario, both /tasks/str and /tasks/solution are being run on the same server. What if they were on different servers, probably different languages and application servers as well, So the context cannot be sent across.
Since the APIs are within the same server, maybe you can avoid making a full blown HTTP call and resort to directly invoking the API/Method. It might turn out to be faster as well.
If you still want to send additional values from context, then you'll have to make use of other attributes like HTTP Headers, Params, Body to send across the required information. This can provide more info on how to serialize data from context over HTTP.
I'm making multiple goroutines share a single connection by passing client as an argument.
uri := "mongodb://localhost:27017"
ctx := context.Background()
client, err := mongo.Connect(ctx, options.Client().ApplyURI(uri))
go Foo(client)
go Bar(client)
func Foo(client *mongo.Client) {
// ...
}
func Bar(client *mongoClient) {
// ...
}
I'm confused about what to do with ctx. Should I create a new context everytime I query to the database, or should I reuse context just like the client?
It depends on how your Foo and Bar methods behave. Let's imagine the Foo method is a simple short-lived goroutine that makes one query to DB and the only thing you want is to check if its parent context is not Done or Cancelled. Then you can provide parent context to your Foo method.
func main() {
uri := "mongodb://localhost:27017"
ctx := context.Background()
client, err := Connect(ctx, uri)
ctx, cancel := context.WithCancel(ctx)
if err != nil {
panic(err)
}
go Foo(ctx, client)
go Bar(context.WithValue(ctx, "uri", uri), client)
// cancel parent context
cancel()
time.Sleep(5*time.Second)
}
func Foo(ctx context.Context, client *Client) {
fmt.Printf("Foo: %s\n", ctx.Value("uri"))
select {
case <- ctx.Done():
err := ctx.Err()
if err != nil {
// you could switch for the actual reason
fmt.Println("In our case context canceled: ", err)
return
}
fmt.Printf("Do something...")
}
}
On the other hand, if Bar performs some non-trivial logic and makes more than one call to DB you probably want a separate context to be able to cancel it separately from your parent context. Then you could derive a new context from your parent.
func Bar(ctx context.Context, client *Client) {
// Bar has a non trivial logic and needs a separate cancellation and handling
ctx, cancelFunc := context.WithCancel(ctx)
fmt.Printf("Bar: %s\n", ctx.Value("uri"))
// cancel derived context
cancelFunc()
}
I have also done it like this
type DB struct {
client *mongo.Client
}
func (db *DB) GetVideoStream {}
func main() {
ctx, _ := context.WithTimeout(context.Background(), 60*time.Second)
client, err := mongo.Connect(ctx, clientOpts)
db := &DB{client: client}
go db.GetVideoStream()
http.HandleFunc("/api/", db.GetVideoStream)
}
You can use pointer receivers to do the same thing.
I am new still new to the language
I store a double linked list in PostgreSQL. I have a Go API to manage this list.
There is a function that creates new Node (in specific position). Let's assume there is an INSERT SQL query inside of it.
Also, there is a function that deletes Node (by id). Let's assume there is a DELETE SQL query inside of it.
It is well known that if you need to move a Node to different position you should call DeleteNode() function and CreateNode() function. So there is the third function called MoveNode()
func MoveNode() error {
if err := DeleteNode(); err != nil {
return err
}
if err := CreateNode(); err != nil {
return err
}
return nil
}
But these functions (which are inside of MoveNode() should be called in one transaction.
Is there a way to "merge" functions in Go? Or what is the way to solve this problem (except copy & paste code from 2 functions to the third)?
p.s The idea is simple: you have two functions which do some SQL queries and you need to do these queries in one transaction (or call 2 functions in one transaction)
The better way to go about this here will be to move tx.Commit() outside the query execution functions (DeleteNode() and CreateNode() here)
Suggested Solution :
func MoveNode() error {
tx, err := db.Begin()
// err handling
res, err := DeleteNode(tx)
// err handling
res, err := CreateNode(tx)
// err handling
tx.Commit()
}
func DeleteNode(transactionFromDbBegin) (responseFromExec, errorFromExec) {
//...
}
func CreateNode(transactionFromDbBegin) (responseFromExec, errorFromExec) {
//...
}
This should do the trick.
I am trying to write a unit test using gotests and gomock to my restful service written in golang using gorilla but service fails to get variables from the url
Here is my request
req, err := http.NewRequest("GET", "product/5b5758f9931653c36bcaf0a0", nil)
actual endpoint is product/{id}
when I step into my service at the below code
params := mux.Vars(req)
params map is empty when it should have id key mapped to 5b5758f9931653c36bcaf0a0
Strange part is endpoint works fine from post man.
May I know whats wrong with the request?
This solved the issue
req = mux.SetURLVars(req, map[string]string{"id": "5b5758f9931653c36bcaf0a0"})
Since you're using GET requests, you can use the http.Get function, it works as expected:
package main
import (
"fmt"
"net/http"
"github.com/gorilla/mux"
)
func handle(w http.ResponseWriter, r *http.Request) {
params := mux.Vars(r)
fmt.Println(params)
}
func main() {
m := mux.NewRouter()
m.HandleFunc("/products/{id}", handle)
http.Handle("/", m)
go func() {
http.ListenAndServe(":8080", nil)
}()
_, err := http.Get("http://localhost:8080/products/765")
// Handle Error
}
If you really want to use http.NewRequest, that function doesn't actually execute the request so here is what you would need:
req, err := http.NewRequest("GET", "product/5b5758f9931653c36bcaf0a0", nil)
client := &http.Client{}
client.Do(req)
Create the mux Router in a separate function in the source code and call that directly in your test.
In Source code:
func Router() *mux.Router {
r := mux.NewRouter()
r.HandleFunc("/product/{id}", productHandler)
return r
}
func main() {
http.Handle("/", Router())
}
In Test:
func TestProductHandler(t *testing.T) {
r := http.NewRequest("GET", "product/5b5758f9931653c36bcaf0a0", nil)
w := httptest.NewRecorder()
Router().ServeHTTP(w, r)
}
Found the related solution in one of the google groups forums.
https://groups.google.com/forum/#!msg/golang-nuts/Xs-Ho1feGyg/xg5amXHsM_oJ
I writing a client that connects to a server with REST endpoints. The client needs to make a chain of 11 different requests to complete an action (it's a rococo backup system).
I'm writing my client in Go and I also want to write my mocks/tests in Go. What I'm unclear about is how a test called func TestMain would call into the client's func main(), to test completion of the chain of 11 requests.
My client's binary would be run from the shell in the following way:
$ client_id=12345 region=apac3 backup
How would I call func main() from the tests, with environment variables set? Or is there another approach? (I'm comfortable writing tests, so that's not the issue)
I'm looking at the Advanced Example in jarcoal/httpmock (but I could use another library). At the end the example says // do stuff that adds and checks articles, is that where I would call main()?
I've pasted the Advanced Example below, for future reference.
func TestFetchArticles(t *testing.T) {
httpmock.Activate()
defer httpmock.DeactivateAndReset()
// our database of articles
articles := make([]map[string]interface{}, 0)
// mock to list out the articles
httpmock.RegisterResponder("GET", "https://api.mybiz.com/articles.json",
func(req *http.Request) (*http.Response, error) {
resp, err := httpmock.NewJsonResponse(200, articles)
if err != nil {
return httpmock.NewStringResponse(500, ""), nil
}
return resp, nil
},
)
// mock to add a new article
httpmock.RegisterResponder("POST", "https://api.mybiz.com/articles.json",
func(req *http.Request) (*http.Response, error) {
article := make(map[string]interface{})
if err := json.NewDecoder(req.Body).Decode(&article); err != nil {
return httpmock.NewStringResponse(400, ""), nil
}
articles = append(articles, article)
resp, err := httpmock.NewJsonResponse(200, article)
if err != nil {
return httpmock.NewStringResponse(500, ""), nil
}
return resp, nil
},
)
// do stuff that adds and checks articles
}
Writing this out helped me answer my own question.
main() would read in environment variables and then call a function like doBackup(client_id, region). My test would mock the endpoints and then call doBackup(client_id, region).