I am having an issue with a form submitting twice on a single click. I have removed all of the other code from the page. There is no javascript. There are no styles set. I have many other pages with the same input and they work fine. I have a log file which shows that the handler was called twice in the same second. I realize that many devs have to deal with the issue where the user clicks the submit button twice. I even tried implementing the suggested javascript to disable the button on click. Didnāt help. I am definitely not clicking twice. I have recreated the issue many times.
This is a user management form with an add user button. Every time I click it it adds two users and, yes, the handler in my Go code gets called twice.
Here is the stripped down page:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Users</title>
</head>
<body>
<form action="/generalLedger" method="get">
<input name = "userID" value = "admin" hidden>
<input name = "hashedPassword" value = "Ā£Ā¢Ā”" hidden>
<input class="input-button" name="addUser" type="submit" value="Add User" id="addUser_btn">
</form>
</body>
</html>
The GET method is the method used by the browser to ask the server to send back a given resource: āHey server, I want to get this resource.ā
ā¦
The POST method is a little different. Itās the method the browser uses to talk to the server when asking for a response that takes into account the data provided in the body of the HTTP request: āHey server, take a look at this data and send me back an appropriate result.ā
Iām wondering if for some reason your browser is refreshing and since itās a GET that is submitting the form again. Regardless, this should almost certainly be a POST.
Thanks so much. That does seem to fix the issue. I am replacing all of my āgetā's with āpostā. After updating to post, Iām getting some errors processing the request body, so Iām working on that now. Iām relatively new to HTML as Iāve always been an application developer, never did website dev.
When I get more info, Iāll update. Thanks again.
In cases like this it is helpful to view the browserās developer tools, particularly the network section. You can see the requests the browser actually makes, or doesnāt make due to caching, and the status codes it receives. Look for redirects, retries, etc. Also, proxies, reverse proxies, CDNs, etc can intervene between the browser and your service code.
This situation also points out the importance of idempotency of your service. You may erroneously receive the same request multiple times. What is Idempotency?
Yes, I use the Safari web inspector quite a bit, but havenāt used the Network tab before. Looks like the Headers section has a lot of useful info. Iām running into two issues. The first is that if I have an input tag string containing an apostrophe, it turns into %27 when received in the handler on my server. How do I convert that back into an apostrophe? Second, the hashed password which I persist between pages is āĀ£Ā¢Ā”ā. This arrives on the browser correctly, but on the next submit, it arrives at the server as:
%C2%A3%C2%A2%C2%A1
Seems strange as the original is 3 characters and the received seems to be 6 characters as there are 6 %'s in it. Then, as you can probably guess, it grows on every submit/receive. On the second iteration it is:
%25C2%25A3%25C2%25A2%25C2%25A1
Obviously I have an encoding/decoding issue.
There are functions in url for decoding, such as PathUnescape and QueryUnescape.
Try base64 encoding to avoid undesirable characters that will get escaped.
I donāt have a guess as to why it differs for subsequent requests, but if you have javascript code submit requests to your service, you get more control over escaping, but then the onus is on your code to make sure the url contains the correct escaping.
Glad that got you further down the road. You are correct that you have an encoding issue. Check this article out. How are you retrieving the value on the server? Using request.FormValue? And how are you having these fields āarriveā on the browser? Check this tutorial out and I bet if you follow it it would solve your problem. This might also be of use.
Thanks for that. It looks like url.QueryUnescape() will do the job. Iām writing my function queryUnescape to process the returned input fields. Hereās what I have so far. It works great on the password and Iām now working through handling the various cases from other inputs:
Working on this one now: é¦é¦é”¶é
Comes from the browser as: %E2%98%A2 %E9%A6%99%E9%A6%98%E9%A1%B6%E9%82%87
Iām ending up with this:
ļæ½ļæ½A2 é¦é¦é”¶é
after updating the function to:
Thanks again. Iāll check out the articles. I am not using request.FormValue. I am using the ioutil.ReadAll(request.Body)
This is working in most cases, but sometimes has more data than I want.
func setDataBodyMapForRequest(request *http.Request, p *generalLedgerPage) error {
var dataBody string
switch request.Method {
case "POST":
body, _ := ioutil.ReadAll(request.Body)
dataBody = string(body)
break
case "GET":
dataBody = request.URL.RawQuery
break
default:
return errors.New(fmt.Sprintf("bad html request method: ", request.Method))
}
p.DataBodyMap = make(map[string]string, 0)
lines := strings.Split(dataBody, "&")
for _, line := range lines {
keyValues := strings.Split(line, "=")
if len(keyValues) < 2 {
log.Println("invalid post body: ", dataBody)
return errors.New(fmt.Sprintf("invalid post body: ", dataBody))
}
p.DataBodyMap[keyValues[0]] = strings.ReplaceAll(keyValues[1], "+", " ")
log.Println("keyValues: ", keyValues)
fmt.Println("keyValues: ", keyValues)
}
return nil
}
I just got yelled at by the forum for making too many posts, but I thought this thread would be useful to others. Iāll be quiet now until I get this stuff fully resolved.
This is due to how you are breaking the string on %'s and deciding whether or not the intervening code should be unescaped. url.QueryUnescape should do all that for you.
Oh. You are for sure going to want to change your approach! Your current method is far more manual than it needs to be. You only care about the data in the form, right? In case "POST":, try calling request.ParseForm() then take a look at request.Form (for easy debugging purposes you could fmt.Println(request.Form)). You could also use request.FormValue("hashedPassword") which calls ParseForm automatically. If you use the stdlib like this your values will be decoded properly and you can get rid of your queryUnescape function.
Ah, yes, anything to make the code simpler will help. I will look into that. Do you know why clearing the cache in Safari (Develop/Empty caches) sends a get request from the browser and does not include the input fields anywhere? Then, what is really weird is my server app considers this a credentials issue and wants to bring up the login form, called ālogin.htmlā using templates.ExecuteTemplate. However the same form stays up in the browser, not the login form. The browser never received the new page, as evidenced by the web inspector still containing the credentials and they were not supplied by the server.
Hmm, I tried calling request.ParseForm() followed by fmt.Println(request.Form)but got an empty map, whereas thebody, _ := ioutil.ReadAll(request.Body)` does return the desired data, albeit not unescaped.
I wonder if there was an error. Try capturing errors:
err := request.ParseForm()
if err != nil {
fmt.Println("Problem while parsing form:", err.Error())
}
If I had to guess, maybe you kept your call to ioutil.ReadAll and it is happening before you call request.ParseForm, which is advancing the reader (request.Body) to EOF. So when ParseForm is called it is already at EOF. But Iām just guessing here. Capture the error and see what it says (if anything).
You are right again! I was calling the request.ParseForm() after ioutil.ReadAll itās so easy now since it returns a map. No more unescape. However, I am back to my initial issue with the add user button triggering two calls to the server, the first one is correct and is a āpostā, but then it issues a second call as a āgetā. Similar to the āgetā submitted on clear caches. No credentials. I have changed all of my 'getās to putās. I guess my next approach is to peruse the web inspector to see if I can figure out whatās happening.
I have another HTML Golang template issue, but Iāll start another thread for that one. Itās not related to this.
Open dev tools and watch network and try to see whatās going on. Iām wondering if maybe those GETs are to something that the browser will automatically try to get (like favicon.ico; browser will look for that without you telling it to) and since you have a global handler, all requests are being handled by that handler. Speaking of which what does your route setup code look like? What router/muxer are you using?
It looks like you are right about the favicon
There are two generalLedger items on the left pane, the first one has the html code and the 2nd one shows a question mark icon.
I guess I can just ignore it. I canāt find anywhere in the request that indicates the favicon, although there is an accept value which doesnāt seem to include .ico filetype:
but it doesnāt get called. The URL path in the request is: /generalLedger
which calls my main handler. Maybe itās asking for something else, but I donāt know what.