Function handler GoLang using Standard Library - go

I have an endpoint like events/{id} and a handler for it. How can I get {id} without using Gorilla/Mux. What are the GoLang in-built alternatives that can achieve this? Need to do this without gorilla/Mux or other third-party libraries. I know this can be done with mux.Vars but can't use it here.

If you already managed to direct the traffic to your handler, then you can simply parse the URL path yourself:
func HandlerFunc(w http.ResponseWriter, request *http.Request) {
segments := strings.Split(request.URL.Path, "/")
// If path is /events/id, then segments[2] will have the id
}
Request.URL.Path is already URL decoded, so if your parameters may contain slashes use Request.RequestURI and url.PathUnescape instead:
segments := strings.Split(r.RequestURI, "/")
for i := range segments {
var err error
segments[i], err = url.PathUnescape(segments[i])
if err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
}

You can just get the slice of the string starting after /events/:
func eventHandler(w http.ResponseWriter, r *http.Request) {
id := r.URL.Path[len("/events/"):]
w.Write([]byte("The ID is " + id))
}
func main() {
http.HandleFunc("/events/", eventHandler)
}

Related

Probleme with httptest postForm in golang

i receive a response body "bad request" with "httptest.Client().Postform"
type testServer struct {
*httptest.Server
}
func newTestServer(t *testing.T, h http.Handler) *testServer {
ts := httptest.NewTLSServer(h)
jar, err := cookiejar.New(nil)
if err != nil {
t.Fatal(err)
}
ts.Client().Jar = jar
ts.Client().CheckRedirect = func(req *http.Request, via []*http.Request) error {
return http.ErrUseLastResponse
}
return &testServer{ts}
}
func (ts *testServer) postForm(t *testing.T, urlPath string, form url.Values) (int, http.Header, string) {
rs, err := ts.Client().PostForm(ts.URL+urlPath, form)
if err != nil {
t.Fatal(err)
}
defer rs.Body.Close()
body, err := io.ReadAll(rs.Body)
if err != nil {
t.Fatal(err)
}
bytes.TrimSpace(body)
return rs.StatusCode, rs.Header, string(body)
}
I don't know where is the problem, i have also verified the url it's correct.
Always badrequest with POST but with GET request it's works fine.
this is the handler object :
func (app *application) routes() http.Handler {
router := httprouter.New()
router.NotFound = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
app.notFound(w)
})
dynamic := alice.New(app.sessionManager.LoadAndSave, noSurf, app.Authenticated)
router.Handler(http.MethodGet, "/", dynamic.ThenFunc(app.home))
router.Handler(http.MethodGet, "/user/signup", dynamic.ThenFunc(app.userSignup))
router.Handler(http.MethodPost, "/user/signup", dynamic.ThenFunc(app.userSignupPost))
standart := alice.New(app.recoverPanic, app.logRequest, securityHeaders)
return standart.Then(router)
}
the test function :https://go.dev/play/p/k45-JYTYCOS
the app.userSignupPost:
func (app *application) userSignupPost(w http.ResponseWriter, r *http.Request) {
var form userSignupForm
err := app.decodPostForm(r, &form)
if err != nil {
app.clientError(w, http.StatusBadRequest)
return
}
form.CheckField(validator.NotBlank(form.Name), "name", "this field must not be blank")
form.CheckField(validator.NotBlank(form.Email), "email", "this field must not be blank")
form.CheckField(validator.Matches(form.Email, validator.EmailRX), "email", "this field must be a valid email address")
form.CheckField(validator.NotBlank(form.Password), "password", "this field must not be blank")
form.CheckField(validator.MinChars(form.Password, 8), "password", "password must bee at least 8 caracter long")
if !form.Valid() {
data := app.newTemplateData(r)
data.Form = form
app.render(w, http.StatusUnprocessableEntity, "signup.tmpl.html", data)
return
}
err = app.users.Insert(form.Name, form.Email, form.Password)
if err != nil {
if errors.Is(err, models.ErrDuplicateEmail) {
form.AddFieldError("email", "Email already exist")
data := app.newTemplateData(r)
data.Form = form
app.render(w, http.StatusUnprocessableEntity, "signup.tmpl.html", data)
} else {
fmt.Println("error user postform")
app.serverError(w, err)
}
return
}
app.sessionManager.Put(r.Context(), "flash", "Signup Successful. Please log in")
http.Redirect(w, r, "/user/login", http.StatusSeeOther)
}
It appears that you're using https://github.com/justinas/alice to register handlers - you don't want to do this. That package is for middleware chaining - e.g. "before all requests to this URL, first authenticate the request" - you'd put the authentication into a middleware function and then add it to the chain.
So every POST /user/signup request is getting passed first to app.userSignup() (what you are using to handle GET requests). This is because calling alice.ThenFunc() appends the passed handler to the chain and then returns the entire chain as a handler - you need to read this part of the Alice docs carefully before using it.
Replace this line:
router.Handler(http.MethodPost, "/user/signup", dynamic.ThenFunc(app.userSignupPost))
with
router.Handler(http.MethodPost, "/user/signup", http.HandlerFunc(app.userSignupPost))
You may not need the additional decoration of http.HandlerFunc() - try it with and without to see what works. I cannot say for sure without knowing what the body of app.userSignupPost() looks like (same for the other handler functions as well).
You'll then need to do the same for the other handler registration lines - you shouldn't be using middleware chaining for your end handlers. An http.Handler is used for saying, "send any request to path /PP/ppp with method XXXX to this function." Middleware chaining is for preprocessing (authentication, authorization, etc.) - a whole host of things can be done there, but end request handling shouldn't be one of them.
I'm still curious if your use of PostForm() is going to cause you issues for the reason I cited in my comment on your question - try a raw Post() and see if the behavior differs, but after refactoring to take out the alice goop (at least temporarily). When testing a handler, I'd start off with a much more minimal approach - test that the handler itself works before muddying the waters with both alice and what looks like this package.
I think i found the problem , the session cookie are not the same for get and post request. i don't know why it has changed.They use the same http.Client()

