nonnative 3.13.0 → 3.15.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.circleci/config.yml +1 -1
- data/AGENTS.md +26 -0
- data/Gemfile.lock +1 -1
- data/README.md +6 -3
- data/lib/nonnative/configuration_proxy.rb +2 -2
- data/lib/nonnative/cucumber.rb +1 -0
- data/lib/nonnative/error.rb +1 -1
- data/lib/nonnative/fault_injection_proxy.rb +12 -0
- data/lib/nonnative/http_proxy_server.rb +7 -9
- data/lib/nonnative/http_server.rb +12 -12
- data/lib/nonnative/http_service.rb +10 -0
- data/lib/nonnative/proxy.rb +1 -1
- data/lib/nonnative/socket_pair.rb +1 -0
- data/lib/nonnative/socket_pair_factory.rb +5 -1
- data/lib/nonnative/timeout_socket_pair.rb +30 -0
- data/lib/nonnative/version.rb +1 -1
- data/lib/nonnative.rb +2 -0
- metadata +3 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 8c6665f1bde87f38b1ba0622168efd569d1cce78df5773597984ab3eb07e07e6
|
|
4
|
+
data.tar.gz: 5662743abeb5f331675ef634754c024fed13f876664d5e0beccea21199c47f9a
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 1ec6d29ca79065600e123c9ec4e78d3d0b8ca76f06538b88c21b771f7be3ffce428f77949c80da574f7420826385e637de94f164711be82a5c7bc664a8ca9e41
|
|
7
|
+
data.tar.gz: 0aba7bdf975ef0f28ae9eb5f14ab4a78d8cf160ac09e63ebaff38d8edddbec8bb3fb94855dec864f18972771ad97076bcb0cfae2e5ad3713e6e0f87e65c38de6
|
data/.circleci/config.yml
CHANGED
data/AGENTS.md
CHANGED
|
@@ -79,6 +79,32 @@ of dependencies.
|
|
|
79
79
|
thread. Do not flag the listener `join` as an unbounded reliability gap unless
|
|
80
80
|
the task is explicitly about proxy shutdown behavior or there is a normal-use
|
|
81
81
|
reproducer, failing CI evidence, or platform-specific hang evidence.
|
|
82
|
+
- Service readiness is intentionally TCP-only for externally managed
|
|
83
|
+
dependencies. Do not flag missing HTTP/gRPC service readiness as a feature gap
|
|
84
|
+
unless the task is explicitly about changing the `Nonnative::Service`
|
|
85
|
+
readiness model. HTTP/gRPC readiness belongs to managed processes, where the
|
|
86
|
+
user explicitly models the application health endpoints.
|
|
87
|
+
- Process runners intentionally inherit the parent working directory. Nonnative
|
|
88
|
+
is normally run from the test folder that owns `test/nonnative.yml`, relative
|
|
89
|
+
config paths, logs, and reports. Do not flag missing process cwd/chdir support
|
|
90
|
+
as a feature gap unless the task is explicitly about changing process working
|
|
91
|
+
directory behavior.
|
|
92
|
+
- Message-specific start/stop assertions and attempted-stop success assertions
|
|
93
|
+
in `features/step_definitions/lifecycle_steps.rb` are repository-local
|
|
94
|
+
Cucumber helpers. Do not flag their absence from `lib/nonnative/cucumber.rb`
|
|
95
|
+
as a feature gap unless downstream usage evidence exists or the task is
|
|
96
|
+
explicitly about expanding public lifecycle assertion steps.
|
|
97
|
+
- Liveness, readiness, and metrics endpoint assertion steps in
|
|
98
|
+
`features/step_definitions/servers_steps.rb` are repository-local Cucumber
|
|
99
|
+
helpers for testing nonnative's own framework assumptions. Do not flag their
|
|
100
|
+
absence from `lib/nonnative/cucumber.rb` as a feature gap unless downstream
|
|
101
|
+
usage evidence exists or the task is explicitly about expanding public
|
|
102
|
+
observability assertion steps.
|
|
103
|
+
- `Nonnative::Configuration` intentionally exposes `process_by_name` without
|
|
104
|
+
matching `server_by_name` or `service_by_name` helpers. Do not flag missing
|
|
105
|
+
server/service configuration lookup helpers as a feature gap unless downstream
|
|
106
|
+
usage evidence exists or the task is explicitly about changing the
|
|
107
|
+
pre-start configuration lookup API.
|
|
82
108
|
|
|
83
109
|
## Runtime Model
|
|
84
110
|
|
data/Gemfile.lock
CHANGED
data/README.md
CHANGED
|
@@ -410,7 +410,7 @@ Define your server:
|
|
|
410
410
|
```ruby
|
|
411
411
|
module Nonnative
|
|
412
412
|
module Features
|
|
413
|
-
class
|
|
413
|
+
class HelloService < Nonnative::HTTPService
|
|
414
414
|
get '/hello' do
|
|
415
415
|
'Hello World!'
|
|
416
416
|
end
|
|
@@ -418,7 +418,7 @@ module Nonnative
|
|
|
418
418
|
|
|
419
419
|
class HTTPServer < Nonnative::HTTPServer
|
|
420
420
|
def initialize(service)
|
|
421
|
-
super(
|
|
421
|
+
super(HelloService.new, service)
|
|
422
422
|
end
|
|
423
423
|
end
|
|
424
424
|
end
|
|
@@ -543,7 +543,7 @@ end
|
|
|
543
543
|
|
|
544
544
|
Define your server:
|
|
545
545
|
|
|
546
|
-
Assume the gRPC service
|
|
546
|
+
Assume the gRPC service type and response types below come from your generated gRPC stubs.
|
|
547
547
|
|
|
548
548
|
```ruby
|
|
549
549
|
module Nonnative
|
|
@@ -758,6 +758,7 @@ Clients connect to the service `host`/`port`, while the proxy forwards traffic t
|
|
|
758
758
|
|
|
759
759
|
- `close_all` - Closes the socket as soon as it connects.
|
|
760
760
|
- `delay` - Delays traffic on the connection. Defaults to 2 seconds and can be configured through options.
|
|
761
|
+
- `timeout` - Accepts the connection and stalls traffic until reset or stop closes the connection, so clients exercise their own read timeout behavior.
|
|
761
762
|
- `invalid_data` - Forwards client requests unchanged, then corrupts upstream responses before they reach the client.
|
|
762
763
|
|
|
763
764
|
###### 🧩 Fault Injection Services
|
|
@@ -769,6 +770,7 @@ name = 'name of service in configuration'
|
|
|
769
770
|
service = Nonnative.pool.service_by_name(name)
|
|
770
771
|
|
|
771
772
|
service.proxy.close_all # To use close_all.
|
|
773
|
+
service.proxy.timeout # To stall traffic until reset or stop.
|
|
772
774
|
service.proxy.reset # To reset it back to a good state.
|
|
773
775
|
```
|
|
774
776
|
|
|
@@ -776,6 +778,7 @@ Use the Cucumber proxy steps:
|
|
|
776
778
|
|
|
777
779
|
```cucumber
|
|
778
780
|
Given I set the proxy for service 'service_1' to 'close_all'
|
|
781
|
+
Given I set the proxy for service 'service_1' to 'timeout'
|
|
779
782
|
Then I should reset the proxy for service 'service_1'
|
|
780
783
|
```
|
|
781
784
|
|
|
@@ -4,8 +4,8 @@ module Nonnative
|
|
|
4
4
|
# Proxy configuration attached to a service configuration.
|
|
5
5
|
#
|
|
6
6
|
# A proxy allows you to interpose behavior between a client and a real service. For example,
|
|
7
|
-
# the built-in `"fault_injection"` proxy can close connections, introduce delays,
|
|
8
|
-
# for resilience testing.
|
|
7
|
+
# the built-in `"fault_injection"` proxy can close connections, introduce delays, stall traffic,
|
|
8
|
+
# or corrupt data for resilience testing.
|
|
9
9
|
#
|
|
10
10
|
# This object is created automatically for each service via {Nonnative::ConfigurationService}.
|
|
11
11
|
# When `kind` is set to `"none"`, no proxy is started and the service will use its configured
|
data/lib/nonnative/cucumber.rb
CHANGED
data/lib/nonnative/error.rb
CHANGED
|
@@ -11,6 +11,7 @@ module Nonnative
|
|
|
11
11
|
#
|
|
12
12
|
# - {#close_all}: close connections immediately on accept
|
|
13
13
|
# - {#delay}: delay reads by a configured duration (default: 2 seconds)
|
|
14
|
+
# - {#timeout}: accept connections and keep them silent until clients time out
|
|
14
15
|
# - {#invalid_data}: forward requests unchanged and mutate upstream responses before they reach clients
|
|
15
16
|
# - {#reset}: return to healthy pass-through behavior
|
|
16
17
|
#
|
|
@@ -107,6 +108,17 @@ module Nonnative
|
|
|
107
108
|
apply_state :delay
|
|
108
109
|
end
|
|
109
110
|
|
|
111
|
+
# Accepts connections and stalls without forwarding bytes.
|
|
112
|
+
#
|
|
113
|
+
# This simulates a dependency that accepts a TCP connection but leaves clients waiting until
|
|
114
|
+
# their own read timeout fires. The proxy keeps the connection silent until reset or stop closes
|
|
115
|
+
# active connections.
|
|
116
|
+
#
|
|
117
|
+
# @return [void]
|
|
118
|
+
def timeout
|
|
119
|
+
apply_state :timeout
|
|
120
|
+
end
|
|
121
|
+
|
|
110
122
|
# Mutates upstream responses while forwarding client requests unchanged.
|
|
111
123
|
#
|
|
112
124
|
# @return [void]
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
#
|
|
8
8
|
# This file defines two classes:
|
|
9
9
|
#
|
|
10
|
-
# - {Nonnative::HTTPProxy}:
|
|
10
|
+
# - {Nonnative::HTTPProxy}: an HTTP service that implements the proxying behavior.
|
|
11
11
|
# - {Nonnative::HTTPProxyServer}: a {Nonnative::HTTPServer} wrapper that runs the proxy app under Puma.
|
|
12
12
|
#
|
|
13
13
|
# Notes:
|
|
@@ -17,12 +17,12 @@
|
|
|
17
17
|
# @see Nonnative::HTTPServer
|
|
18
18
|
# @see Nonnative::Server
|
|
19
19
|
module Nonnative
|
|
20
|
-
#
|
|
20
|
+
# HTTP service implementing a simple forward proxy.
|
|
21
21
|
#
|
|
22
|
-
# The upstream host is configured via
|
|
22
|
+
# The upstream host is configured via service settings (see {Nonnative::HTTPProxyServer}).
|
|
23
23
|
#
|
|
24
24
|
# Supported HTTP verbs: GET, POST, PUT, PATCH, DELETE.
|
|
25
|
-
class HTTPProxy <
|
|
25
|
+
class HTTPProxy < Nonnative::HTTPService
|
|
26
26
|
NON_FORWARDABLE_HEADERS = %w[
|
|
27
27
|
Host
|
|
28
28
|
Accept-Encoding
|
|
@@ -135,13 +135,11 @@ module Nonnative
|
|
|
135
135
|
# @param host [String] upstream host to proxy to (HTTPS)
|
|
136
136
|
# @param service [Nonnative::ConfigurationServer] server configuration
|
|
137
137
|
def initialize(host, service)
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
set :host, host
|
|
141
|
-
end
|
|
138
|
+
http_service = Class.new(Nonnative::HTTPProxy) do
|
|
139
|
+
set :host, host
|
|
142
140
|
end
|
|
143
141
|
|
|
144
|
-
super(
|
|
142
|
+
super(http_service.new, service)
|
|
145
143
|
end
|
|
146
144
|
end
|
|
147
145
|
end
|
|
@@ -3,19 +3,19 @@
|
|
|
3
3
|
module Nonnative
|
|
4
4
|
# Puma-based HTTP server runner.
|
|
5
5
|
#
|
|
6
|
-
# This is a convenience server implementation for running
|
|
7
|
-
#
|
|
6
|
+
# This is a convenience server implementation for running an HTTP service in-process under
|
|
7
|
+
# Nonnative's server lifecycle. It binds to the configured server `host` and first `ports` entry.
|
|
8
8
|
#
|
|
9
9
|
# The server is started and stopped by {Nonnative::Server} via {#perform_start} / {#perform_stop}.
|
|
10
10
|
#
|
|
11
|
-
# @example Running
|
|
11
|
+
# @example Running an HTTP service
|
|
12
|
+
# class HelloHTTPService < Nonnative::HTTPService
|
|
13
|
+
# get('/hello') { 'Hello World!' }
|
|
14
|
+
# end
|
|
15
|
+
#
|
|
12
16
|
# class HelloHTTPServer < Nonnative::HTTPServer
|
|
13
17
|
# def initialize(service)
|
|
14
|
-
#
|
|
15
|
-
# get('/hello') { 'Hello World!' }
|
|
16
|
-
# end
|
|
17
|
-
#
|
|
18
|
-
# super(app, service)
|
|
18
|
+
# super(HelloHTTPService.new, service)
|
|
19
19
|
# end
|
|
20
20
|
# end
|
|
21
21
|
#
|
|
@@ -34,18 +34,18 @@ module Nonnative
|
|
|
34
34
|
#
|
|
35
35
|
# @see Nonnative::Server
|
|
36
36
|
class HTTPServer < Nonnative::Server
|
|
37
|
-
# Creates a Puma server for the given
|
|
37
|
+
# Creates a Puma server for the given HTTP service and runner configuration.
|
|
38
38
|
#
|
|
39
|
-
# @param
|
|
39
|
+
# @param http_service [#call] an HTTP service instance
|
|
40
40
|
# @param service [Nonnative::ConfigurationServer] server configuration
|
|
41
|
-
def initialize(
|
|
41
|
+
def initialize(http_service, service)
|
|
42
42
|
# Keep the log IO so the server lifecycle can release Puma's file handle on stop.
|
|
43
43
|
@log = File.open(service.log, 'a')
|
|
44
44
|
options = {
|
|
45
45
|
log_writer: Puma::LogWriter.new(log, log),
|
|
46
46
|
force_shutdown_after: service.timeout
|
|
47
47
|
}
|
|
48
|
-
@server = Puma::Server.new(
|
|
48
|
+
@server = Puma::Server.new(http_service, Puma::Events.new, options)
|
|
49
49
|
|
|
50
50
|
super(service)
|
|
51
51
|
end
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Nonnative
|
|
4
|
+
# HTTP service run by {Nonnative::HTTPServer}.
|
|
5
|
+
#
|
|
6
|
+
# Subclass this instead of `Sinatra::Application` when defining an in-process HTTP server for
|
|
7
|
+
# Nonnative. {Nonnative::HTTPServer} accepts a service instance and runs it under Puma.
|
|
8
|
+
class HTTPService < Sinatra::Application
|
|
9
|
+
end
|
|
10
|
+
end
|
data/lib/nonnative/proxy.rb
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
module Nonnative
|
|
4
|
-
#
|
|
4
|
+
# Parent type for proxy implementations.
|
|
5
5
|
#
|
|
6
6
|
# A proxy is responsible for interposing behavior between a client and a target service.
|
|
7
7
|
# Runtime services create a proxy instance via {Nonnative::ProxyFactory} based on `service.proxy.kind`.
|
|
@@ -16,6 +16,7 @@ module Nonnative
|
|
|
16
16
|
# @see Nonnative::SocketPairFactory
|
|
17
17
|
# @see Nonnative::CloseAllSocketPair
|
|
18
18
|
# @see Nonnative::DelaySocketPair
|
|
19
|
+
# @see Nonnative::TimeoutSocketPair
|
|
19
20
|
# @see Nonnative::InvalidDataSocketPair
|
|
20
21
|
class SocketPair
|
|
21
22
|
# @param proxy [#host, #port, #options] proxy configuration used to connect upstream
|
|
@@ -10,18 +10,20 @@ module Nonnative
|
|
|
10
10
|
# - `:none` (or any unknown value) -> {Nonnative::SocketPair} (pass-through)
|
|
11
11
|
# - `:close_all` -> {Nonnative::CloseAllSocketPair}
|
|
12
12
|
# - `:delay` -> {Nonnative::DelaySocketPair}
|
|
13
|
+
# - `:timeout` -> {Nonnative::TimeoutSocketPair}
|
|
13
14
|
# - `:invalid_data` -> {Nonnative::InvalidDataSocketPair}
|
|
14
15
|
#
|
|
15
16
|
# @see Nonnative::FaultInjectionProxy
|
|
16
17
|
# @see Nonnative::SocketPair
|
|
17
18
|
# @see Nonnative::CloseAllSocketPair
|
|
18
19
|
# @see Nonnative::DelaySocketPair
|
|
20
|
+
# @see Nonnative::TimeoutSocketPair
|
|
19
21
|
# @see Nonnative::InvalidDataSocketPair
|
|
20
22
|
class SocketPairFactory
|
|
21
23
|
class << self
|
|
22
24
|
# Creates a socket-pair instance for the given proxy state.
|
|
23
25
|
#
|
|
24
|
-
# @param kind [Symbol] proxy state (e.g. `:none`, `:close_all`, `:delay`, `:invalid_data`)
|
|
26
|
+
# @param kind [Symbol] proxy state (e.g. `:none`, `:close_all`, `:delay`, `:timeout`, `:invalid_data`)
|
|
25
27
|
# @param proxy [Nonnative::ConfigurationProxy] proxy configuration (host/port/options)
|
|
26
28
|
# @return [Nonnative::SocketPair] a socket-pair implementation instance
|
|
27
29
|
def create(kind, proxy)
|
|
@@ -30,6 +32,8 @@ module Nonnative
|
|
|
30
32
|
CloseAllSocketPair
|
|
31
33
|
when :delay
|
|
32
34
|
DelaySocketPair
|
|
35
|
+
when :timeout
|
|
36
|
+
TimeoutSocketPair
|
|
33
37
|
when :invalid_data
|
|
34
38
|
InvalidDataSocketPair
|
|
35
39
|
else
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Nonnative
|
|
4
|
+
# Socket-pair variant used by the fault-injection proxy to simulate read timeouts.
|
|
5
|
+
#
|
|
6
|
+
# When active, the proxy accepts the client connection and keeps it open without forwarding bytes.
|
|
7
|
+
# Clients with read deadlines should observe their own timeout behavior.
|
|
8
|
+
#
|
|
9
|
+
# This behavior is enabled by calling {Nonnative::FaultInjectionProxy#timeout}.
|
|
10
|
+
#
|
|
11
|
+
# @see Nonnative::FaultInjectionProxy
|
|
12
|
+
# @see Nonnative::SocketPairFactory
|
|
13
|
+
# @see Nonnative::SocketPair
|
|
14
|
+
class TimeoutSocketPair < SocketPair
|
|
15
|
+
# Keeps the accepted socket silent until reset or stop closes active proxy connections.
|
|
16
|
+
#
|
|
17
|
+
# @param local_socket [TCPSocket] the accepted client socket
|
|
18
|
+
# @return [void]
|
|
19
|
+
def connect(local_socket)
|
|
20
|
+
@local_socket = local_socket
|
|
21
|
+
|
|
22
|
+
Nonnative.logger.info "stalling socket '#{local_socket.inspect}' for 'timeout' pair"
|
|
23
|
+
|
|
24
|
+
# Do not call super: the base implementation opens the upstream socket and forwards traffic.
|
|
25
|
+
sleep 0.1 until local_socket.closed?
|
|
26
|
+
ensure
|
|
27
|
+
close
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
data/lib/nonnative/version.rb
CHANGED
data/lib/nonnative.rb
CHANGED
|
@@ -91,6 +91,7 @@ require 'nonnative/tcp_probe'
|
|
|
91
91
|
require 'nonnative/http_probe'
|
|
92
92
|
require 'nonnative/grpc_health'
|
|
93
93
|
require 'nonnative/grpc_probe'
|
|
94
|
+
require 'nonnative/http_service'
|
|
94
95
|
require 'nonnative/http_server'
|
|
95
96
|
require 'nonnative/http_proxy_server'
|
|
96
97
|
require 'nonnative/grpc_server'
|
|
@@ -102,6 +103,7 @@ require 'nonnative/fault_injection_proxy'
|
|
|
102
103
|
require 'nonnative/socket_pair'
|
|
103
104
|
require 'nonnative/close_all_socket_pair'
|
|
104
105
|
require 'nonnative/delay_socket_pair'
|
|
106
|
+
require 'nonnative/timeout_socket_pair'
|
|
105
107
|
require 'nonnative/invalid_data_socket_pair'
|
|
106
108
|
require 'nonnative/socket_pair_factory'
|
|
107
109
|
require 'nonnative/go_executable'
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: nonnative
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 3.
|
|
4
|
+
version: 3.15.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Alejandro Falkowski
|
|
@@ -310,6 +310,7 @@ files:
|
|
|
310
310
|
- lib/nonnative/http_probe.rb
|
|
311
311
|
- lib/nonnative/http_proxy_server.rb
|
|
312
312
|
- lib/nonnative/http_server.rb
|
|
313
|
+
- lib/nonnative/http_service.rb
|
|
313
314
|
- lib/nonnative/invalid_data_socket_pair.rb
|
|
314
315
|
- lib/nonnative/no_proxy.rb
|
|
315
316
|
- lib/nonnative/not_found_error.rb
|
|
@@ -330,6 +331,7 @@ files:
|
|
|
330
331
|
- lib/nonnative/stop_error.rb
|
|
331
332
|
- lib/nonnative/tcp_probe.rb
|
|
332
333
|
- lib/nonnative/timeout.rb
|
|
334
|
+
- lib/nonnative/timeout_socket_pair.rb
|
|
333
335
|
- lib/nonnative/version.rb
|
|
334
336
|
- nonnative.gemspec
|
|
335
337
|
homepage: https://github.com/alexfalkowski/nonnative
|