@@ -127,6 +127,10 @@ The URL can use either http or https as the scheme. If http is used then HTTP 1.
127127unless the --http2-prior-knowledge flag is set. If https is used then HTTP/2 will be preferred
128128during protocol negotiation and HTTP 1.1 used only if the server does not support HTTP/2.
129129
130+ Server reflection, the gRPC protocol, and bidirectional streaming methods all require HTTP/2. So
131+ if any of them is used with an http URL, HTTP/2 is used as if the --http2-prior-knowledge flag
132+ were set.
133+
130134The default RPC protocol used will be Connect. To use a different protocol (gRPC or gRPC-Web),
131135use the --protocol flag. Note that the gRPC protocol cannot be used with HTTP 1.1.
132136
@@ -152,7 +156,7 @@ Examples:
152156Issue a unary RPC to a plain-text (i.e. "h2c") gRPC server, where the schema for the service is
153157in a Buf module in the current directory, using an empty request message:
154158
155- $ buf curl --schema . --protocol grpc --http2-prior-knowledge \
159+ $ buf curl --schema . --protocol grpc \
156160 http://localhost:20202/foo.bar.v1.FooService/DoSomething
157161
158162Issue an RPC to a Connect server, where the schema comes from the Buf Schema Registry, using
@@ -328,10 +332,13 @@ and port indicated in the URL`,
328332 & f .HTTP2PriorKnowledge ,
329333 http2PriorKnowledgeFlagName ,
330334 false ,
331- `This flag can be used to indicate that HTTP/2 should be used. Without this, HTTP 1.1
335+ fmt . Sprintf ( `This flag can be used to indicate that HTTP/2 should be used. Without this, HTTP 1.1
332336will be used with URLs with an http scheme, and protocol negotiation will be used to
333337choose either HTTP 1.1 or HTTP/2 for URLs with an https scheme. With this flag set,
334- HTTP/2 is always used, even over plain-text.` ,
338+ HTTP/2 is always used, even over plain-text. This flag is implied for URLs with an http
339+ scheme when server reflection is used, when --%s is "grpc", or when the method uses
340+ bidirectional streaming, since all of these require HTTP/2.` ,
341+ protocolFlagName ),
335342 )
336343
337344 flagSet .BoolVar (
@@ -568,8 +575,12 @@ func (f *flags) validate(hasURL, isSecure bool) error {
568575 return fmt .Errorf ("if --%s is set, --%s should not be set as it is unused" , insecureFlagName , caCertFlagName )
569576 }
570577
571- if ! isSecure && ! f .HTTP2PriorKnowledge && f .Protocol == connect .ProtocolGRPC {
572- return fmt .Errorf ("grpc protocol cannot be used with plain-text URLs (http) unless --%s flag is set" , http2PriorKnowledgeFlagName )
578+ if ! isSecure && ! f .HTTP2PriorKnowledge && (f .Reflect || f .Protocol == connect .ProtocolGRPC ) {
579+ // Server reflection uses a bidirectional stream and the gRPC protocol
580+ // requires HTTP/2, neither of which works over HTTP 1.1. Since a
581+ // plain-text URL can only use HTTP/2 via prior knowledge, enable it
582+ // automatically rather than requiring the flag.
583+ f .HTTP2PriorKnowledge = true
573584 }
574585
575586 if ! isSecure && f .HTTP3 {
@@ -601,9 +612,6 @@ func (f *flags) validate(hasURL, isSecure bool) error {
601612 reflectHeaderFlagName , reflectProtocolFlagName , reflectFlagName )
602613 }
603614 if f .Reflect {
604- if ! isSecure && ! f .HTTP2PriorKnowledge {
605- return fmt .Errorf ("--%s cannot be used with plain-text URLs (http) unless --%s flag is set" , reflectFlagName , http2PriorKnowledgeFlagName )
606- }
607615 if _ , err := bufcurl .ParseReflectProtocol (f .ReflectProtocol ); err != nil {
608616 return fmt .Errorf (
609617 "--%s value must be one of %s" ,
@@ -878,6 +886,31 @@ func parseEndpointURL(urlArg string) (service, method, baseURL string, err error
878886 return service , method , baseURL , nil
879887}
880888
889+ // wrapPlainTextHTTP2Error is a best effort to return a more helpful error if err indicates
890+ // that the server answered an HTTP/2 prior knowledge (h2c) connection with an
891+ // HTTP 1.1 response, which means it does not support HTTP/2 over plain-text.
892+ // Otherwise, err is returned unchanged.
893+ //
894+ // Without this, the CLI's error interceptor would render this without details as:
895+ //
896+ // Failure: the server hosted at that remote is unavailable.
897+ func wrapPlainTextHTTP2Error (err error , http2PriorKnowledge bool , host string , isSecure bool ) error {
898+ if err == nil || isSecure || ! http2PriorKnowledge ||
899+ // The stdlib does not expose a structured way of knowing the error is from a
900+ // HTTP/1.1-like response so do a string match, meaning this function is
901+ // best-effort across Go versions.
902+ ! strings .Contains (err .Error (), "frame header looked like an HTTP/1.1 header" ) {
903+ return err
904+ }
905+ // Format with %v rather than %w on purpose: the CLI's error interceptor
906+ // rewrites any error that wraps a connect.CodeUnavailable error into a
907+ // generic message, which would hide this explanation.
908+ return fmt .Errorf (
909+ "the RPC protocol or method requires HTTP/2, but the server at %s responded with HTTP/1.1 and does not appear to support HTTP/2 over plain-text (h2c): %v" ,
910+ host , err ,
911+ )
912+ }
913+
881914func run (ctx context.Context , container appext.Container , f * flags ) (err error ) {
882915 var urlArg , host string
883916 var isSecure bool
@@ -889,6 +922,9 @@ func run(ctx context.Context, container appext.Container, f *flags) (err error)
889922 return err
890923 }
891924 }
925+ defer func () {
926+ err = wrapPlainTextHTTP2Error (err , f .HTTP2PriorKnowledge , host , isSecure )
927+ }()
892928 if err := f .validate (urlArg != "" , isSecure ); err != nil {
893929 return err
894930 }
@@ -1096,6 +1132,11 @@ func run(ctx context.Context, container appext.Container, f *flags) (err error)
10961132 if err != nil {
10971133 return err
10981134 }
1135+ if ! isSecure && methodDescriptor .IsStreamingClient () && methodDescriptor .IsStreamingServer () {
1136+ // Bidirectional streaming requires HTTP/2 regardless of the RPC
1137+ // protocol, so automatically attempt prior knowledge over plain-text.
1138+ f .HTTP2PriorKnowledge = true
1139+ }
10991140 transport , err := makeTransportOnce ()
11001141 if err != nil {
11011142 return err
@@ -1160,6 +1201,9 @@ func makeHTTPRoundTripper(f *flags, isSecure bool, authority string, printer ver
11601201 protocols .SetHTTP1 (! f .HTTP2PriorKnowledge )
11611202 protocols .SetHTTP2 (true )
11621203 protocols .SetUnencryptedHTTP2 (f .HTTP2PriorKnowledge && ! isSecure )
1204+ if protocols .UnencryptedHTTP2 () {
1205+ printer .Printf ("* Using HTTP/2 prior knowledge over plain-text (h2c)" )
1206+ }
11631207 return & http.Transport {
11641208 Proxy : http .ProxyFromEnvironment ,
11651209 DialContext : dialFunc ,
@@ -1475,15 +1519,16 @@ func completePathFromServices(
14751519}
14761520
14771521// makeCompletionHTTPClient builds an HTTP client for use during shell
1478- // completion. Returns (client, true) on success, or (nil, false) when
1479- // reflection is not possible (e.g. plain HTTP without HTTP/2 prior knowledge) .
1522+ // completion. Returns (client, true) on success, or (nil, false) when the
1523+ // client could not be built .
14801524func makeCompletionHTTPClient (cmd * cobra.Command , isSecure bool , authority string ) (connect.HTTPClient , bool ) {
14811525 insecure , _ := cmd .Flags ().GetBool (insecureFlagName )
14821526 http2PriorKnowledge , _ := cmd .Flags ().GetBool (http2PriorKnowledgeFlagName )
1483- if ! isSecure && ! http2PriorKnowledge {
1484- // Plain HTTP: server reflection requires HTTP/2, which needs prior knowledge
1485- // over a cleartext connection. Skip completion if the flag is not set.
1486- return nil , false
1527+ if ! isSecure {
1528+ // Completion uses server reflection, which requires HTTP/2. Over
1529+ // plain-text, HTTP/2 needs prior knowledge, so use it just like the
1530+ // command itself does when reflection is used with an http URL.
1531+ http2PriorKnowledge = true
14871532 }
14881533 key , _ := cmd .Flags ().GetString (keyFlagName )
14891534 cert , _ := cmd .Flags ().GetString (certFlagName )
0 commit comments