Net/http Simple Dynamic Routes

I am looking for a simple way to create dynamic routes with net/http (no routers like mux etc.)
Here is my current code:
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
pages := r.URL.Query()["q"]
if len(pages) == 0 {
fmt.Fprintf(w, "§§§§§§§§§§ You need to specify a page §§§§§§§§§§")
return
}
page := pages[0]
var a Page
err := db.QueryRow("SELECT * FROM pages where page = ?", page).Scan(&a.Page, &a.Date, &a.Url)
a.Year = time.Now().UTC().Year()
if err != nil {
if err == sql.ErrNoRows {
fmt.Fprintf(w, "Page %s not found", page)
return
} else {
fmt.Fprintf(w, "Some error happened")
return
}
}
http.Redirect(w, r, a.Url, 301)
})
So now the URL sample.com/?q= works dynamically.
My objective is to work without having to use r.URL.Query()["q"] so directly /pagename
This is not a duplicate of Go url parameters mapping because it is a single level (not nested levels) AND many answers in that question refer to using an external library.
If you don't want to use any third-party libraries, you have to handle the parsing of the path yourself.
For start, you can do this:
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
page := r.URL.Path[1:]
// do whatever logic you want
// mind that the page could be "multi/level/path/" as well
})
You can use http.HandleFunc.
In this function, a pattern ending in a slash defines a subtree.
You can register a handler function with the pattern "/page/" like the below example.
package main
import (
"net/http"
"fmt"
)
func handler(w http.ResponseWriter, r *http.Request) {
if is_valid_page(r.URL) {
fmt.Fprint(w, "This is a valid page")
} else {
w.WriteHeader(http.StatusNotFound)
fmt.Fprint(w, "Error 404 - Page not found")
}
}
func is_valid_page(page string) {
// check here if page is valid from url
}
func main() {
http.HandleFunc("/page/", handler)
http.ListenAndServe(":8080", nil)
}
more info you can find here: https://golang.org/pkg/net/http/#ServeMux

Creating a counter with cookies

I have used the below code to create a counter using cookies. But I guess there is an issue with this http.HandleFunc("/", foo) function. Ideally the counter should be incremented only when the request is http:localhost:8080 or http:localhost:8080/.
But the count is getting incremented even if I type some random text after "/" (ex: http:localhost:8080/abcd).
func main() {
http.HandleFunc("/", foo)
http.Handle("/favicon.ico", http.NotFoundHandler())
http.ListenAndServe(":8080", nil)
}
func foo(res http.ResponseWriter, req *http.Request) {
cookie, err := req.Cookie("my-cookie-counter")
if err == http.ErrNoCookie {
cookie = &http.Cookie{
Name: "my-cookie-counter",
Value: "0",
}
}
count, err := strconv.Atoi(cookie.Value)
if err != nil {
log.Fatalln(err)
}
count++
cookie.Value = strconv.Itoa(count)
http.SetCookie(res, cookie)
io.WriteString(res, cookie.Value)
}
This is the documented behavior of the / path when handled by the standard library's ServeMux, as you are doing.
Your options are:
Use a different router, which does exact matching.
In your handler, check for the expected path.
http.HandleFunc("/", foo)
This works as a prefix matcher. It will match root and everything below. One way to make it match only the root is check r.URL.Path in your handler and not do work if the path is not the root.

