go: net/http: http client regression building with js/wasm and running on Chrome: `net::ERR_H2_OR_QUIC_REQUIRED`

What version of Go are you using (go version)?

$ go version
go version go1.21.0 linux/amd64

Does this issue reproduce with the latest release?

Yes

What operating system and processor architecture are you using (go env)?

go env Output
$ go env
GO111MODULE=''
GOARCH='wasm'
GOBIN=''
GOCACHE='/home/h/.cache/go-build'
GOENV='/home/h/.config/go/env'
GOEXE=''
GOEXPERIMENT=''
GOFLAGS=''
GOHOSTARCH='amd64'
GOHOSTOS='linux'
GOINSECURE=''
GOMODCACHE='/home/h/go/pkg/mod'
GONOPROXY=''
GONOSUMDB=''
GOOS='js'
GOPATH='/home/h/go'
GOPRIVATE=''
GOPROXY='https://proxy.golang.org,direct'
GOROOT='/usr/lib/go'
GOSUMDB='sum.golang.org'
GOTMPDIR=''
GOTOOLCHAIN='auto'
GOTOOLDIR='/usr/lib/go/pkg/tool/linux_amd64'
GOVCS=''
GOVERSION='go1.21.0'
GCCGO='gccgo'
GOWASM=''
AR='ar'
CC='gcc'
CXX='g++'
CGO_ENABLED='0'
GOMOD='/home/h/code/pk/axesfull/go.mod'
GOWORK=''
CGO_CFLAGS='-O2 -g'
CGO_CPPFLAGS=''
CGO_CXXFLAGS='-O2 -g'
CGO_FFLAGS='-O2 -g'
CGO_LDFLAGS='-O2 -g'
PKG_CONFIG='pkg-config'
GOGCCFLAGS='-fPIC -Wl,--no-gc-sections -fmessage-length=0 -ffile-prefix-map=/tmp/go-build4139731908=/tmp/go-build -gno-record-gcc-switches'
uname -sr: Linux 6.4.8-arch1-1
LSB Version:	n/a
Distributor ID:	Arch
Description:	Arch Linux
Release:	rolling
Codename:	n/a
/usr/lib/libc.so.6: GNU C Library (GNU libc) stable release version 2.38.

What did you do?

NOTE: I only get these errors in Chromium based browsers, not in Firefox.

I have this following function:

type Account struct {
	ID string `json:"id"`
}

func TestPersist(t *testing.T) {
	fmt.Printf("runtime.Version(): %v\n", runtime.Version())
	acc := Account{"5e7fd575-b5f5-4d7e-8fa7-70b5fef7662f"}
	buf, _ := json.Marshal(acc)
	r := bytes.NewBuffer(buf)
	req, err := http.NewRequest("POST", "https://nk.axesfull.dev/v2/account/authenticate/device", r)
	if err != nil {
		t.Fatalf("could not create request: %v", err)
	}
	_, err = http.DefaultClient.Do(req)
	if err != nil {
		t.Fatalf("could not authenticate account: %v", err)
	}
}

compiled with: GOOS=js GOARCH=wasm go test -c -o test.wasm -run TestPersist

I ran this with the default wasm_exec.html thats provided in $GOROOT/misc/wasm with just adding test.v to argv:.

		async function run() {
			console.clear();
			await go.run(inst);
			go.arvc = ["js", "-test.v"]
			inst = await WebAssembly.instantiate(mod, go.importObject); // reset instance
		}

This works with go1.20, but not with go1.21.

Reproducible example available at https://github.com/jnowls/wasm-http-chrome - just run ./run.sh

What did you expect to see?

The browser successfully complete the request (as seen in Firefox and older versions of chrome)

What did you see instead?

Firefox go1.21 and go1.20: image image

Chrome go1.21 and go1.20: image image

About this issue

  • Original URL
  • State: closed
  • Created a year ago
  • Comments: 29 (17 by maintainers)

Commits related to this issue

Most upvoted comments

If I understand correctly, https://go.dev/cl/458395 which added support for streaming HTTP request bodies in wasm bulids, results requests failing when:

  1. The request has a body.
  2. The program is being executed by Chrome.
  3. The server handling the request supports HTTP1, but neither HTTP/2 nor HTTP/3.

Also, it sounds like there is no workaround: Requests always fail when the above conditions are met.

Is that all correct?

If so, I agree that we should roll this change back. We should also backport the rollback to the next 1.21 minor release, as this is both a significant regression (you can’t send requests at all under these circumstances) and there is no workaround.

I see nobody disagree about revert, and submitted CL of revert! https://go-review.googlesource.com/c/go/+/522955

I agree that it should either be turned off entirely or opt-in, so that it always works as expected by default. The change that introduced this was https://go-review.googlesource.com/c/go/+/458395, so the first step would be to revert this (so that it’s working in tip) and then consider reintroducing it with an opt-in.

Thanks for the offer of contributing the fix @haruyama480 😃

I don’t see any quick and easy way to determine whether or not the browser will complete a HTTP request with H2 or H1 before it happens 😦. Just disabling request streaming for HTTP also probably isn’t a proper fix since POST requests with HTTPS+H1 (what OP describes) also fail.

If we do end up going with implementing it such that clients select whether to use the streaming API, that should probably not be done with a global variable, it will only make things more complicated to deal with, though I’m not sure what the best alternative to give the client the control is.

I personally think request streaming should be reverted for now, and revisited at a later point where the streaming API provides proper mechanisms to fallback in case of no support from the server.

I don’t think adding explicit type assertions into the code is the way to go. The problem with that is that it becomes impossible for users to wrap the transport transparently. I’d sooner see we allow the user to be more explicit about opting in. We already have a pattern for configuring the HTTP request via headers (See https://go.googlesource.com/go/+/refs/heads/master/src/net/http/roundtrip_js.go#26 and friends). We could probably just add another header that would opt into the behavior?

That is perfectly okay with me! I was just excited that I found a technical way to do what we wanted!

I don’t think adding explicit type assertions into the code is the way to go. The problem with that is that it becomes impossible for users to wrap the transport transparently. I’d sooner see we allow the user to be more explicit about opting in. We already have a pattern for configuring the HTTP request via headers (See https://go.googlesource.com/go/+/refs/heads/master/src/net/http/roundtrip_js.go#26 and friends). We could probably just add another header that would opt into the behavior?

CC @hawkinsw. Please feel free to open a CL with a fix, sorry for the inconvenience!

@jnowls. Correct. Sorry, should have worded it better.

Just to clarify, based on that article and my own testing, it’s not an issue with HTTP or HTTPS, but more with HTTP 1, regardless of whether SSL/TLS is there or not.