Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

NDJSON stream with an AutoRest generated C# ServiceClient #2650

Closed
markns opened this issue Oct 10, 2017 · 3 comments
Closed

NDJSON stream with an AutoRest generated C# ServiceClient #2650

markns opened this issue Oct 10, 2017 · 3 comments

Comments

@markns
Copy link

markns commented Oct 10, 2017

Using AutoRest, I've generated a C# ServiceClient for a service that supports long polling type operations, by streaming NDJSON events.

The swagger document for the service defines a produces element in the path:

"/apis/thisisastreamingendpoint": {
    produces: ["application/json;stream=watch"]

Is there anything in the AutoRest configuration that will allow to generate a client that will support this streaming behaviour?

@fearthecowboy
Copy link
Member

Not at the moment.

We've got a pattern for Long running operations when we generate code for Azure-ARM operations, but it's nothing like what you're describing there.

We are just starting discussions about expanding how we support patterns along LROs; I can't promise anything quickly, but I'd be interested in learning more about what you think it should do in your case. Perhaps it would help us to flesh it out more.

@markns
Copy link
Author

markns commented Oct 11, 2017

I've managed to hack something together that works for my purposes (watching a Kubernetes endpoint). I took a leaf out of the Angular http services book, and return an Observable from the service call.

public static async Task<HttpOperationResponse<IObservable<WatchEvent<V1alpha1App>>>>
    WatchNamespacedAppWithHttpMessagesAsync(
        this Namespaceapiserver operations, string namespaceParameter
        CancellationToken cancellationToken = default(CancellationToken))
{
.....
         // Send Request
        _httpResponse = await operations.HttpClient.SendAsync(_httpRequest,
            HttpCompletionOption.ResponseHeadersRead,
            cancellationToken).ConfigureAwait(false);
....
       // Deserialize Response
        if ((int) _statusCode == 200)
        {
            var stream = await _httpResponse.Content.ReadAsStreamAsync();

            _result.Body = Observable.Create<WatchEvent<V1alpha1App>>(o =>
            {
                try
                {
                    using (var reader = new StreamReader(stream))
                        while (!reader.EndOfStream)
                        {
                            var responseLine = reader.ReadLine();
                            var watchEvent =
                                JsonConvert.DeserializeObject<WatchEvent<V1alpha1App>>(responseLine,
                                    operations.DeserializationSettings);

                            o.OnNext(watchEvent);
                        }
                }
                catch (JsonException ex)
                {
                    _httpRequest.Dispose();
                    if (_httpResponse != null)
                    {
                        _httpResponse.Dispose();
                    }
                    throw new SerializationException("Unable to deserialize the response.", _responseContent, ex);
                }
                catch (Exception ex)
                {
                    o.OnError(ex);
                }
                finally
                {
                    o.OnCompleted();
                }
                return Disposable.Empty;
            });
        if (_shouldTrace)
        {
            ServiceClientTracing.Exit(_invocationId, _result);
        }
        return _result;
    }
}

public class WatchEvent<T>
{
    [JsonProperty(PropertyName = "type")]
    public string Type { get; }
    [JsonProperty(PropertyName = "object")]
    public T Object { get; }

    public WatchEvent(string type, T @object)
    {
        this.Type = type;
        this.Object = @object;
    }
}

I guess it's many orders of magnitude harder to figure out how to make this generic enough to be generated from a swagger specification (even presuming that you'd be happy to add the dependency on Reactive Extensions). I guess the first step would be adding a delegate function to handle the deserialisation though.

I think I'm happy with my hack for now though, please feel free to close the issue if you wish.

@olydis
Copy link
Contributor

olydis commented Oct 27, 2017

added the issue to our rollup of "future media type considerations" 🙂

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants