Update jsonrpc4net
[CSharpForFun/.git] / jsonrpc4net / jsonrpc4net / JsonRpcHttpAsyncClient.cs
1 using Newtonsoft.Json;
2 using Newtonsoft.Json.Linq;
3 using NLog;
4 using System;
5 using System.Collections.Generic;
6 using System.Net.Http;
7 using System.Threading;
8 using System.Threading.Tasks;
9 using System.IO;
10 using System.Text;
11
12 namespace GumartinM.JsonRPC4NET
13 {
14         /// <summary>
15         /// Json rpc http async client.
16         /// </summary>
17     public class JsonRpcHttpAsyncClient
18     {
19         /// <summary>
20         /// RPC call id.
21         /// </summary>
22         private long _nextId;
23
24         /// <summary>
25         /// The logger.
26         /// </summary>
27         private static readonly Logger _logger = LogManager.GetCurrentClassLogger();
28
29         /// <summary>
30         /// The _json settings.
31         /// </summary>
32         private readonly JsonSerializerSettings _jsonSettings =
33           new JsonSerializerSettings
34           {
35             Error = delegate(object sender, Newtonsoft.Json.Serialization.ErrorEventArgs args)
36               {
37                   _logger.Error(args.ErrorContext.Error.Message);
38                   args.ErrorContext.Handled = true;
39               }
40           };
41
42         /// <summary>
43         /// The _exception resolver.
44         /// </summary>
45         private readonly ExceptionResolver _exceptionResolver = new ExceptionResolver();
46
47         /// <summary>
48         /// The JSON RPC version.
49         /// </summary>
50         private readonly string _JSON_RPC_VERSION = "2.0";
51
52
53
54         /// <summary>
55         /// Posts the with parameters remote service async.
56         /// </summary>
57         /// <returns>The with parameters remote service async.</returns>
58         /// <param name="uri">URI.</param>
59         /// <param name="method">Method.</param>
60         /// <param name="arguments">Arguments.</param>
61         /// <typeparam name="TResult">The 1st type parameter.</typeparam>
62         async public Task<TResult> PostRemoteServiceAsync<TResult>(string uri, string method, params object[] arguments)
63         {
64             string jsonData;
65
66             if (arguments != null)
67             {
68                 var inputParameters = new List<object>(arguments);
69                 var postData = new POSTWithParameters();
70                 postData.id = Interlocked.Increment(ref _nextId).ToString();
71                 postData.jsonrpc = _JSON_RPC_VERSION;
72                 postData.method = method;
73                 postData.@params = inputParameters;
74                 jsonData = JsonConvert.SerializeObject(postData, _jsonSettings);
75             } else
76             {
77                 var postData = new POST();
78                 postData.id = Interlocked.Increment(ref _nextId).ToString();
79                 postData.jsonrpc = _JSON_RPC_VERSION;
80                 postData.method = method;
81                 jsonData = JsonConvert.SerializeObject(postData, _jsonSettings);
82             }
83
84             POSTResult<TResult> postResult = await this.PostAsync<TResult>(uri, method, jsonData, CancellationToken.None).ConfigureAwait(false);
85
86             return postResult.result;
87         }
88
89         /// <summary>
90         /// Posts the with parameters remote service async.
91         /// </summary>
92         /// <returns>The with parameters remote service async.</returns>
93         /// <param name="uri">URI.</param>
94         /// <param name="method">Method.</param>
95         /// <param name="parameters">Parameters.</param>
96         async public Task PostRemoteServiceAsync(string uri, string method, params object[] parameters)
97         {
98             await this.PostRemoteServiceAsync<object>(uri, method, parameters).ConfigureAwait(false);
99         }
100
101         /// <summary>
102         /// Posts the async.
103         /// </summary>
104         /// <returns>The async.</returns>
105         /// <param name="uri">URI.</param>
106         /// <param name="method">Method.</param>
107                 /// <param name="jsonData">JsonData.</param>
108         /// <param name="cancellation">Cancellation.</param>
109         /// <typeparam name="TResult">The 1st type parameter.</typeparam>
110         async private Task<POSTResult<TResult>> PostAsync<TResult>(string uri, string method, string jsonData, CancellationToken cancellation)
111         {
112             // see: http://stackoverflow.com/questions/1329739/nested-using-statements-in-c-sharp
113             // see: http://stackoverflow.com/questions/5895879/when-do-we-need-to-call-dispose-in-dot-net-c
114             using (HttpClient client = new HttpClient { Timeout = TimeSpan.FromSeconds(5) })
115             using (HttpContent contentPOST = new StringContent(jsonData, System.Text.Encoding.UTF8, "application/json-rpc"))
116             // TODO: HttpCompletionOption, without it, by default, I am buffering the received data.
117             // ConfigureAwait(false): This API will always return data in a different context (different thread) to the calling one.
118             // In this way upper layers may decide what context to use for returning data (the calling context or a diferent one)
119             using (HttpResponseMessage response = await client.PostAsync(uri, contentPOST, cancellation).ConfigureAwait(false))
120             {
121                 //TODO: What if response is null? :(
122                 response.EnsureSuccessStatusCode();
123
124                 using (HttpContent contentRESULT = response.Content)
125                 {
126                     //TODO: What if contentRESULT is null? :(
127                     return await this.ReadResponseAsync<TResult>(contentRESULT).ConfigureAwait(false);
128                 }
129             }
130         }
131
132                 /// <summary>
133                 /// Reads the response async.
134                 /// </summary>
135                 /// <returns>The response async.</returns>
136                 /// <param name="content">Content.</param>
137                 /// <typeparam name="TResult">The 1st type parameter.</typeparam>
138         async private Task<POSTResult<TResult>> ReadResponseAsync<TResult>(HttpContent content)
139         {
140             // Taken from HttpContent.cs ReadAsStringAsync() Mono implementation.
141             Encoding encoding;
142             if (content.Headers != null && content.Headers.ContentType != null && content.Headers.ContentType.CharSet != null)
143             {
144                 encoding = Encoding.GetEncoding(content.Headers.ContentType.CharSet);
145             }
146             else
147             {
148                 encoding = Encoding.UTF8;
149             }
150
151             // Option a) with bytes
152             //byte[] jsonBytes = await contentRESULT.ReadAsByteArrayAsync();
153             //return this.ReadResponse<TResult>(jsonBytes);
154
155             // Option b) with stream
156             using (Stream stream = await content.ReadAsStreamAsync().ConfigureAwait(false))
157             using (StreamReader streamReader = new StreamReader(stream, encoding))
158             {
159                 // This line makes this method useless (IMHO it is the same as the one working with bytes)
160                 // How could I work with JSON saving memory?
161                 string json = await streamReader.ReadToEndAsync().ConfigureAwait(false);
162
163                 return this.ReadResponse<TResult>(json);
164             }
165         }
166
167         /// <summary>
168         /// Reads the response.
169         /// </summary>
170         /// <returns>The response.</returns>
171         /// <param name="jsonBytes">Json bytes.</param>
172         /// <typeparam name="TResult">The 1st type parameter.</typeparam>
173         private POSTResult<TResult> ReadResponse<TResult>(byte[] jsonBytes)
174         {
175             string json = System.Text.Encoding.UTF8.GetString(jsonBytes, 0, jsonBytes.Length);
176
177             return this.ReadResponse<TResult>(json);
178         }
179
180         private POSTResult<TResult> ReadResponse<TResult>(string json)
181         {
182             JObject jsonObjects = JObject.Parse(json);
183             IDictionary<string, JToken> jsonTokens = jsonObjects;
184
185
186             if (jsonTokens.ContainsKey("error"))
187             {
188                 throw _exceptionResolver.ResolveException(jsonObjects["error"]);
189             }
190
191             if (jsonTokens.ContainsKey("result"))
192             {
193                 return JsonConvert.DeserializeObject<POSTResult<TResult>>(json, _jsonSettings);
194             }
195
196             throw new JsonRpcClientException(0, "There is not error nor result in JSON response data.", jsonObjects);
197         }
198
199         private class POST
200         {
201             public string id { get; set; }
202             public string jsonrpc { get; set; }
203             public string method { get; set; }
204         }
205
206         private class POSTWithParameters
207         {
208             public string id { get; set; }
209             public string jsonrpc { get; set; }
210             public string method { get; set; }
211             public List<object> @params { get; set; }
212         }
213
214         private class POSTResult<TResult>
215         {
216             public string id { get; set; }
217             public string jsonrpc { get; set; }
218             public TResult result { get; set; }
219         }
220     }
221 }