ginuerzh/gost · error

method not allowed

Error message

method not allowed

What it means

h2Listener.upgrade rejects an incoming HTTP/2 request whose method is not CONNECT when the listener was created without a fixed path (l.path == ""). In pathless mode the listener only accepts HTTP/2 CONNECT-style tunneling requests; anything else is answered with 405 Method Not Allowed and this error.

Source

Thrown at http2.go:787

	if err != nil {
		log.Logf("[http2] %s - %s %s %s %s: %s",
			r.RemoteAddr, r.Host, r.Method, r.RequestURI, r.Proto, err)
		return
	}
	select {
	case l.connChan <- conn:
	default:
		conn.Close()
		log.Logf("[http2] %s - %s: connection queue is full", conn.RemoteAddr(), conn.LocalAddr())
	}

	<-conn.closed // NOTE: we need to wait for streaming end, or the connection will be closed
}

func (l *h2Listener) upgrade(w http.ResponseWriter, r *http.Request) (*http2Conn, error) {
	if l.path == "" && r.Method != http.MethodConnect {
		w.WriteHeader(http.StatusMethodNotAllowed)
		return nil, errors.New("method not allowed")
	}

	if l.path != "" && r.RequestURI != l.path {
		w.WriteHeader(http.StatusBadRequest)
		return nil, errors.New("bad request")
	}

	w.WriteHeader(http.StatusOK)
	if fw, ok := w.(http.Flusher); ok {
		fw.Flush() // write header to client
	}

	remoteAddr, _ := net.ResolveTCPAddr("tcp", r.RemoteAddr)
	if remoteAddr == nil {
		remoteAddr = &net.TCPAddr{
			IP:   net.IPv4zero,
			Port: 0,
		}

View on GitHub (pinned to a33fdbf4c9)

Solutions

  1. Use the library's HTTP/2 client dialer so the request is sent as an HTTP/2 CONNECT, or upgrade via the expected method.
  2. Configure a path on the listener (l.path) if you want to accept ordinary requests on a specific URI.
  3. Check the server-side listener options: if path is empty, only CONNECT is valid — fix the client to match.
  4. Verify any reverse proxy in front preserves the CONNECT method and does not rewrite it to GET.

Example fix

// before (client sends plain GET)
resp, _ := http.Get("https://host/tunnel")
// after (client performs HTTP/2 CONNECT via library dialer)
conn, _ := (&gost.http2.Client{Host: "host:443"}).Dial(ctx, "tcp", "target:80")
Defensive patterns

Strategy: validation

Validate before calling

// client side: only CONNECT-style requests are valid when the listener has no path
if listenerPath == "" && req.Method != http.MethodConnect {
    return fmt.Errorf("pathless h2 listener requires CONNECT, got %s", req.Method)
}

Try / catch

conn, err := dialer.Dial(ctx, network, addr)
if err != nil {
    var oe *net.OpError
    if errors.As(err, &oe) && strings.Contains(err.Error(), "method not allowed") {
        return fmt.Errorf("server requires CONNECT for this endpoint: %w", err)
    }
    return err
}

Prevention

When it happens

Trigger: Dialing an h2Listener configured with no path using a plain GET/POST (not http2 transport CONNECT), e.g. a normal HTTP request from a browser or curl hitting the server's endpoint while l.path == "".

Common situations: Testing the h2 endpoint with curl/browser instead of the library's http2 client; client and server configured with mismatched modes (one expects CONNECT tunneling, the other sends ordinary requests); proxying non-CONNECT traffic to an http2 tunnel endpoint.

Related errors


AI-assisted analysis of ginuerzh/gost@a33fdbf4c9 (2026-09-02). Data as JSON: /api/errors/fadd8d7e3a84d95a. Report an issue: GitHub.