Creating a counter with cookies - go

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.

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

Function handler GoLang using Standard Library

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)
}

Ensure a URI is valid

I'm trying to ensure that URLs passed to my go program are valid. However, I can't seem to work out how to. I thought I could just feed it through url.Parse, but that doesn't seem to do the job.
package main
import (
"fmt"
"net/url"
)
func main() {
url, err := url.Parse("http:::/not.valid/a//a??a?b=&&c#hi")
if err != nil {
panic(err)
}
fmt.Println("It's valid!", url.String())
}
playground
Is there anything along the lines of filter_var I can use?
You can check that your URL has a Scheme, Host, and/or a Path.
If you inspect the URL returned, you can see that the invalid part is inserted into the Opaque data section (so in a sense, it is valid).
url.URL{Scheme:"http", Opaque:"::/not.valid/a//a", Host:"", Path:"", RawQuery:"?a?b=&&c", Fragment:"hi"}
If you parse a URL and don't have a Scheme, Host and Path you can probably assume it's not valid. (though a host without a path is often OK, since it implies /, so you need to check for that)
u, err := url.Parse("http:::/not.valid/a//a??a?b=&&c#hi")
if err != nil {
log.Fatal(err)
}
if u.Scheme == "" || u.Host == "" || u.Path == "" {
log.Fatal("invalid URL")
}
have a try of this package go validator and the IsURL func is what you want.(you can use regexp package as well)
package main
import (
"fmt"
"regexp"
)
const URL string = `^((ftp|http|https):\/\/)?(\S+(:\S*)?#)?((([1-9]\d?|1\d\d|2[01]\d|22[0-3])(\.(1?\d{1,2}|2[0-4]\d|25[0-5])){2}(?:\.([0-9]\d?|1\d\d|2[0-4]\d|25[0-4]))|(((([a-z\x{00a1}-\x{ffff}0-9]+-?-?_?)*[a-z\x{00a1}-\x{ffff}0-9]+)\.)?)?(([a-z\x{00a1}-\x{ffff}0-9]+-?-?_?)*[a-z\x{00a1}-\x{ffff}0-9]+)(?:\.([a-z\x{00a1}-\x{ffff}]{2,}))?)|localhost)(:(\d{1,5}))?((\/|\?|#)[^\s]*)?$`
func Matches(str, pattern string) bool {
match, _ := regexp.MatchString(pattern, str)
return match
}
func main() {
u1 := "http:::/not.valid/a//a??a?b=&&c#hi"
u2 := "http://golang.fastrl.com"
func(us ...string) {
for _, u := range us {
fmt.Println(Matches(u, URL))
}
}(u1, u2)
}
The url.Parse() function will return an error mainly if viaRequest is true, meaning if the URL is assumed to have arrived via an HTTP request.
Which is not the case when you call url.Parse() directly: see the source code.
if url, err = parse(u, false); err != nil {
return nil, err
}
And func parse(rawurl string, viaRequest bool) (url *URL, err error) { only returns err if viaRequest is true.
That is why you never see any error when using url.Parse() directly.
In that latter case, where no err is ever returned, you can check the fields of the URL object returned.
An empty url.Scheme, or an url.Path which isn't the one expected would indicate an error.

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