nonnative 3.35.0 → 3.36.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/README.md +4 -2
- data/lib/nonnative/http_proxy_server.rb +53 -8
- data/lib/nonnative/version.rb +1 -1
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 6969c5c21e416e67752d3421af108506fe44730230537c9b426529e28dad7934
|
|
4
|
+
data.tar.gz: 148ab5d39ef4f367501cca5786de14bc50be175616048a19a792671eee27fc1a
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 85218dfe59a108dad82130912aaaa1bb0a79e60017595fd089383aafc005e4b9c9e72c74341cd38efc97ac2f31dbf460967cb2dc4eb2fcb34c565ad663b12e41
|
|
7
|
+
data.tar.gz: 8125a4ef344c00e450f8b03df400cbe2df991f8e2905420673fa2fbd09032c1623f52b540c92cf7ab08c60967b7dfb1d9239c68fcf737341e97c21e1feffa10a
|
data/README.md
CHANGED
|
@@ -602,11 +602,13 @@ The system allows you to define an in-process HTTP forward proxy server for exte
|
|
|
602
602
|
The upstream scheme defaults to HTTPS on the scheme's default port; pass `scheme:`/`port:` to
|
|
603
603
|
`Nonnative::HTTPProxyServer.new` to target an `http://` upstream or a non-default port. The proxy
|
|
604
604
|
forwards the request path and query for `GET`, `HEAD`, `POST`, `PUT`, `PATCH`, `DELETE`, and
|
|
605
|
-
`OPTIONS`, while removing proxy credentials
|
|
606
|
-
request.
|
|
605
|
+
`OPTIONS`, while removing proxy credentials, `Host`, `Accept-Encoding`, hop-by-hop headers, and
|
|
606
|
+
headers nominated by `Connection` before the upstream request.
|
|
607
607
|
|
|
608
608
|
The proxy preserves the upstream status, body, and safe end-to-end response headers such as `Content-Type`, `ETag`, and application-specific metadata. It removes hop-by-hop, connection-nominated, proxy-authentication, and framing headers; `Set-Cookie`, `Location`, and `Content-Encoding` are not forwarded.
|
|
609
609
|
|
|
610
|
+
When the upstream is unavailable, disconnects unexpectedly, or times out, the proxy returns a clean gateway response (`502` or `504`) instead of exposing an internal exception page.
|
|
611
|
+
|
|
610
612
|
Define your server:
|
|
611
613
|
|
|
612
614
|
```ruby
|
|
@@ -44,13 +44,35 @@ module Nonnative
|
|
|
44
44
|
].freeze
|
|
45
45
|
|
|
46
46
|
NON_FORWARDABLE_REQUEST_HEADERS = %w[
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
47
|
+
host
|
|
48
|
+
accept-encoding
|
|
49
|
+
version
|
|
50
|
+
proxy-authenticate
|
|
51
|
+
proxy-authorization
|
|
52
|
+
proxy-connection
|
|
53
|
+
connection
|
|
54
|
+
keep-alive
|
|
55
|
+
te
|
|
56
|
+
trailer
|
|
57
|
+
transfer-encoding
|
|
58
|
+
upgrade
|
|
59
|
+
].freeze
|
|
60
|
+
|
|
61
|
+
GATEWAY_TIMEOUT_ERRORS = [
|
|
62
|
+
RestClient::Exceptions::Timeout
|
|
52
63
|
].freeze
|
|
53
64
|
|
|
65
|
+
GATEWAY_CONNECTION_ERRORS = [
|
|
66
|
+
RestClient::ServerBrokeConnection,
|
|
67
|
+
Errno::ECONNREFUSED,
|
|
68
|
+
Errno::EHOSTUNREACH,
|
|
69
|
+
SocketError
|
|
70
|
+
].freeze
|
|
71
|
+
|
|
72
|
+
# Match malformed percent escapes and bytes outside RFC 3986's path-safe set. Valid `%HH`
|
|
73
|
+
# escapes remain intact; each match is percent-encoded before building the upstream URI.
|
|
74
|
+
PATH_ESCAPE_PATTERN = %r{%(?![\da-fA-F]{2})|[^A-Za-z0-9\-._~!$&'()*+,;=:@/%]}
|
|
75
|
+
|
|
54
76
|
# Extracts request headers from the Rack environment and normalizes them to standard HTTP names.
|
|
55
77
|
#
|
|
56
78
|
# Certain hop-by-hop or proxy-specific headers are removed.
|
|
@@ -64,7 +86,8 @@ module Nonnative
|
|
|
64
86
|
result[normalized_request_header_name(header)] = value
|
|
65
87
|
end
|
|
66
88
|
|
|
67
|
-
headers
|
|
89
|
+
connection_headers = request_connection_headers(headers)
|
|
90
|
+
headers.reject { |header, _| non_forwardable_request_header?(header, connection_headers) }
|
|
68
91
|
end
|
|
69
92
|
|
|
70
93
|
# Builds the upstream URL for the given request.
|
|
@@ -75,8 +98,9 @@ module Nonnative
|
|
|
75
98
|
def build_url(request, settings)
|
|
76
99
|
uri_class = settings.scheme == 'http' ? URI::HTTP : URI::HTTPS
|
|
77
100
|
query = request.query_string
|
|
101
|
+
path = escape_path(request.path_info)
|
|
78
102
|
|
|
79
|
-
uri_class.build(host: settings.host, port: settings.port, path
|
|
103
|
+
uri_class.build(host: settings.host, port: settings.port, path:, query: query.empty? ? nil : query).to_s
|
|
80
104
|
end
|
|
81
105
|
|
|
82
106
|
# Executes the upstream request and returns the response.
|
|
@@ -86,13 +110,20 @@ module Nonnative
|
|
|
86
110
|
# @param headers [Hash] request headers
|
|
87
111
|
# @param payload [String, nil] request payload
|
|
88
112
|
# @return [RestClient::Response] response for error statuses, otherwise RestClient return value
|
|
113
|
+
# @raise [Sinatra::Halt] with a gateway status when the upstream is unavailable or times out
|
|
89
114
|
def api_response(method:, url:, headers:, payload: nil)
|
|
90
115
|
options = { method:, url:, headers: }
|
|
91
116
|
options[:payload] = payload unless payload.nil?
|
|
92
117
|
|
|
93
118
|
RestClient::Request.execute(options)
|
|
119
|
+
rescue *GATEWAY_TIMEOUT_ERRORS
|
|
120
|
+
halt 504
|
|
121
|
+
rescue *GATEWAY_CONNECTION_ERRORS
|
|
122
|
+
halt 502
|
|
94
123
|
rescue RestClient::Exception => e
|
|
95
|
-
e.response
|
|
124
|
+
return e.response if e.response
|
|
125
|
+
|
|
126
|
+
halt 502
|
|
96
127
|
end
|
|
97
128
|
|
|
98
129
|
# Extracts the request payload for verbs that can carry a body.
|
|
@@ -149,6 +180,20 @@ module Nonnative
|
|
|
149
180
|
header.delete_prefix('HTTP_').split('_').map(&:capitalize).join('-')
|
|
150
181
|
end
|
|
151
182
|
|
|
183
|
+
def escape_path(path)
|
|
184
|
+
URI::DEFAULT_PARSER.escape(path, PATH_ESCAPE_PATTERN)
|
|
185
|
+
end
|
|
186
|
+
|
|
187
|
+
def request_connection_headers(headers)
|
|
188
|
+
headers.fetch('Connection', '').split(',').map { |header| header.strip.downcase }
|
|
189
|
+
end
|
|
190
|
+
|
|
191
|
+
def non_forwardable_request_header?(header, connection_headers)
|
|
192
|
+
normalized_header = header.downcase
|
|
193
|
+
|
|
194
|
+
NON_FORWARDABLE_REQUEST_HEADERS.include?(normalized_header) || connection_headers.include?(normalized_header)
|
|
195
|
+
end
|
|
196
|
+
|
|
152
197
|
# Registered before `get` so it takes precedence over Sinatra's GET-generated HEAD route,
|
|
153
198
|
# which would otherwise forward HEAD requests upstream as GET.
|
|
154
199
|
head(/.*/) do
|
data/lib/nonnative/version.rb
CHANGED