private async void MakeRequest <T>(string verb, string path, IDictionary <string, object> parameters, Action <Exception, HttpWebResponse> callback, bool skipAuth) { if (!path.StartsWith("/")) { path = "/" + path; } // get the token before generating the request url, as there may be a new ServiceRoot var token = skipAuth ? null : await Client.GetAccessToken(); var url = String.Format(CultureInfo.InvariantCulture, "{0}{1}", ServiceRoot, path); var requestType = HttpRequestType.HttpPostJson; IEnumerable <KeyValuePair <string, object> > files = null; switch (verb.ToUpperInvariant()) { case "GET": // For redirects (specifically Azure Blob), if our authentication header is in there // it'll deny us access. So for that case, we just need to add the access token to the parameters // collection so it doesn't get added as a header. // if (typeof(T) == typeof(BuddyFile) && !parameters.ContainsKey("accessToken")) { parameters["accessToken"] = token; token = null; } url += "?" + GetUrlEncodedParameters(parameters); requestType = HttpRequestType.HttpGet; break; default: // do we have any file parameters. // files = from p in parameters where p.Value is BuddyFile select p; if (files.Any()) { // remove the files from the main array. requestType = HttpRequestType.HttpPostMultipartForm; var newParameters = new Dictionary <string, object>(); foreach (var fileKvp in files) { newParameters.Add(fileKvp.Key, fileKvp.Value); } foreach (var kvp in newParameters) { parameters.Remove(kvp.Key); } // get json for the remainder and make it into a file var json = JsonConvert.SerializeObject(parameters, Formatting.None); var jsonFile = new BuddyFile(new MemoryStream(System.Text.Encoding.UTF8.GetBytes(json)), "body", "application/json"); newParameters.Add(jsonFile.Name, jsonFile); parameters = newParameters; } break; } HttpWebRequest wr = null; try { wr = (HttpWebRequest)WebRequest.Create(url); } catch (Exception ex) { callback(ex, null); return; } wr.Headers["BuddyPlatformSDK"] = SdkVersion; if (token != null && (parameters == null || !parameters.ContainsKey("accessToken"))) { if (SharedSecret != null) { string requestSig = GenerateSignatureForRequest(verb, path); if (requestSig != null) { wr.Headers["Authorization"] = String.Format("Buddy {0} {1}", token, requestSig); } } else { wr.Headers["Authorization"] = String.Format("Buddy {0}", token); } } wr.Method = verb; Action getResponse = () => { try { int requestStatus = -1; LogRequest(MethodName(verb, path), url, null); wr.BeginGetResponse((async2) => { try { lock (wr) { if (requestStatus == 1) { throw new WebException("Request timed out.", WebExceptionStatus.RequestCanceled); } else { HttpWebResponse response = (HttpWebResponse)wr.EndGetResponse(async2); callback(null, response); } } } catch (WebException ex) { callback(ex, null); } }, null); // spin up a timer to check for timeout. not all platforms // support proper threadpool wait. // Action timeoutHandler = () => { lock (wr) { if (requestStatus == -1) { requestStatus = 1; wr.Abort(); } } }; #if NETFX_CORE TimeSpan delay = TimeSpan.FromMilliseconds(TimeoutMilliseconds); Windows.System.Threading.ThreadPoolTimer.CreateTimer( (source) => { timeoutHandler(); }, delay); #else new System.Threading.Timer((state) => { timeoutHandler(); }, null, TimeoutMilliseconds, System.Threading.Timeout.Infinite); #endif } catch (WebException wex) { LogResponse(MethodName(verb, path), wex.ToString(), TimeSpan.Zero); callback(wex, null); } }; try { if (HttpRequestType.HttpGet == requestType) { getResponse(); } else { wr.BeginGetRequestStream((async) => { try { using (var rs = wr.EndGetRequestStream(async)) { switch (requestType) { case HttpRequestType.HttpPostJson: wr.ContentType = "application/json"; parameters = ConvertUnspecifiedDateTimes(parameters); var json = JsonConvert.SerializeObject(parameters, Formatting.None, new JsonSerializerSettings { DateTimeZoneHandling = DateTimeZoneHandling.Utc }); byte[] jsonbytes = System.Text.Encoding.UTF8.GetBytes(json); rs.Write(jsonbytes, 0, jsonbytes.Length); LogRequest(MethodName(verb, path), url, json); break; case HttpRequestType.HttpPostMultipartForm: HttpPostMultipart(wr, rs, parameters); break; } rs.Flush(); } getResponse(); } catch (WebException wex) { LogResponse(MethodName(verb, path), wex.ToString(), TimeSpan.Zero); callback(wex, null); } }, null); } } catch (WebException wex) { LogResponse(MethodName(verb, path), wex.ToString(), TimeSpan.Zero); callback(wex, null); } }
protected override Task <BuddyCallResult <T> > CallMethodAsyncCore <T>( string verb, string path, object parameters, bool skipAuth) { // TODO: Refactor to use async/await instead of // TCS/callbacks. var tcs = new TaskCompletionSource <BuddyCallResult <T> >(); DateTime start = DateTime.Now; StartRequest(); Action <Exception, BuddyCallResult <T> > finishMethodCall = (ex, bcr) => { EndRequest(); if (ex == null) { tcs.TrySetResult(bcr); return; } WebException webEx = ex as WebException; HttpWebResponse response = null; var err = "UnknownServiceError"; bcr.Message = ex.ToString(); if (webEx != null) { err = "InternetConnectionError"; if (webEx.Response != null) { response = (HttpWebResponse)webEx.Response; bcr.Message = response.StatusDescription; bcr.StatusCode = (int)response.StatusCode; if (bcr.StatusCode >= 400) { err = "UnknownServiceError"; } } else { bcr.Message = webEx.Status.ToString(); } } bcr.Error = err; LogResponse(verb + " " + path, bcr.Message, DateTime.Now.Subtract(start), response); OnServiceException(ex); tcs.TrySetResult(bcr); }; var d = ParametersToDictionary(parameters); MakeRequest <T>(verb, path, d, async(ex, response) => { var bcr = new BuddyCallResult <T>(); var isFile = typeof(BuddyFile).Equals(typeof(T)); if (response == null && ex != null && ex is WebException) { response = (HttpWebResponse)((WebException)ex).Response; } if ((response == null) && ex != null) { finishMethodCall(ex, bcr); return; } else if (response != null) { bcr.StatusCode = (int)response.StatusCode; if (!isFile || (bcr.StatusCode >= 400 && response.ContentType.Contains("application/json"))) { string body = null; try { using (var responseStream = response.GetResponseStream()) { body = await new StreamReader(responseStream).ReadToEndAsync(); } } catch (Exception rex) { finishMethodCall(rex, bcr); return; } LogResponse(MethodName(verb, path), body, DateTime.Now.Subtract(start), response); //json parse try { var envelope = JsonConvert.DeserializeObject <JsonEnvelope <T> >(body, new JsonSerializerSettings { DateTimeZoneHandling = DateTimeZoneHandling.Local }); if (envelope == null) { // fall through } else if (envelope.error != null) { bcr.Error = envelope.error; bcr.ErrorNumber = envelope.errorNumber; bcr.Message = envelope.message; } else { // special case dictionary. if (typeof(IDictionary <string, object>).IsAssignableFrom(typeof(T))) { object obj = envelope.result; IDictionary <string, object> d2 = (IDictionary <string, object>)obj; obj = (obj == null) ? new Dictionary <string, object>(DotNetDeltas.InvariantComparer(true)) : new Dictionary <string, object>(d2, DotNetDeltas.InvariantComparer(true)); envelope.result = (T)obj; } bcr.Result = envelope.result; } if (envelope != null) { bcr.RequestID = envelope.request_id; } } catch (Exception pex) { LogMessage(pex.ToString()); bcr.Error = "BadJsonResponse"; bcr.Message = "Couldn't parse JSON: \r\n" + body; } } else { bcr = new BuddyCallResult <T>(); if (bcr.StatusCode < 400) { var file = new BuddyFile(response.GetResponseStream(), null, response.ContentType); bcr.Result = (T)(object)file; } bcr.StatusCode = (int)response.StatusCode; } try { finishMethodCall(null, bcr); } catch (Exception ex3) { finishMethodCall(ex3, bcr); } } }, skipAuth); return(tcs.Task); }