Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 3 additions & 6 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,9 @@ under the affected version with a reference to the CVE or advisory.
## [Unreleased]

### Added
- `sendit generate --url`: sitemap support — `Sitemap:` entries in `robots.txt` and `<a href="*sitemap*.xml">` links found during crawl are now parsed as sitemap XML sources, feeding discovered `<loc>` URLs into the crawl queue; handles both `<urlset>` and `<sitemapindex>` formats

### Fixed
- `sendit generate --url`: crawl now normalises URLs before deduplication — strips trailing slashes from non-root paths and removes fragments/query strings so that `https://example.com/page` and `https://example.com/page/` are no longer treated as separate targets
- `sendit generate --url`: non-HTML file extensions (`.xml`, `.json`, `.pdf`, `.css`, `.js`, images, fonts, media, archives) are now excluded as crawl targets; only pages likely to be HTML are included
- `sendit generate --url`: sitemap `<loc>` entries with a different scheme than the seed URL (e.g. `http://` locs in an `https://` site's sitemap) are normalised to the seed's scheme, preventing scheme-variant duplicates
- `type: grpc` driver — executes unary gRPC calls using server reflection; no `.proto` files required. URL format: `grpc://host:port/Service/Method` (plaintext) or `grpcs://` (TLS). JSON body is unmarshalled to protobuf via reflection. gRPC status codes are mapped to HTTP-like codes so the engine's error classifier and backoff work uniformly. Connections and method descriptors are cached per address.
- `grpc` block in `TargetConfig` and `TargetDefaultsConfig` with fields: `body`, `timeout_s`, `tls`, `insecure`
- gRPC driver documented in Drivers, Configuration, and Dependencies docs pages

---

Expand Down
2 changes: 1 addition & 1 deletion docs/content/docs/_index.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ Press Ctrl-C to stop and print a summary. See the [CLI Reference](cli/) for all
| [Getting Started](getting-started/) | Install, build, validate a config, run your first traffic, and deploy with Docker |
| [Configuration](configuration/) | Every config key, its type, default, and description |
| [Pacing Modes](pacing/) | `human`, `rate_limited`, `scheduled`, and `burst` — how request timing works |
| [Drivers](drivers/) | `http`, `browser`, `dns`, `websocket` — options and examples for each |
| [Drivers](drivers/) | `http`, `browser`, `dns`, `websocket`, `grpc` — options and examples for each |
| [Metrics](metrics/) | Prometheus metrics exposed by sendit and how to scrape them |
| [CLI Reference](cli/) | All commands and flags |
| [Dependencies](dependencies/) | Direct dependencies, their purpose, and their licences |
Expand Down
10 changes: 6 additions & 4 deletions docs/content/docs/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -92,10 +92,11 @@ Load targets from a plain-text file instead of (or in addition to) the inline `t

```
# config/targets.txt
https://example.com http 5
https://api.example.com http 3
example.com dns 2
wss://ws.example.com websocket
https://example.com http 5
https://api.example.com http 3
example.com dns 2
wss://ws.example.com websocket
grpc://svc.example.com:50051/helloworld.Greeter/SayHello grpc 4
```

`target_defaults` supplies remaining fields for every file-loaded target:
Expand All @@ -122,6 +123,7 @@ target_defaults:
| `dns.resolver` | `8.8.8.8:53` | DNS resolver address |
| `dns.record_type` | `A` | DNS record type |
| `websocket.duration_s` | `30` | How long to hold the connection open (seconds) |
| `grpc.timeout_s` | `15` | Per-call timeout (seconds) |

## `output`

Expand Down
6 changes: 4 additions & 2 deletions docs/content/docs/dependencies.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ weight: 95
description: "Direct dependencies, their purpose, and their licences."
---

sendit has 14 direct runtime dependencies. All are permissive open-source licences
sendit has 16 direct runtime dependencies. All are permissive open-source licences
compatible with the project's [MIT licence](https://github.com/lewta/sendit/blob/main/LICENSE).

The module graph is managed with `go mod tidy` and kept minimal — no dependency
Expand All @@ -28,6 +28,8 @@ appears that cannot be justified by the table below.
| [`github.com/spf13/viper`](https://github.com/spf13/viper) | v1.21.0 | MIT | Config file loading with environment variable overlay and `mapstructure` unmarshalling |
| [`golang.org/x/net`](https://pkg.go.dev/golang.org/x/net) | v0.52.0 | BSD-3-Clause | `html` subpackage — HTML parser used by the `generate` command to extract links |
| [`golang.org/x/time`](https://pkg.go.dev/golang.org/x/time) | v0.15.0 | BSD-3-Clause | `rate` subpackage — token-bucket rate limiter used by `rate_limited` and `scheduled` pacing |
| [`google.golang.org/grpc`](https://pkg.go.dev/google.golang.org/grpc) | v1.79.3 | Apache-2.0 | gRPC client and server — powers the `grpc` driver; includes reflection client and health service |
| [`google.golang.org/genproto/googleapis/rpc`](https://pkg.go.dev/google.golang.org/genproto/googleapis/rpc) | v0.0.0-20251202 | Apache-2.0 | Generated gRPC status and error type definitions — transitive requirement of `google.golang.org/grpc` |
| [`modernc.org/sqlite`](https://pkg.go.dev/modernc.org/sqlite) | v1.47.0 | BSD-3-Clause | Pure-Go SQLite driver (CGo-free) — used by `generate` to read Chrome/Firefox history and bookmark databases |

## Alternatives considered
Expand All @@ -50,7 +52,7 @@ All dependency licences are permissive and compatible with the project's MIT lic
| MIT | `bubbletea`, `lipgloss`, `chromedp`, `cron/v3`, `zerolog`, `viper` |
| ISC | `coder/websocket` |
| BSD-3-Clause | `miekg/dns`, `gopsutil/v3`, `x/net`, `x/time`, `modernc.org/sqlite` |
| Apache-2.0 | `prometheus/client_golang`, `cobra` |
| Apache-2.0 | `prometheus/client_golang`, `cobra`, `google.golang.org/grpc`, `genproto/googleapis/rpc` |

ISC, BSD-2-Clause, and BSD-3-Clause are functionally equivalent to MIT for distribution purposes.
Apache-2.0 is compatible with MIT when distributing binaries (no copyleft restriction).
67 changes: 66 additions & 1 deletion docs/content/docs/drivers.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
title: "Drivers"
linkTitle: "Drivers"
weight: 4
description: "HTTP, browser, DNS, and WebSocket driver options and examples."
description: "HTTP, browser, DNS, WebSocket, and gRPC driver options and examples."
---

A **driver** is responsible for executing a single request and returning a result. Each target in your config specifies a `type` that selects the driver. All drivers map their results to HTTP-like status codes so the engine's error classifier, backoff, and metrics work uniformly.
Expand Down Expand Up @@ -136,3 +136,68 @@ targets:
- url: "wss://stream.example.com:9443/feed"
type: websocket
```

## `grpc`

Executes a **unary gRPC call** using [google.golang.org/grpc](https://pkg.go.dev/google.golang.org/grpc). No `.proto` files are required — the driver uses [server reflection](https://grpc.io/docs/guides/reflection/) to discover request and response types at runtime, then marshals the JSON body to protobuf automatically.

```yaml
targets:
- url: grpc://localhost:50051/helloworld.Greeter/SayHello
weight: 10
type: grpc
grpc:
body: '{"name": "world"}' # JSON-encoded request (optional — defaults to empty message)
timeout_s: 15 # per-call timeout in seconds
tls: false # force TLS even when scheme is grpc://
insecure: false # skip TLS certificate verification
```

| Field | Default | Description |
|---|---|---|
| `body` | `""` | JSON-encoded request body. Must match the method's input proto type. Empty sends a default-constructed message. |
| `timeout_s` | `15` | Per-call timeout in seconds |
| `tls` | `false` | Force TLS even when the URL scheme is `grpc://` |
| `insecure` | `false` | Skip TLS certificate verification (combine with `tls: true` or `grpcs://` scheme) |

**URL scheme** selects transport security:

| Scheme | Transport |
|---|---|
| `grpc://host:port/Service/Method` | Plaintext |
| `grpcs://host:port/Service/Method` | TLS |

**gRPC status codes** are mapped to HTTP-like status codes so the engine's backoff and error classifier work uniformly:

| gRPC code | HTTP equivalent | Effect |
|---|---|---|
| OK (0) | 200 | success |
| InvalidArgument (3), OutOfRange (11) | 400 | permanent skip |
| Unauthenticated (16) | 401 | permanent skip |
| PermissionDenied (7) | 403 | permanent skip |
| NotFound (5) | 404 | permanent skip |
| AlreadyExists (6) | 409 | permanent skip |
| ResourceExhausted (8) | 429 | transient backoff |
| Unimplemented (12) | 501 | permanent skip |
| Unavailable (14) | 503 | transient backoff |
| DeadlineExceeded (4) | 504 | transient backoff |
| other | 500 | transient backoff |

**Prerequisite:** the gRPC server must have the [server reflection service](https://grpc.io/docs/guides/reflection/) enabled. Most frameworks enable it via a single line (e.g. `reflection.Register(s)` in Go). If reflection is not available, the driver returns an error immediately.

**Connection and descriptor caching:** connections and method descriptors are cached per address+TLS mode. Reflection is called only on the first request to each method; subsequent calls reuse the cached descriptor.

```yaml
# Multiple gRPC targets on the same server — connection is shared
targets:
- url: grpc://api.example.com:50051/user.UserService/GetUser
type: grpc
weight: 8
grpc:
body: '{"user_id": "u-123"}'
- url: grpc://api.example.com:50051/user.UserService/ListUsers
type: grpc
weight: 2
grpc:
body: '{}'
```
4 changes: 3 additions & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ require (
github.com/spf13/viper v1.21.0
golang.org/x/net v0.52.0
golang.org/x/time v0.15.0
google.golang.org/grpc v1.79.3
google.golang.org/protobuf v1.36.10
howett.net/plist v1.0.1
modernc.org/sqlite v1.47.0
)
Expand Down Expand Up @@ -77,7 +79,7 @@ require (
golang.org/x/sys v0.42.0 // indirect
golang.org/x/text v0.35.0 // indirect
golang.org/x/tools v0.42.0 // indirect
google.golang.org/protobuf v1.36.8 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20251202230838-ff82c1b0f217 // indirect
modernc.org/libc v1.70.0 // indirect
modernc.org/mathutil v1.7.1 // indirect
modernc.org/memory v1.11.0 // indirect
Expand Down
28 changes: 26 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,10 @@ github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S
github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0=
github.com/go-json-experiment/json v0.0.0-20260214004413-d219187c3433 h1:vymEbVwYFP/L05h5TKQxvkXoKxNvTpjxYKdF1Nlwuao=
github.com/go-json-experiment/json v0.0.0-20260214004413-d219187c3433/go.mod h1:tphK2c80bpPhMOI4v6bIc2xWywPfbqi1Z06+RcrMkDg=
github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=
github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY=
github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=
github.com/go-viper/mapstructure/v2 v2.4.0 h1:EBsztssimR/CONLSZZ04E8qAkxNYq4Qp9LvH92wZUgs=
Expand All @@ -49,6 +53,8 @@ github.com/gobwas/pool v0.2.1/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6Wezm
github.com/gobwas/ws v1.4.0 h1:CTaoG1tojrh4ucGPcoJFiAQUAsEWekEWvLy7GsVNqGs=
github.com/gobwas/ws v1.4.0/go.mod h1:G3gNqMNtPppf5XUz7O4shetPpcZ1VJ7zt18dlUeakrc=
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
Expand Down Expand Up @@ -160,6 +166,18 @@ github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e h1:JVG44RsyaB9T2KIHavM
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e/go.mod h1:RbqR21r5mrJuqunuUZ/Dhy/avygyECGrLceyNeo4LiM=
github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0=
github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0=
go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64=
go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y=
go.opentelemetry.io/otel v1.39.0 h1:8yPrr/S0ND9QEfTfdP9V+SiwT4E0G7Y5MO7p85nis48=
go.opentelemetry.io/otel v1.39.0/go.mod h1:kLlFTywNWrFyEdH0oj2xK0bFYZtHRYUdv1NklR/tgc8=
go.opentelemetry.io/otel/metric v1.39.0 h1:d1UzonvEZriVfpNKEVmHXbdf909uGTOQjA0HF0Ls5Q0=
go.opentelemetry.io/otel/metric v1.39.0/go.mod h1:jrZSWL33sD7bBxg1xjrqyDjnuzTUB0x1nBERXd7Ftcs=
go.opentelemetry.io/otel/sdk v1.39.0 h1:nMLYcjVsvdui1B/4FRkwjzoRVsMK8uL/cj0OyhKzt18=
go.opentelemetry.io/otel/sdk v1.39.0/go.mod h1:vDojkC4/jsTJsE+kh+LXYQlbL8CgrEcwmt1ENZszdJE=
go.opentelemetry.io/otel/sdk/metric v1.39.0 h1:cXMVVFVgsIf2YL6QkRF4Urbr/aMInf+2WKg+sEJTtB8=
go.opentelemetry.io/otel/sdk/metric v1.39.0/go.mod h1:xq9HEVH7qeX69/JnwEfp6fVq5wosJsY1mt4lLfYdVew=
go.opentelemetry.io/otel/trace v1.39.0 h1:2d2vfpEDmCJ5zVYz7ijaJdOF59xLomrvj7bjt6/qCJI=
go.opentelemetry.io/otel/trace v1.39.0/go.mod h1:88w4/PnZSazkGzz/w84VHpQafiU4EtqqlVdxWy+rNOA=
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
go.yaml.in/yaml/v2 v2.4.2 h1:DzmwEr2rDGHl7lsFgAHxmNz/1NlQ7xLIrlN2h5d1eGI=
Expand Down Expand Up @@ -191,8 +209,14 @@ golang.org/x/time v0.15.0/go.mod h1:Y4YMaQmXwGQZoFaVFk4YpCt4FLQMYKZe9oeV/f4MSno=
golang.org/x/tools v0.42.0 h1:uNgphsn75Tdz5Ji2q36v/nsFSfR/9BRFvqhGBaJGd5k=
golang.org/x/tools v0.42.0/go.mod h1:Ma6lCIwGZvHK6XtgbswSoWroEkhugApmsXyrUmBhfr0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/protobuf v1.36.8 h1:xHScyCOEuuwZEc6UtSOvPbAT4zRh0xcNRYekJwfqyMc=
google.golang.org/protobuf v1.36.8/go.mod h1:fuxRtAxBytpl4zzqUh6/eyUujkJdNiuEkXntxiD/uRU=
gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk=
gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E=
google.golang.org/genproto/googleapis/rpc v0.0.0-20251202230838-ff82c1b0f217 h1:gRkg/vSppuSQoDjxyiGfN4Upv/h/DQmIR10ZU8dh4Ww=
google.golang.org/genproto/googleapis/rpc v0.0.0-20251202230838-ff82c1b0f217/go.mod h1:7i2o+ce6H/6BluujYR+kqX3GKH+dChPTQU19wjRPiGk=
google.golang.org/grpc v1.79.3 h1:sybAEdRIEtvcD68Gx7dmnwjZKlyfuc61Dyo9pGXXkKE=
google.golang.org/grpc v1.79.3/go.mod h1:KmT0Kjez+0dde/v2j9vzwoAScgEPx/Bw1CYChhHLrHQ=
google.golang.org/protobuf v1.36.10 h1:AYd7cD/uASjIL6Q9LiTjz8JLcrh/88q5UObnmY3aOOE=
google.golang.org/protobuf v1.36.10/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
Expand Down
12 changes: 8 additions & 4 deletions internal/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,7 @@ func loadTargetsFile(cfg *Config) error {
defer f.Close()

d := cfg.TargetDefaults
validTypes := map[string]bool{"http": true, "browser": true, "dns": true, "websocket": true}
validTypes := map[string]bool{"http": true, "browser": true, "dns": true, "websocket": true, "grpc": true}

scanner := bufio.NewScanner(f)
lineNum := 0
Expand All @@ -120,7 +120,7 @@ func loadTargetsFile(cfg *Config) error {
typ := strings.ToLower(fields[1])

if !validTypes[typ] {
return fmt.Errorf("line %d: unknown type %q (must be http|browser|dns|websocket)", lineNum, typ)
return fmt.Errorf("line %d: unknown type %q (must be http|browser|dns|websocket|grpc)", lineNum, typ)
}

weight := d.Weight
Expand All @@ -143,6 +143,7 @@ func loadTargetsFile(cfg *Config) error {
Browser: d.Browser,
DNS: d.DNS,
WebSocket: d.WebSocket,
GRPC: d.GRPC,
})
}

Expand Down Expand Up @@ -221,7 +222,7 @@ func validate(cfg *Config) error {
errs = append(errs, "targets must have at least one entry (via 'targets' in config or 'targets_file')")
}

validTypes := map[string]bool{"http": true, "browser": true, "dns": true, "websocket": true}
validTypes := map[string]bool{"http": true, "browser": true, "dns": true, "websocket": true, "grpc": true}
for i, t := range cfg.Targets {
if t.URL == "" {
errs = append(errs, fmt.Sprintf("targets[%d].url must not be empty", i))
Expand All @@ -230,7 +231,10 @@ func validate(cfg *Config) error {
errs = append(errs, fmt.Sprintf("targets[%d].weight must be > 0", i))
}
if !validTypes[t.Type] {
errs = append(errs, fmt.Sprintf("targets[%d].type must be one of http|browser|dns|websocket, got %q", i, t.Type))
errs = append(errs, fmt.Sprintf("targets[%d].type must be one of http|browser|dns|websocket|grpc, got %q", i, t.Type))
}
if t.Type == "grpc" && !strings.HasPrefix(t.URL, "grpc://") && !strings.HasPrefix(t.URL, "grpcs://") {
errs = append(errs, fmt.Sprintf("targets[%d].url must start with grpc:// or grpcs:// for type grpc, got %q", i, t.URL))
}
}

Expand Down
14 changes: 13 additions & 1 deletion internal/config/schema.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ type TargetDefaultsConfig struct {
Browser BrowserConfig `mapstructure:"browser"`
DNS DNSConfig `mapstructure:"dns"`
WebSocket WebSocketConfig `mapstructure:"websocket"`
GRPC GRPCConfig `mapstructure:"grpc"`
}

// PacingConfig controls how requests are spaced in time.
Expand Down Expand Up @@ -78,11 +79,12 @@ type BackoffConfig struct {
type TargetConfig struct {
URL string `mapstructure:"url"`
Weight int `mapstructure:"weight"`
Type string `mapstructure:"type"` // http | browser | dns | websocket
Type string `mapstructure:"type"` // http | browser | dns | websocket | grpc
HTTP HTTPConfig `mapstructure:"http"`
Browser BrowserConfig `mapstructure:"browser"`
DNS DNSConfig `mapstructure:"dns"`
WebSocket WebSocketConfig `mapstructure:"websocket"`
GRPC GRPCConfig `mapstructure:"grpc"`
}

// HTTPConfig holds HTTP-specific target settings.
Expand Down Expand Up @@ -113,6 +115,16 @@ type WebSocketConfig struct {
ExpectMessages int `mapstructure:"expect_messages"`
}

// GRPCConfig holds gRPC target settings.
// The URL scheme selects TLS: grpc:// for plaintext, grpcs:// for TLS.
// The cfg.TLS field can also force TLS regardless of scheme.
type GRPCConfig struct {
Body string `mapstructure:"body"` // JSON-encoded request body (unmarshalled via reflection)
TimeoutS int `mapstructure:"timeout_s"` // per-call timeout in seconds (default 15)
TLS bool `mapstructure:"tls"` // force TLS even when scheme is grpc://
Insecure bool `mapstructure:"insecure"` // skip TLS certificate verification
}

// OutputConfig controls writing request results to a file.
type OutputConfig struct {
Enabled bool `mapstructure:"enabled"`
Expand Down
Loading
Loading