Aller au contenu principal

Testing gRPC Services

Aethrix Studio imports Protocol Buffers (.proto) service definitions — or discovers them directly from a running server via the gRPC Server Reflection Protocol — and lets you test unary and streaming RPC calls interactively.

Prerequisites​

  • A .proto file (or a directory of .proto files), or a live gRPC server that exposes the Server Reflection Protocol.
  • No external protoc tooling is required — the compiler is bundled with Aethrix.

Importing a Proto File or Discovering a Service​

  1. Click Import API in the API Playground toolbar.
  2. In Service Type, select gRPC - Protobuf Definition (.proto).
  3. In Service Description, provide one of:
    • A local .proto file path, or a directory containing .proto files.
    • A URL to a .proto file.
    • A live gRPC server URL — enter an http(s)://host:port address, or use the grpc:// / grpc+insecure:// schemes. Aethrix connects to the server, enumerates its services via the Server Reflection Protocol, and imports them without a .proto file.
  4. Step through Authentication (only if the server requires it) and Application & Service Name, then click Import.
  5. Aethrix compiles the definitions into typed C# DTOs and wraps them in an extension package (.pkg). The import pipeline runs protoc to build a FileDescriptorSet, generates C# message/enum classes and an extension class wiring each RPC, then compiles via Roslyn and loads the extension.
remarque

When discovering via server reflection, Aethrix issues ListServices and FileContainingSymbol reflection requests. If the server reports reflection as Unimplemented, import the .proto files directly instead.

Service Discovery via Server Reflection​

The grpc:// and grpc+insecure:// schemes (and plain http(s):// URLs that do not end in .proto) are treated as live servers. Aethrix reflects their descriptor set, so you can test a service without possessing its .proto sources. Basic-auth credentials entered in the Authentication step are forwarded to the reflection channel.

Service Tree Organization​

After import, gRPC services are organized in the service tree:

  • Application — the name you assigned during import.
  • Service — the proto package name (e.g. helloworld).
  • Operations — each RPC method listed individually (e.g. SayHello, GetFeature).

Streaming RPCs are identified by their direction flags. Selecting a server-streaming, client-streaming, or bidirectional RPC opens the StreamingEditor — see Streaming RPCs below.

Composing a Unary Request​

Select a unary gRPC operation from the service tree to open the request editor. The request message is edited in a structured tree view (an ObjectEditor) that mirrors the protobuf message fields — there is no separate text/raw view for gRPC.

Example​

For a SayHello RPC with the proto definition:

service Greeter {
rpc SayHello (HelloRequest) returns (HelloReply);
}

message HelloRequest {
string name = 1;
}

The editor presents a name field. Enter a value and click Send.

Importing a Directory​

When the path is a folder, its .proto files are compiled as one specification and their import lines are resolved from a single root — normally the folder you picked. If the folder itself holds no .proto files, Aethrix searches its sub-folders instead, because that is how most proto trees are shipped; when it does hold some, only those are compiled, so an old/ or a vendored directory underneath is not swept into the import unasked. Two things follow, and both used to go wrong in silence:

  • Services in sub-folders keep distinct identities, so a tree holding both auth/v1/service.proto and billing/v1/service.proto imports completely. Under one root neither shadows the other.
  • Some projects write imports from a level above the folder — Go trees spell them etcd/api/mvccpb/kv.proto, opentelemetry/proto/common/v1/common.proto. Aethrix resolves those from the parent when the files are really there. If they are not, pick the folder the import paths are written against — usually the repository root rather than a directory inside it.

A dependency that is simply not on disk (the gogoproto/gogo.proto or google/api/annotations.proto closure you did not vendor) stops the import and names the missing path, so you know what to add. A very large folder — thousands of protos, such as a whole googleapis checkout — can outgrow what the operating system allows on one command line. That is reported as GRPC_PROTO_004, naming how many files and how many characters, and the answer is to import the sub-folder holding the service you want to test.

Defaults, Empty Values, and Presence​

Protobuf distinguishes a field you left alone from a field you set to its empty value, and Aethrix sends what you actually did. This matters most when you are testing how a server behaves on missing or zero data.

What you do in the editorWhat is sent
Leave a field untouched (proto3, no explicit optional)Nothing for that field — the server sees it as absent and uses the type's default.
Set a number to 0, or text to emptySent as 0 / empty. The field is present on the wire.
Choose a oneof branch and set it to 0The branch is selected and sent, so the server can tell which alternative you picked.
Set a wrapper field such as google.protobuf.Int32Value to 0Present with the value 0 — distinct from leaving it out.
Leave a proto2 optional field untouched — number, text or bytesNot sent, so the server applies the [default = …] its schema declares. Set it to 0 or to empty text and that is sent instead.
Leave a proto2 required field untouchedAlways sent, as the type's zero (0, empty text). A [default = …] the schema declares is not substituted, and the import names it as a warning.