Go lang Redis PubSub in different go routes for publish and subscribe

Am new to go programming language, and I have a requirement to create Redis PubSub with websocket.
My reference code is here
https://github.com/vortec/orchestrate
Am using following libraries
"golang.org/x/net/websocket"
"github.com/garyburd/redigo/redis"
Everything is working for me like this way, but I don't understand what is "websocket.Handler(handleWSConnection)" here.
I need 2 different go routes for /ws-subscribe and /ws-publish. I don't know anything wrong in this concept?
Doubts
Can I do this way, http.HandleFunc("/ws", handleWSConnection) // Tried this way but am getting "not enough arguments in call to handleWSConnection"
Is there any way to call "handleWSConnection()" as a normal function.
Any suggestions how to write /ws-publish as a different go route
Following is my code
main function
func (wsc *WSConnection) ReadWebSocket() {
for {
var json_data []byte
var message WSMessage
// Receive data from WebSocket
err := websocket.Message.Receive(wsc.socket, &json_data)
if err != nil {
return
}
// Parse JSON data
err = json.Unmarshal(json_data, &message)
if err != nil {
return
}
switch message.Action {
case "SUBSCRIBE":
wsc.subscribe.Subscribe(message.Channel)
case "UNSUBSCRIBE":
wsc.subscribe.Unsubscribe(message.Channel)
case "PUBLISH":
wsc.publish.Conn.Do("PUBLISH", message.Channel, message.Data)
}
}
}
func handleWSConnection(socket *websocket.Conn) {
wsc := &WSConnection {socket: socket}
defer wsc.Uninitialize()
wsc.Initialize()
go wsc.ProxyRedisSubscribe()
wsc.ReadWebSocket()
}
func serveWeb() {
http.Handle("/ws", websocket.Handler(handleWSConnection)) // I need to call this route as funciton
if err := http.ListenAndServe(":9000", nil); err != nil {
log.Fatal("ListenAndServe:", err)
}
}
Done following way, I dont know is it the proper way to do this
http.HandleFunc("/publish", publishHandler)
func publishHandler(conn http.ResponseWriter, req *http.Request) {
log.Println("PUBLISH HANDLER")
wsHandler := websocket.Handler(func(ws *websocket.Conn) {
handleWSConnection(ws)
})
wsHandler.ServeHTTP(conn, req)
}

golang return static html file at specified route

I am working on a simple todo app in go.
I have determined that all the pages except a user's list of todos can safely be a static html page.
* Login form
* new account form
* index page that talks about the todo app
I see no reason currently for these to be go templates.
My question is how (within go, not using something like nginx) can I have a static html set to return at a specific route most efficiently?
For example index.html to be returned at "/"
I know I could do something like:
func GetNewAccount(res http.ResponseWriter, req *http.Request) {
body, _ := ioutil.ReadFile("templates/register.html")
fmt.Fprint(res, string(body))
}
or
var register, _ = string(ioutil.ReadFile("templates/register.html"))
func GetNewAccount(res http.ResponseWriter, req *http.Request) {
fmt.Fprint(res, register)
}
To me these seems like more roundabout ways to do something seemingly simple.
If all your static files under the same tree, you could use http.FileServer:
http.Handle("/s/", http.StripPrefix("/s/", http.FileServer(http.Dir("/path/to/static/files/"))))
Otherwise pre-loading the html files you want into a map in func init() then making one handler to fmt.Fprint them based on the request's path should work.
Example of a simple static file handler :
func StaticFilesHandler(path, prefix, suffix string) func(w http.ResponseWriter, req *http.Request) {
files, err := filepath.Glob(filepath.Join(path, "*", suffix))
if err != nil {
panic(err)
}
m := make(map[string][]byte, len(files))
for _, fn := range files {
if data, err := ioutil.ReadFile(fn); err == nil {
fn = strings.TrimPrefix(fn, path)
fn = strings.TrimSuffix(fn, suffix)
m[fn] = data
} else {
panic(err)
}
}
return func(w http.ResponseWriter, req *http.Request) {
path := strings.TrimPrefix(req.URL.Path, prefix)
if data := m[path]; data != nil {
fmt.Fprint(w, data)
} else {
http.NotFound(w, req)
}
}
}
then you can use it like :
http.Handle("/s/", StaticFilesHandler("/path/to/static/files", "/s/", ".html"))
Or just use third party library and do something like this:
iris.StaticServe("/path/to/static/files","/theroute") //gzip compression enabled
The above code snippet is part of the Iris

Resources