using Newtonsoft.Json; using Newtonsoft.Json.Linq; using NLog; using System; using System.Collections.Generic; using System.Net.Http; using System.Threading; using System.Threading.Tasks; using System.IO; using System.Text; namespace GumartinM.JsonRPC4NET { /// /// Json rpc http async client. /// public class JsonRpcHttpAsyncClient { /// /// RPC call id. /// private long _nextId; /// /// The logger. /// private static readonly Logger _logger = LogManager.GetCurrentClassLogger(); /// /// The _json settings. /// private readonly JsonSerializerSettings _jsonSettings = new JsonSerializerSettings { Error = delegate(object sender, Newtonsoft.Json.Serialization.ErrorEventArgs args) { _logger.Error(args.ErrorContext.Error.Message); args.ErrorContext.Handled = true; } }; /// /// The _exception resolver. /// private readonly ExceptionResolver _exceptionResolver = new ExceptionResolver(); /// /// The JSON RPC version. /// private readonly string _JSON_RPC_VERSION = "2.0"; /// /// Posts the with parameters remote service async. /// /// The with parameters remote service async. /// URI. /// Method. /// Arguments. /// The 1st type parameter. async public Task PostRemoteServiceAsync(string uri, string method, params object[] arguments) { string jsonData; if (arguments != null) { var inputParameters = new List(arguments); var postData = new POSTWithParameters(); postData.id = Interlocked.Increment(ref _nextId).ToString(); postData.jsonrpc = _JSON_RPC_VERSION; postData.method = method; postData.@params = inputParameters; jsonData = JsonConvert.SerializeObject(postData, _jsonSettings); } else { var postData = new POST(); postData.id = Interlocked.Increment(ref _nextId).ToString(); postData.jsonrpc = _JSON_RPC_VERSION; postData.method = method; jsonData = JsonConvert.SerializeObject(postData, _jsonSettings); } POSTResult postResult = await this.PostAsync(uri, method, jsonData, CancellationToken.None).ConfigureAwait(false); return postResult.result; } /// /// Posts the with parameters remote service async. /// /// The with parameters remote service async. /// URI. /// Method. /// Parameters. async public Task PostRemoteServiceAsync(string uri, string method, params object[] parameters) { await this.PostRemoteServiceAsync(uri, method, parameters).ConfigureAwait(false); } /// /// Posts the async. /// /// The async. /// URI. /// Method. /// JsonData. /// Cancellation. /// The 1st type parameter. async private Task> PostAsync(string uri, string method, string jsonData, CancellationToken cancellation) { // see: http://stackoverflow.com/questions/1329739/nested-using-statements-in-c-sharp // see: http://stackoverflow.com/questions/5895879/when-do-we-need-to-call-dispose-in-dot-net-c using (HttpClient client = new HttpClient { Timeout = TimeSpan.FromSeconds(5) }) using (HttpContent contentPOST = new StringContent(jsonData, System.Text.Encoding.UTF8, "application/json-rpc")) // TODO: HttpCompletionOption, without it, by default, I am buffering the received data. // ConfigureAwait(false): This API will always return data in a different context (different thread) to the calling one. // In this way upper layers may decide what context to use for returning data (the calling context or a diferent one) using (HttpResponseMessage response = await client.PostAsync(uri, contentPOST, cancellation).ConfigureAwait(false)) { //TODO: What if response is null? :( response.EnsureSuccessStatusCode(); using (HttpContent contentRESULT = response.Content) { //TODO: What if contentRESULT is null? :( return await this.ReadResponseAsync(contentRESULT).ConfigureAwait(false); } } } /// /// Reads the response async. /// /// The response async. /// Content. /// The 1st type parameter. async private Task> ReadResponseAsync(HttpContent content) { // Taken from HttpContent.cs ReadAsStringAsync() Mono implementation. Encoding encoding; if (content.Headers != null && content.Headers.ContentType != null && content.Headers.ContentType.CharSet != null) { encoding = Encoding.GetEncoding(content.Headers.ContentType.CharSet); } else { encoding = Encoding.UTF8; } // Option a) with bytes //byte[] jsonBytes = await contentRESULT.ReadAsByteArrayAsync(); //return this.ReadResponse(jsonBytes); // Option b) with stream using (Stream stream = await content.ReadAsStreamAsync().ConfigureAwait(false)) using (StreamReader streamReader = new StreamReader(stream, encoding)) { // This line makes this method useless (IMHO it is the same as the one working with bytes) // How could I work with JSON saving memory? string json = await streamReader.ReadToEndAsync().ConfigureAwait(false); return this.ReadResponse(json); } } /// /// Reads the response. /// /// The response. /// Json bytes. /// The 1st type parameter. private POSTResult ReadResponse(byte[] jsonBytes) { string json = System.Text.Encoding.UTF8.GetString(jsonBytes, 0, jsonBytes.Length); return this.ReadResponse(json); } private POSTResult ReadResponse(string json) { JObject jsonObjects = JObject.Parse(json); IDictionary jsonTokens = jsonObjects; if (jsonTokens.ContainsKey("error")) { throw _exceptionResolver.ResolveException(jsonObjects["error"]); } if (jsonTokens.ContainsKey("result")) { return JsonConvert.DeserializeObject>(json, _jsonSettings); } throw new JsonRpcClientException(0, "There is not error nor result in JSON response data.", jsonObjects); } private class POST { public string id { get; set; } public string jsonrpc { get; set; } public string method { get; set; } } private class POSTWithParameters { public string id { get; set; } public string jsonrpc { get; set; } public string method { get; set; } public List @params { get; set; } } private class POSTResult { public string id { get; set; } public string jsonrpc { get; set; } public TResult result { get; set; } } } }