A field the schema marks required is flagged in the tree, not enforced. Aethrix is a testing tool, and sending a request without required data is a legitimate thing to test, so the marker tells you the server will probably reject it — it does not stop you from trying.

Most Google-style APIs do not use proto2's required keyword: they mark a field with the option [(google.api.field_behavior) = REQUIRED]. Aethrix reads that too, along with OUTPUT_ONLY / IMMUTABLE (server-set) and INPUT_ONLY (request-only), so the badges on an imported Google service match what its own documentation claims. The option's number is looked up in the compiled specification rather than assumed, so a spec that declares the option differently is still read correctly — and if the option's definition is not in the import at all, no badge appears rather than a guessed one.

:::tip Reading the body text The JSON used for a gRPC message body keys each field by the name written in your .proto file (display_name, not displayName), shows enums by name rather than by number, and shows 64-bit integers as plain numbers. Values may also be read back in the other spellings you are likely to paste: a quoted number for a 64-bit field, or an enum given as its number instead of its name.

One exception, and it comes from your own schema: a field that declares json_name uses that as its key, because that is the name the service itself puts in its JSON. The common case is a field named type declared as [json_name = "_type"] — in-toto and SLSA attestations write it that way — and a document copied from the service now pastes in whole.

The standard types keep protobuf's own JSON notation, because that is what a service sends and what its documentation shows:

Field typeHow it appears
google.protobuf.Timestampa date string — "2024-05-01T12:10:00.123456789Z"
google.protobuf.Durationa length of time — written as "3.500s", read as "3.5s" too
google.protobuf.FieldMaskcomma-separated names — "a,bC"
google.protobuf.Struct, Value, ListValueordinary JSON objects, scalars and arrays
google.protobuf.Anyan object carrying "@type", with the packed message spelled out

An Any holding a message your import did not declare — a type from a schema sitting behind the service — carries its payload base64-encoded beside the type name, which is the same notation protobuf defines for that case, and it reads back unchanged. The response is shown in this same notation, so a value you read out of one call can be pasted into the next.

Apart from that, note where this still differs from the JSON some gRPC tools emit by default: those tools usually rewrite field names to lower camel case. Aethrix keeps the name from your .proto file, so when you copy a payload from another tool, rename the keys that came from a snake_case field — or paste the whole document and fill in the fields the editor did not pick up. :::

gRPC Options​

When a gRPC operation is selected, the Options tab shows gRPC-specific settings (under the gRPC Options header):

OptionDescription
Deadline (ms)Maximum time in milliseconds to wait for a response. Leave empty for no deadline.
Max Response Size (bytes)Maximum allowed response message size. Leave empty for the default (4 MB).
Use TLSToggle to enable/disable Transport Layer Security. Enabled by default.

Metadata​

The Metadata table lets you add custom key-value pairs to the gRPC call metadata (equivalent to HTTP headers):

  • Add — adds a new metadata row.
  • Remove — removes the selected row.
  • Clear — clears all metadata entries.

Retries​

Retry behaviour is not a per-step option — it is configured on the gRPC client itself, through its Retry Count and Retry Delay (ms) parameters (defaults: 0 — disabled, and 100). A request's own options can also carry a retry count and delay, and those take precedence over the client values.

When a unary call fails with a transport-grade status, the request is sent again up to Retry Count times; the wait between attempts is the base delay multiplied by the attempt number, so it grows with each retry. Only these statuses are retried:

  • Unavailable (14) — the service is temporarily unreachable or overloaded.
  • DeadlineExceeded (4) — the call ran past its deadline.
  • ResourceExhausted (8) — out of quota, rate-limited, or out of streams.
  • Aborted (10) — the service asked for the call to be retried, e.g. after a concurrency conflict.

A status that is the server's answer about the request itself — InvalidArgument, NotFound, FailedPrecondition and the rest — is reported as-is: retrying it would only repeat the same answer. With the default Retry Count of 0 nothing is retried, and streaming calls are never retried; a failed stream is reported once (see Failed Calls above) and the client re-sends by starting the call again.

Authentication​

gRPC requests use the same authentication mechanisms as REST. Configure authentication in the Authentication tab; credentials are injected into the gRPC call metadata (as an Authorization entry and any custom metadata), not into HTTP headers.

MechanismDescription
NoneNo authentication — for public or local services.
BasicUsername and password sent as call metadata.
Bearer TokenJWT or opaque token sent as call metadata.
OAuth 2.0Token obtained from an OAuth2 authorization server.
Client Certificate (mTLS)Mutual TLS with an X.509 client certificate attached to the gRPC channel.

