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; }
}
}
}