Form submitted twice with one click

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>

On the server this is the request received:

/generalLedger?userID=admin&hashedPassword=%C2%A3%C2%A2%C2%A1&addUser=Add+User
userID=admin
hashedPassword=%C2%A3%C2%A2%C2%A1
addUser=Add+User

This request shows up in the log twice

If I had to guess, you have some JavaScript or something that is submitting the form and not preventing default on the form submit.

Thanks for your response. But I have no javascript and no external js files. Something is submitting but I sure canā€™t find it.

Can you change it to a POST? You probably shouldnā€™t be using a GET for this anyway. From w3 schools:

  • Never use GET to send sensitive data! (will be visible in the URL)
  • Useful for form submissions where a user wants to bookmark the result
  • GET is better for non-secure data, like query strings in Google

ā€¦ and from MDN:

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.

1 Like

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?

1 Like

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.

  1. There are functions in url for decoding, such as PathUnescape and QueryUnescape.
  2. 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:

func queryUnescape(str string)string{
	// example: %C2%A3%C2%A2%C2%A1
	unescaped := ""
	if strings.Contains(str, "%"){
		splits := strings.Split(str, "%")
		for _, code := range splits{
			if len(code) ==2 {
				unescapedCharacter, _ := url.QueryUnescape("%" + code)
				unescaped = unescaped + unescapedCharacter
			} else {
				unescaped = unescaped + code
			}
		}
	}else {
		return str
	}
	return unescaped
}

Working on this one now: :radioactive: 香馘锶邇
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:

func queryUnescape(str string)string{
	// example: %C2%A3%C2%A2%C2%A1
	unescaped := ""
	if strings.Contains(str, "%"){

		splits := strings.Split(str, "%")
		for _, code := range splits{
			if len(code) ==2 {
				unescapedCharacter, err := url.QueryUnescape("%" + code)
				if err != nil {
					unescaped = unescaped + code
				}else {
					unescaped = unescaped + unescapedCharacter
				}

			} else {
				unescaped = unescaped + code
			}
		}
	}else {
		return str
	}
	return unescaped
}

Strange, I thought I would get an error.

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.

1 Like

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?

Iā€™m not familiar with router/muxer. But here are my handlers:

	http.HandleFunc("/generalLedger/login", loginHandler)
	http.HandleFunc("/generalLedger", generalLedgerHandler)
	http.HandleFunc("/generalLedger/segments", segmentsHandler)

then, from the http package:

func (mux *ServeMux) HandleFunc

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:

image/webp,image/png,image/svg+xml,image/*;q=0.8,video/*;q=0.8,*/*;q=0.5

After some web searching, I added this:

	http.HandleFunc("/favicon.ico", faviconHandler)
func faviconHandler(w http.ResponseWriter, r *http.Request) {
	http.ServeFile(w, r, "./images/favicon.ico")
}

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.