[REQ] [RUBY] Add support for streaming responses to ruby clients
Created by: zhulik
Is your feature request related to a problem? Please describe.
I'm currently working on a kubernetes API client for ruby using Faraday and I'd like to implement support for watching for changes. On kubernetes side this is basically implemented via streaming.
Both supported by openapi-generator http clients have support for streaming:
Describe the solution you'd like
Python's client for kubernetes which is also created with help of openapi-generator provides an API to make it possible:
from kubernetes import client, config, watch
# Configs can be set in Configuration class directly or using helper utility
config.load_kube_config()
v1 = client.CoreV1Api()
count = 10
w = watch.Watch()
for event in w.stream(v1.list_namespace, _request_timeout=60):
print("Event: %s %s" % (event['type'], event['object'].metadata.name))
count -= 1
if not count:
w.stop()
print("Ended.")
I've checked the generated ruby source code and couldn't find a way to hook into the request process and define a custom options.on_data
callback (in case of Faraday). I assume it can be done somewhere here. The Api::Client#call_api
method could receive a block and call it right after build_request
. In this case all generated methods that call Api::Client#call_api
should also receive a block and pass it to Api::Client#call_api
. It will make it possible to hook into every single request made by the client and give enough flexibility to implement streaming. It will also be fully backwards compatible. Something like
Generated Api client:
def call_api(http_method, path, opts = {}, &block)
begin
response = connection(opts).public_send(http_method.to_sym.downcase) do |req|
build_request(http_method, path, req, opts)
block.call(http_method, path, req, opts)
end
...
end
Generated client for CoreV1Api:
def list_core_v1_namespace(opts = {}, &block)
data, _status_code, _headers = list_core_v1_namespace_with_http_info(opts, &block)
data
end
def list_core_v1_namespace_with_http_info(opts = {}, &block)
...
data, status_code, headers = @api_client.call_api(:GET, local_var_path, new_options, &block)
...
end
Client code, somewhat similar to python's example
v1 = CoreV1Api.new
w = Watcher.new
w.stream(v1, :list_core_v1_namespace) do |event|
pp(event)
end
Watcher
under the hood will call CoreV1Api#list_core_v1_namespace
with modified arguments, pass a block into it and hook into the request process to receive the streamed response, parse it, wrap it into objects and yield it to the client.
Describe alternatives you've considered
With raw Faraday this can be achieved this way
conn = Faraday.new("http://127.0.0.1:8001") # kubectl proxy for simplicity
# inspired by https://github.com/kubernetes-client/python/blob/release-26.0/kubernetes/base/watch/watch.py#L54
prev = ""
conn.get("/api/v1/namespaces", { watch: 1 }) do |req|
req.options.on_data = proc do |seg, _overall_received_bytes, _env|
prev += seg
lines = prev.split("\n")
if seg.end_with?("\n")
prev = ""
else
*lines, prev = lines
end
lines.each do |line|
pp(JSON.parse(line, symbolize_names: true))
end
end
end