The following code attempts to send a POST API request with a payload that is in RequestDetails.FormData. When I run main.go function, then I get the following errors.
go run main.go
# command-line-arguments
./main.go:53:17: not enough arguments in call to http.HandleFunc
./main.go:53:33: not enough arguments in call to reqDetails.Send
have ()
want (http.ResponseWriter, *http.Request)
./main.go:53:33: reqDetails.Send() used as value
The code is available below. Anybody knows what I could do wrong here? Thanks a lot for your help.
//main.go
package main
import (
"bytes"
"encoding/json"
"fmt"
"log"
"net/http"
)
// RequestDetails contains input data for Send
type RequestDetails struct {
EndPoint string
FormType string
FormData map[string]string
}
// Send sends API POST request to an endpoint
func (rd RequestDetails) Send(w http.ResponseWriter, r *http.Request) {
json_data, err := json.Marshal(rd.FormData)
if err != nil {
log.Fatal(err)
}
resp, err := http.Post(rd.EndPoint, rd.FormType, bytes.NewBuffer(json_data))
if err != nil {
log.Fatal(err)
}
fmt.Println(resp)
}
func main() {
m := map[string]string{
"AuthParamOne": "AP0000001",
"AuthParamTwo": "AP0000002",
"AuthParamThree": "AP0000003",
}
reqDetails := RequestDetails{
EndPoint: "https://httpbin.org/post",
FormType: "application/json",
FormData: m,
}
http.HandleFunc(reqDetails.Send())
}
you have to use HandleFunc in following below:
func HandleFunc(pattern string, handler func(ResponseWriter, *Request))
for code above follow this:
http.HandleFunc("/test",reqDetails.Send) //-> add reference instead of calling 'reqDetails.Send()'
reference: https://pkg.go.dev/net/http#HandleFunc
please vote up :)
In your Send method, you don't make use of w http.ResponseWriter, r *http.Request, So it seems you don't need them:
func (rd RequestDetails) Send() {...
Also in your last line, HandleFunc requires different arguments which once again is not necessary in your case. Just try to run the Send method:
reqDetails.Send()
The whole main.go file:
//main.go
package main
import (
"bytes"
"encoding/json"
"fmt"
"log"
"net/http"
)
// RequestDetails contains input data for Send
type RequestDetails struct {
EndPoint string
FormType string
FormData map[string]string
}
// Send sends API POST request to an endpoint
func (rd RequestDetails) Send() {
json_data, err := json.Marshal(rd.FormData)
if err != nil {
log.Fatal(err)
}
resp, err := http.Post(rd.EndPoint, rd.FormType, bytes.NewBuffer(json_data))
if err != nil {
log.Fatal(err)
}
fmt.Println(resp)
}
func main() {
m := map[string]string{
"AuthParamOne": "AP0000001",
"AuthParamTwo": "AP0000002",
"AuthParamThree": "AP0000003",
}
reqDetails := RequestDetails{
EndPoint: "https://httpbin.org/post",
FormType: "application/json",
FormData: m,
}
reqDetails.Send()
}
if your code like this
watcher := bufio.NewReader(os.Stdin)
input, _ := watcher.ReadString()
fmt.Println(input)
you needed this for reading line line
old -> input, _ := watcher.ReadString()
new -> input, _ := watcher.ReadString('\n')
I have a custom handler for my API endpoints like this:
type HTTPError struct {
Error error
Message string
Code int
}
type endpointREST func(http.ResponseWriter, *http.Request) *HTTPError
func (fn endpointREST) ServeHTTP(w http.ResponseWriter, r *http.Request) {
if e := fn(w, r); e != nil {
http.Error(w, e.Message, e.Code)
}
}
my example route look like this:
func GetShare(w http.ResponseWriter, r *http.Request) *HTTPError {
vars := mux.Vars(r)
fmt.Println(r.URL) // http://127.0.0.1:36455/share/5713d228-a042-446d-a5e4-183b19fa832a
fmt.Println(vars) // -->> always empty map when testing
return nil
}
These routes work well (manual, using Postman) after setting them up with
router := mux.NewRouter().StrictSlash(true)
handler := cors.Default().Handler(router)
router.Handle("/share/{id}", endpointREST(GetShare)).Methods("GET")
log.Fatal(http.ListenAndServe(":6969", handler))
The Problem is, that i can't test the API this way, since mux.Vars(r) will always return an empty map in the testing environment.
This is my testing code:
func TestGetShare(t *testing.T) {
Reset()
router := mux.NewRouter()
ts := httptest.NewServer(router)
router.Handle("/share/{id}", endpointREST(GetShare)).Methods("GET")
defer ts.Close()
t.Run("unauthorized", func(t *testing.T) {
req, _ := http.NewRequest("GET", ts.URL + "/share/5713d228-a042-446d-a5e4-183b19fa832a", nil)
res, _ := http.DefaultClient.Do(req)
assert.Equal(t, http.StatusUnauthorized, res.StatusCode)
})
}
I suggest you tu use the SetURLVars test helper func:
// SetURLVars sets the URL variables for the given request, to be accessed via
// mux.Vars for testing route behaviour. Arguments are not modified, a shallow
// copy is returned.
//
// This API should only be used for testing purposes; it provides a way to
// inject variables into the request context. Alternatively, URL variables
// can be set by making a route that captures the required variables,
// starting a server and sending the request to that server.
func SetURLVars(r *http.Request, val map[string]string) *http.Request {
return requestWithVars(r, val)
}
I have found a lot of golang tutorials on how to handle transactions, clean architecture, but none has satisfied my needs.
I have a go web application with a series of rest API each of one served by a three layers structure:
API
Service
Dao
DB
This is the main :
func main() {
DB = db.New()
dao = dao.New(DB)
service := service.New(dao)
server := &http.Server(....)
server.ListenAndServe()
}
Now, what I would realize is a transaction manager in the service layer.
Something like this:
type Service interface {
TransactionalExecute(fn func() error)
}
type ServiceImpl struct {
dao DaoImpl
}
func (service *ServiceImpl) TransactionalExecute(fn func() error) {
service.dao.TxBegin()
err := fn()
service.dao.TxEnd(err)
}
Dao should be like this:
type Dao interface {
TxBegin()
TxEnd(err error)
}
type DaoImpl struct {
DB db
tx *sql.Tx
}
func (dao *DaoImpl) TxBegin() {
dao.tx = dao.db.Begin()
}
func (dao *DaoImpl) TxEnd(err error) {
if p:= recover(); p != nil {
dao.tx.Rollback()
panic(p)
} else if err != nil {
dao.tx.Rollback()
} else {
dao.tx.Commit()
}
}
This kind of POC has two problems :
fn func() error parameter passed in the transactionalExecute() method must use the dao.Tx variable of the dao instance
This approach is not thread-safe : I am currently using gorilla mux and each http request will start a goroutine using a single instance of service, dao and DB. These instances are shared between multiple threads and is not safe. Anyway I was thinking about the use of mutex to access to the dao.tx variable but I am concerned about performances.
Any suggestion? Or different approaches to the problem?
Transactions should not really be part of DAO layer, it's a completely separate concern. A transaction is simply a single connection that executes all statements atomically. DAO, on the other hand, is a collection of database functions which use connections/transactions.
You can implement transactions and DAO nicely as two separate interfaces. Transaction can be an interface that is implemented both by sql.DB (connection pool from the standard library) and sql.Tx (transaction).
// Transaction represents both database connection pool and a single transaction/connection
type Transaction interface {
// ExecContext from database/sql library
ExecContext(ctx context.Context, query string, args ...interface{}) (sql.Result, error)
// PrepareContext from database/sql library
PrepareContext(ctx context.Context, query string) (*sql.Stmt, error)
// QueryContext from database/sql library
QueryContext(ctx context.Context, query string, args ...interface{}) (*sql.Rows, error)
// QueryRowContext from database/sql library
QueryRowContext(ctx context.Context, query string, args ...interface{}) *sql.Row
// QueryxContext from sqlx library
QueryxContext(ctx context.Context, query string, args ...interface{}) (*sqlx.Rows, error)
// QueryRowxContext from sqlx library
QueryRowxContext(ctx context.Context, query string, args ...interface{}) *sqlx.Row
// PreparexContext from sqlx library
PreparexContext(ctx context.Context, query string) (*sqlx.Stmt, error)
// GetContext from sqlx library
GetContext(ctx context.Context, dest interface{}, query string, args ...interface{}) error
// SelectContext from sqlx library
SelectContext(ctx context.Context, dest interface{}, query string, args ...interface{}) error
}
We use both database/sql and sqlx extended library for easier data scanning, you can just drop sqlx functions if you do not need them. Then a helper function for easier work:
// A TxFn is a function that will be called with an initialized `Transaction` object
// that can be used for executing statements and queries against a database.
type TxFn func(Transaction) error
// WithTransaction creates a new transaction and handles rollback/commit based on the
// error object returned by the `TxFn` or when it panics.
func WithTransaction(ctx context.Context, db *sqlx.DB, fn TxFn) error {
logger := ctxval.GetLogger(ctx)
tx, err := db.BeginTxx(ctx, &sql.TxOptions{
Isolation: sql.LevelDefault,
ReadOnly: false,
})
if err != nil {
logger.Error().Err(err).Msg("Cannot begin database transaction")
return fmt.Errorf("cannot begin transaction: %w", err)
}
defer func() {
if p := recover(); p != nil {
logger.Trace().Msg("Rolling database transaction back")
err := tx.Rollback()
if err != nil {
logger.Error().Err(err).Msg("Cannot rollback database transaction")
return
}
panic(p)
} else if err != nil {
logger.Trace().Msg("Rolling database transaction back")
err := tx.Rollback()
if err != nil {
logger.Error().Err(err).Msg("Cannot rollback database transaction")
return
}
} else {
logger.Trace().Msg("Committing database transaction")
err = tx.Commit()
if err != nil {
logger.Error().Err(err).Msg("Cannot rollback database transaction")
return
}
}
}()
logger.Trace().Msg("Starting database transaction")
err = fn(tx)
return err
}
Then you need a DAO interface, we have an initializer as a function that is then initialized by individual implementations, it takes context and transaction and returns DAO implementation:
var GetAccountDao func(ctx context.Context, tx Transaction) (AccountDao, error)
type AccountDao interface {
GetById(ctx context.Context, id uint64) (*models.Account, error)
GetByAccountNumber(ctx context.Context, number string) (*models.Account, error)
GetByOrgId(ctx context.Context, orgId string) (*models.Account, error)
List(ctx context.Context, limit, offset uint64) ([]*models.Account, error)
}
Implementation looks something like this:
const (
getAccountById = `SELECT * FROM accounts WHERE id = $1 LIMIT 1`
)
type accountDaoSqlx struct {
name string
getById *sqlx.Stmt
}
func getAccountDao(ctx context.Context, tx dao.Transaction) (dao.AccountDao, error) {
var err error
daoImpl := accountDaoSqlx{}
daoImpl.name = "account"
daoImpl.getById, err = tx.PreparexContext(ctx, getAccountById)
if err != nil {
return nil, NewPrepareStatementError(ctx, &daoImpl, getAccountById, err)
}
return &daoImpl, nil
}
func init() {
dao.GetAccountDao = getAccountDao
}
func (dao *accountDaoSqlx) GetById(ctx context.Context, id uint64) (*models.Account, error) {
query := getAccountById
stmt := dao.getById
result := &models.Account{}
err := stmt.GetContext(ctx, result, id)
if err != nil {
return nil, NewGetError(ctx, dao, query, err)
}
return result, nil
}
// etc
And example use in a "service" package (without and with transaction):
// import sqlx implementation which calls init()
_ "internal/dao/sqlx"
"internal/dao"
// etc
func CreatePubkey(w http.ResponseWriter, r *http.Request) {
payload := &payloads.PubkeyRequest{}
if err := render.Bind(r, payload); err != nil {
renderError(w, r, payloads.NewInvalidRequestError(r.Context(), err))
return
}
// example with transaction
err := dao.WithTransaction(r.Context(), db.DB, func(tx dao.Transaction) error {
pkDao, err := dao.GetPubkeyDao(r.Context(), tx)
if err != nil {
return payloads.NewInitializeDAOError(r.Context(), "pubkey DAO", err)
}
err = pkDao.Create(r.Context(), payload.Pubkey)
if err != nil {
return payloads.NewDAOError(r.Context(), "create pubkey", err)
}
// ... cut ...
return nil
})
// ignore errors etc
}
func ListPubkeys(w http.ResponseWriter, r *http.Request) {
// example without transaction
pubkeyDao, err := dao.GetPubkeyDao(r.Context(), db.DB)
if err != nil {
renderError(w, r, payloads.NewInitializeDAOError(r.Context(), "pubkey DAO", err))
return
}
pubkeys, err := pubkeyDao.List(r.Context(), 100, 0)
if err != nil {
renderError(w, r, payloads.NewDAOError(r.Context(), "list pubkeys", err))
return
}
if err := render.RenderList(w, r, payloads.NewPubkeyListResponse(pubkeys)); err != nil {
renderError(w, r, payloads.NewRenderError(r.Context(), "list pubkeys", err))
return
}
}
This is incomplete code I did cut details. Note I have both these interfaces in our dao package, these might be either separate or the same package.
One important thing to know: you must not share DAO implementation just like that (as in your snippet). The standard library sql.DB interface is actually a connection pool and it is thread safe (as documented), you can call as many Query/Exec/Etc function as you want, the driver will use one or more connection as needed.
Therefore the correct approach is to create new DAO type everytime a new request comes in, the connection pool will automatically acquire existing or new connection. More about this in the Go documentation.
you can modify the package dao to look like the following, which will make it thread-safe as func NewDao will be called in handler. you make this work you need to pass db *sql.DB from package to handler.
type Dao interface {
TxEnd(err error)
}
type DaoImpl struct {
tx *sql.Tx
db *sql.DB
txStared bool
}
func NewDao(db *sql.DB, txStared bool) Dao {
if txStared {
tx, _ := db.Begin()
return &DaoImpl{tx: tx, db: db, txStared: txStared}
}
return &DaoImpl{tx: nil, db: db, txStared: txStared}
}
func (dao *DaoImpl) TxEnd(err error) {
if !dao.txStared {
return
}
if p:= recover(); p != nil {
dao.tx.Rollback()
panic(p)
} else if err != nil {
dao.tx.Rollback()
} else {
dao.tx.Commit()
}
}
}
This is my API Handler for handling the request, I am trying to add if statement which checks that if it contain header with a token if not then reject, basically I have to read header here.
func getLeads(w http.ResponseWriter, r *http.Request) {
//if the request doesn't contain header then reject
if r.Header.Get("Content-Type") = nil {
fmt.Println("Request Rejected")
return
}
w.Header().Set("Content-Type", "application/json")
var leads []Lead
db, err := getDB()
if err != nil {
log.Println(err)
}
db.Find(&leads)
// todo: error handling
json.NewEncoder(w).Encode(leads)
}
Header.Get cannot return nil. Per the docs:
Get gets the first value associated with the given key. If there are no values associated with the key, Get returns "".
You should be checking against "", not nil.
I am currently building a web application in golang (with Gorilla) and have implemented a handful of API endpoints. However, I noticed that every time I implement a function like
func CreateUserHandler(w http.ResponseWriter, r *http.Request) {}
I have to add the function below to the body of handler functions to check if request is authorized:
func checkAuthorizedUser (r * http.Request) error {
uid, err := CheckRequestUser (r.Cookie("uid"))
if err != nil {
return errors.New("Can't find cookie value for uid")
}
if !IsValidUser (uid.Value) {
return errors.New("Not a valid user")
}
return nil
}
What happens to me right now is that I have to add checkAuthorizedUser() to every handler function, and I have already have a lot of handler functions so far. I wonder if there is a better way to check whether a client is authorized to access certain endpoint other than explicitly checking authentication in every handler function.
Gorilla has a router you can use. You can then wrap the router with authentication checking. Something like this would work:
func checkPermissions(h http.Handler) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
authCheck := true //implement the actual checking
if authCheck {
w.WriteError(w, 400, "error")
return
}
h.ServeHttp(w, r)
}
}
func main() {
r := mux.NewRouter()
r.HandleFunc("/", HomeHandler)
r.HandleFunc("/products", ProductsHandler)
r.HandleFunc("/articles", ArticlesHandler)
http.Handle("/", checkPermissions(r))
}
Supporting links:
https://godoc.org/github.com/gorilla/mux#NewRouter
https://github.com/gorilla/mux