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