Skip to content

API reference

The whole public surface of gitlab.com/phpboyscout/go/grpcclient is one function and one struct. This page states what each field means, what it defaults to and what happens when it is wrong.

Symbol Kind Summary
Dial func(Target, ...grpc.DialOption) (*grpc.ClientConn, error) Builds a lazy *grpc.ClientConn for the target
Target struct Host, port and TLS material for one gRPC server

There is nothing else exported — no constructor, no options type, no interface, no package-level state. Full godoc is on pkg.go.dev.

Dial

func Dial(t Target, opts ...grpc.DialOption) (*grpc.ClientConn, error)

Dial does four things, in this order:

  1. Derives transport credentials from t.TLS (see Target fields).
  2. Substitutes localhost for an empty t.Host.
  3. Formats the endpoint as fmt.Sprintf("%s:%d", host, t.Port).
  4. Calls grpc.NewClient(endpoint, opts...) with the derived credentials placed first in the option list and the caller's options after them.

On success it returns a stock *grpc.ClientConn — nothing about it is specific to this module, so every method, every grpc.CallOption and every teardown convention is the gRPC SDK's. Close it with conn.Close() when you are done.

On failure it returns nil and a wrapped error. See Errors returned by Dial.

Dial does not connect to anything

Dial calls grpc.NewClient, which is lazy. No DNS lookup, no TCP connect and no TLS handshake happen inside Dial; they happen on the first RPC, or when you call conn.Connect() explicitly.

A Dial that returns without error therefore means the target string parsed and the options validated. It says nothing about whether the server exists, is listening, or presents a certificate you trust. Connection failures arrive at the call site, as a codes.Unavailable status. Errors returned by Dial lists which failures land where.

Dial is not a connection pool

Each Dial returns one independent *grpc.ClientConn. The module keeps no cache and no registry, so calling Dial twice with the same Target gives you two connections and two sets of subchannels. A *grpc.ClientConn is already safe for concurrent use and already multiplexes calls over HTTP/2 — dial once per service and share the connection.

Target fields

type Target struct {
    Host string
    Port int
    TLS  gtls.Pair // gitlab.com/phpboyscout/go/tls
}
Field Type Zero value Meaning
Host string ""localhost Dial host, used verbatim as the left side of host:port
Port int 0 Dial port, formatted with %d, never validated
TLS gtls.Pair disabled Transport security; only Enabled and Cert are read

Target.Host — what an empty host means

An empty Host becomes localhost. That is the module's only default, and it exists because dialling a co-located server on loopback is the common case.

Host is a hostname or IP literal, not a gRPC target URI. It is pasted into host:port with fmt.Sprintf, so a scheme or a resolver prefix is not stripped and not rejected:

Host you set Endpoint produced Result
"" localhost:8080 works
svc.internal svc.internal:8080 works
10.0.0.5 10.0.0.5:8080 works
[::1] [::1]:8080 works — IPv6 literals must be bracketed
::1 ::1:8080 Dial succeeds; the RPC times out — see below
dns:///svc dns:///svc:8080 Dial succeeds; the RPC resolves the wrong name
https://svc https://svc:8080 Dial succeeds; the RPC fails

Every one of those bad values passes Dial silently. If you accept a host from configuration, validate it before you build the Target.

An unbracketed IPv6 host fails in one of two ways, depending on the port

Host: "::1" with Port: 8080 produces the endpoint ::1:8080, and that string is itself a valid IPv6 literal — 8080 is a legal four-hex-digit group. The resolver reads the whole thing as an address, applies the default port 443, and your intended port is silently discarded. The RPC then fails with a timeout rather than an address error:

rpc error: code = DeadlineExceeded desc = context deadline exceeded while waiting for connections to become ready

A port that cannot be a hextet — anything above ffff, or with a non-hex digit — cannot be absorbed that way, so the address is rejected outright and you get the clearer error quoted in Errors:

rpc error: code = Unavailable desc = invalid target address ::1:37225, error info: address ::1:37225:443: too many colons in address

The second form is the one you want, because it names the problem. Which one you get is an accident of the port number, so do not rely on either — bracket the literal.

Target.Port — no validation, no default

Port is formatted with %d and never checked. 0, -1 and 99999 all produce a *grpc.ClientConn from Dial without error; the failure appears at the first RPC. There is no default port — leaving Port unset gives you localhost:0, which is not what you meant.

Target.TLS — which Pair fields are read

gtls.Pair is a shared type used for both server and client TLS across the phpboyscout toolkit, so it carries more fields than a client needs. Dial reads exactly two.

gtls.Pair field Read by Dial? What it does here
Enabled yes false → insecure credentials; true → TLS via go/tls's ClientConfig
Cert yes, when Enabled Path to a PEM file of CA certificates to trust. Empty → system roots
Key no Silently ignored — the client presents no certificate
ClientCAs no Server-side only; silently ignored
ClientAuth no Server-side only; silently ignored, including an invalid value

Two of those deserve emphasis because they mislead.

Cert is the CA you trust, not the certificate you present. On a server, Pair.Cert and Pair.Key are the identity that server offers. On a client, Dial passes Cert straight to gtls.ClientConfig, which loads it into tls.Config.RootCAs. Handing Dial a server's own certificate therefore "works" for a self-signed server — you end up trusting exactly that leaf — but it is not what the field name suggests.

Reusing one Pair for a server and a client is a trap. A Pair populated for mutual TLS, with Key, ClientCAs and ClientAuth all set, produces a one-way TLS client here and no warning. See What grpcclient does not do.

How Target.TLS becomes transport credentials

TLS.Enabled TLS.Cert Credentials Dial uses
false anything (ignored) insecure.NewCredentials() — plaintext HTTP/2
true "" credentials.NewTLS(gtls.ClientConfig()) — hardened config, system roots
true path to PEM credentials.NewTLS(gtls.ClientConfig(cert)) — hardened config, that file as the only trust anchor

When Cert is set it replaces the system roots rather than adding to them: gtls.ClientConfig builds a fresh x509.CertPool from the named files and assigns it to RootCAs. A client configured with a private CA will not trust a public one.

What the hardened TLS config actually sets

Enabled: true selects go/tls's DefaultConfig, which is fixed and not configurable through Target:

  • MinVersion: TLS 1.2.
  • CipherSuites: ECDHE_ECDSA_WITH_AES_256_GCM_SHA384, ECDHE_RSA_WITH_AES_256_GCM_SHA384, ECDHE_ECDSA_WITH_AES_128_GCM_SHA256, ECDHE_RSA_WITH_AES_128_GCM_SHA256, ECDHE_ECDSA_WITH_CHACHA20_POLY1305, ECDHE_RSA_WITH_CHACHA20_POLY1305.
  • CurvePreferences: X25519, then P-256.

Note that Go applies CipherSuites to TLS 1.2 only; a TLS 1.3 handshake uses Go's own TLS 1.3 suites regardless.

Which hostname the certificate is checked against

Dial does not set ServerName, so gRPC verifies the server certificate against the authority it derives from the endpoint — that is, Target.Host, or localhost when Host is empty. There is no way to dial one address and verify a different name through Target; if you need that, pass grpc.WithAuthority yourself as a dial option.