Streaming RPCs​

Selecting a server-streaming, client-streaming, or bidirectional RPC from the service tree opens the StreamingEditor — a specialized layout replacing the standard unary editor.

Streaming Editor Layout​

The StreamingEditor has a split-pane layout:

  • Left pane (Request): an ObjectEditor for composing the request message. For client-streaming and bidirectional RPCs this edits the streaming request message(s).
  • Right pane (Response): a DataGrid showing each received response frame with columns for index (#) and a truncated JSON body preview (Body). Selecting a row opens the full message body in a read-only inspector (ObjectEditor) below.
  • Status bar: Shows live elapsed time and stream status — transitioning through "Stream idle", "X message(s) received…", "X message(s) received — complete", and "Error: …".

Toolbar​

ButtonWhen VisibleAction
Start StreamStream idleSends the request and begins receiving response frames.
CancelStream activeTerminates the in-flight stream.

Streaming Modes​

ModeRequestResponse
Server-streamingSingle request messageMultiple response frames in the DataGrid.
Client-streamingMultiple request messagesSingle response.
BidirectionalMultiple request messagesMultiple response frames in the DataGrid.

The session is driven by IStreamingSession, which raises MessageReceived, StreamCompleted, and StreamFailed events to update the DataGrid and status bar live as frames arrive.

Response Inspection​

Unary Responses​

gRPC unary responses are displayed in the response viewer:

  • JSON body — the protobuf response serialized as JSON.
  • Tree view — navigate the response structure hierarchically.
  • JSON Path evaluation — right-click a value to copy its JSON Path.

The status area shows the request result and elapsed time.

Failed Calls​

A call the service rejects records the gRPC status in the response viewer:

gRPC Status: 3 INVALID_ARGUMENT

Most services say more than a status code can. They pack a google.rpc.Status into the call's trailers, and when one arrives its details are decoded and shown in the same JSON body:

  • BadRequest — which field was refused, and why: user.email: must be a valid email.
  • PreconditionFailure — the condition that was not met, e.g. FIND/users/1: no such user.
  • QuotaFailure — the quota that was exceeded.
  • ErrorInfo — the machine-readable reason and the domain that defines it.
  • Anything else the service packed, including a type from its own schema, appears in the JSON body. A detail whose type Aethrix cannot name is shown as base64 beside its type name rather than dropped.

Those field-level reasons are also appended to the request's error message, so the explanation is visible without opening the response body:

gRPC error InvalidArgument: Invalid request (user.email: must be a valid email; order.id: not found)

A service that sends only a code and a one-line message shows exactly those two things — there is nothing further on the wire to decode.

Streaming calls report a rejection the same way. A server-streaming or bidirectional call the service refuses does not read as sent: the request is marked failed, the status line and the decoded details appear exactly as they do for a unary call, and the streaming session shows the stream ended in error. A service that refuses before sending a single frame reports the same way, with an empty frame list.

Streaming Responses​

For streaming RPCs, each received frame appears as a row in the response DataGrid. Select a row to inspect the full JSON body in the read-only inspector panel. The status bar shows the cumulative message count and completion status.

Service Simulation​

Aethrix includes a built-in gRPC Server for simulating gRPC services. This is useful for testing your application when the real gRPC service is unavailable.

The gRPC Server:

  • Listens for HTTP/2 connections on a configurable port (default 50051).
  • Automatically registers routes from imported proto packages.
  • Returns configured responses based on response conditions.
  • Answers a response that declares an error status (the Error status strip in the response editor) with that grpc-status instead of 0 — no body frames go out, and the response body is written as a google.rpc.Status JSON whose details travel in the grpc-status-details-bin trailer and whose message in grpc-message, exactly the shape a real service sends. The client reads it like any rejection (see Failed Calls above), so a client's error handling can be tested against a rejection the simulation itself produced.
  • Supports TLS (enabled by default) with self-signed certificate fallback.
  • Responds to the Server Reflection Protocol, so simulated services can also be discovered via reflection.

See Service Simulation Overview for details on configuring responses and conditions.

Limitations​

For every gRPC / Protobuf construct that Aethrix degrades or rejects — with an example, what you see in Studio, and how severe it is — see Import Diagnostics — Unsupported structures & fallbacks (gRPC tab). This is the single, exclusive reference.

  • Declared error statuses carry a status and optional details, not a typed body — a response that declares a non-zero grpc-status answers trailers-only; a body JSON that does not parse as google.rpc.Status is ignored rather than guessed at, and the call then carries the status code alone.

See Also​