private async Task <T> SendWebRequestAsync <T>(string httpMethod, string absoluteUrl, object request, CancellationToken token, bool recall = false) { if (httpMethod == null) { throw new ArgumentNullException(nameof(httpMethod)); } this.PopulateRequestMetadata(request); var requestUri = absoluteUrl; var hasQueryString = request != null && !HttpUtils.HasRequestBody(httpMethod); if (hasQueryString) { var queryString = QueryStringSerializer.SerializeToString(request); if (!string.IsNullOrEmpty(queryString)) { requestUri += "?" + queryString; } } var webReq = this.CreateHttpWebRequest(requestUri); if (webReq != null && Proxy != null) { webReq.Proxy = Proxy; } var timedOut = false; ITimer timer = null; timer = PclExportClient.Instance.CreateTimer(state => { timedOut = true; webReq?.Abort(); webReq = null; timer?.Cancel(); timer = null; }, this.Timeout.GetValueOrDefault(DefaultTimeout), this); Exception ResolveException(Exception ex) { if (token.IsCancellationRequested) { return(new OperationCanceledException(token)); } if (timedOut) { return(PclExportClient.Instance.CreateTimeoutException(ex, "The request timed out")); } return(ex); } bool returningWebResponse = false; HttpWebResponse webRes = null; T Complete(T response) { timer.Cancel(); PclExportClient.Instance.SynchronizeCookies(this); ResultsFilterResponse?.Invoke(webRes, response, httpMethod, absoluteUrl, request); return(response); } webReq.Accept = ContentType; if (this.EmulateHttpViaPost) { webReq.Method = "POST"; webReq.Headers[HttpHeaders.XHttpMethodOverride] = httpMethod; } else { webReq.Method = httpMethod; } PclExportClient.Instance.AddHeader(webReq, Headers); PclExport.Instance.Config(webReq, userAgent: UserAgent); if (this.authInfo != null && !string.IsNullOrEmpty(this.UserName)) { webReq.AddAuthInfo(this.UserName, this.Password, authInfo); } else if (this.BearerToken != null) { webReq.Headers[HttpHeaders.Authorization] = "Bearer " + this.BearerToken; } else if (this.Credentials != null) { webReq.Credentials = this.Credentials; } else if (this.AlwaysSendBasicAuthHeader) { webReq.AddBasicAuth(this.UserName, this.Password); } if (!DisableAutoCompression) { PclExport.Instance.AddCompression(webReq); } ApplyWebRequestFilters(webReq); try { if (HttpUtils.HasRequestBody(webReq.Method)) { webReq.ContentType = ContentType; if (RequestCompressionType != null) { webReq.Headers[HttpHeaders.ContentEncoding] = RequestCompressionType; } if (HttpLog != null) { webReq.AppendHttpRequestHeaders(HttpLog, new Uri(BaseUri)); } using var requestStream = await webReq.GetRequestStreamAsync().ConfigAwait(); token.ThrowIfCancellationRequested(); if (request != null) { StreamSerializer(null, request, requestStream); } } else { if (HttpLog != null) { webReq.AppendHttpRequestHeaders(HttpLog, new Uri(BaseUri)); } } HttpLog?.AppendLine(); } catch (Exception ex) { if (Log.IsDebugEnabled) { Log.Debug($"Error Sending Request: {ex.Message}", ex); } throw HandleResponseError <T>(ResolveException(ex), requestUri, request); } try { webRes = (HttpWebResponse)await webReq.GetResponseAsync().ConfigAwait(); { token.ThrowIfCancellationRequested(); ApplyWebResponseFilters(webRes); returningWebResponse = typeof(T) == typeof(HttpWebResponse); if (returningWebResponse) { return(Complete((T)(object)webRes)); } var responseStream = webRes.ResponseStream(); var responseBodyLength = webRes.ContentLength; var bufferRead = new byte[BufferSize]; var totalRead = 0; int read; var ms = MemoryStreamFactory.GetStream(); while ((read = await responseStream.ReadAsync(bufferRead, 0, bufferRead.Length, token).ConfigAwait()) != 0) { await ms.WriteAsync(bufferRead, 0, read, token).ConfigAwait(); totalRead += read; OnDownloadProgress?.Invoke(totalRead, responseBodyLength); } try { ms.Position = 0; if (HttpLog != null) { webRes.AppendHttpResponseHeaders(HttpLog); if (webRes.ContentLength != 0 && webRes.StatusCode != HttpStatusCode.NoContent) { var isBinary = typeof(T) == typeof(Stream) || typeof(T) == typeof(byte[]) || ContentType.IsBinary(); if (isBinary) { HttpLog.Append("(base64) "); HttpLog.AppendLine(Convert.ToBase64String(ms.ReadFully())); } else { HttpLog.AppendLine(ms.ReadToEnd()); } HttpLog.AppendLine().AppendLine(); ms.Position = 0; } } if (typeof(T) == typeof(Stream)) { return(Complete((T)(object)ms)); } else { var stream = ms; try { if (typeof(T) == typeof(string)) { return(Complete((T)(object)await stream.ReadToEndAsync().ConfigAwait())); } else if (typeof(T) == typeof(byte[])) { return(Complete((T)(object)stream.ToArray())); } else { return(Complete((T)this.StreamDeserializer(typeof(T), stream))); } } finally { if (stream.CanRead) { stream.Dispose(); // Not yet disposed, but could've been. } } } } catch (Exception ex) { if (Log.IsDebugEnabled) { Log.Debug($"Error Reading Response Error: {ex.Message}", ex); } throw; } finally { if (HttpLog != null) { HttpLogFilter?.Invoke(HttpLog); } responseStream.Close(); } } } catch (Exception ex) { var webEx = ex as WebException; var firstCall = !recall; var hasRefreshTokenCookie = this.CookieContainer.GetRefreshTokenCookie(BaseUri) != null; var hasRefreshToken = RefreshToken != null || hasRefreshTokenCookie; if (firstCall && WebRequestUtils.ShouldAuthenticate(webEx, (!string.IsNullOrEmpty(UserName) && !string.IsNullOrEmpty(Password)) || Credentials != null || BearerToken != null || hasRefreshToken || OnAuthenticationRequired != null)) { try { if (hasRefreshToken) { var refreshRequest = new GetAccessToken { RefreshToken = hasRefreshTokenCookie ? null : RefreshToken, }; var uri = this.RefreshTokenUri ?? this.BaseUri.CombineWith(refreshRequest.ToPostUrl()); this.BearerToken = null; this.CookieContainer?.DeleteCookie(new Uri(BaseUri), "ss-tok"); GetAccessTokenResponse tokenResponse; try { tokenResponse = (await uri.PostJsonToUrlAsync(refreshRequest, requestFilter: req => { if (hasRefreshTokenCookie) { req.CookieContainer = CookieContainer; } }, token: token).ConfigAwait()).FromJson <GetAccessTokenResponse>(); } catch (WebException refreshEx) { var webServiceEx = ServiceClientBase.ToWebServiceException(refreshEx, stream => StreamDeserializer(typeof(T), stream), ContentType); if (webServiceEx != null) { throw new RefreshTokenException(webServiceEx); } throw new RefreshTokenException(refreshEx.Message, refreshEx); } var accessToken = tokenResponse?.AccessToken; var refreshClient = webReq = (HttpWebRequest)WebRequest.Create(requestUri); var tokenCookie = this.CookieContainer.GetTokenCookie(BaseUri); if (!string.IsNullOrEmpty(accessToken)) { refreshClient.AddBearerToken(this.BearerToken = accessToken); } else if (tokenCookie != null) { refreshClient.CookieContainer = CookieContainer; refreshClient.CookieContainer.SetTokenCookie(BaseUri, tokenCookie); } else { throw new RefreshTokenException("Could not retrieve new AccessToken from: " + uri); } return(await SendWebRequestAsync <T>(httpMethod, absoluteUrl, request, token, recall : true).ConfigAwait()); } OnAuthenticationRequired?.Invoke(); var newReq = (HttpWebRequest)WebRequest.Create(requestUri); if (StoreCookies) { newReq.CookieContainer = CookieContainer; } HandleAuthException(ex, webReq); return(await SendWebRequestAsync <T>(httpMethod, absoluteUrl, request, token, recall : true).ConfigAwait()); } catch (WebServiceException) { throw; } catch (Exception /*subEx*/) { throw HandleResponseError <T>(ResolveException(ex), requestUri, request); } } if (ExceptionFilter != null && webEx?.Response != null) { var cachedResponse = ExceptionFilter(webEx, webEx.Response, requestUri, typeof(T)); if (cachedResponse is T variable) { return(variable); } } throw HandleResponseError <T>(ResolveException(ex), requestUri, request); } finally { if (!returningWebResponse) { webRes?.Dispose(); } } }
public static string ToUrl(this object requestDto, string httpMethod, Func<Type, string> fallback) { httpMethod = httpMethod.ToUpper(); var urlFilter = requestDto as IUrlFilter; var requestType = requestDto.GetType(); var requestRoutes = routesCache.GetOrAdd(requestType, GetRoutesForType); if (requestRoutes.Count == 0) { if (fallback == null) throw new InvalidOperationException($"There are no rest routes mapped for '{requestType}' type. " + "(Note: The automatic route selection only works with [Route] attributes on the request DTO and " + "not with routes registered in the IAppHost!)"); var predefinedRoute = fallback(requestType); if (httpMethod is "GET" or "DELETE" or "OPTIONS" or "HEAD") { var queryProperties = RestRoute.GetQueryProperties(requestDto.GetType()); if (queryProperties.Count > 0) predefinedRoute += "?" + RestRoute.GetQueryString(requestDto, queryProperties); } return urlFilter == null ? predefinedRoute : urlFilter.ToUrl(predefinedRoute); } var routesApplied = requestRoutes.Select(route => route.Apply(requestDto, httpMethod)).ToList(); var matchingRoutes = routesApplied.Where(x => x.Matches).ToList(); if (matchingRoutes.Count == 0) { var errors = Join(Empty, routesApplied.Select(x => $"\r\n\t{x.Route.Path}:\t{x.FailReason}").ToArray()); var errMsg = $"None of the given rest routes matches '{requestType.GetOperationName()}' request:{errors}"; throw new InvalidOperationException(errMsg); } RouteResolutionResult matchingRoute; if (matchingRoutes.Count > 1) { matchingRoute = FindMostSpecificRoute(matchingRoutes); if (matchingRoute == null) { var errors = Join(Empty, matchingRoutes.Select(x => "\r\n\t" + x.Route.Path).ToArray()); var errMsg = $"Ambiguous matching routes found for '{requestType.Name}' request:{errors}"; throw new InvalidOperationException(errMsg); } } else { matchingRoute = matchingRoutes[0]; } var url = matchingRoute.Uri; if (!HttpUtils.HasRequestBody(httpMethod)) { var queryParams = matchingRoute.Route.FormatQueryParameters(requestDto); if (!IsNullOrEmpty(queryParams)) { url += "?" + queryParams; } } return urlFilter == null ? url : urlFilter.ToUrl(url); }
public List <PostmanRequest> GetRequests(Postman request, string parentId, IEnumerable <Operation> operations) { var ret = new List <PostmanRequest>(); var feature = HostContext.GetPlugin <PostmanFeature>(); var headers = feature.Headers ?? ("Accept: " + MimeTypes.Json); if (Response is IHttpResponse httpRes) { if (request.ssopt != null || request.sspid != null || request.ssid != null) { if (feature.EnableSessionExport != true) { throw new ArgumentException("PostmanFeature.EnableSessionExport is not enabled"); } } if (request.ssopt != null) { Request.AddSessionOptions(request.ssopt); } if (request.sspid != null) { httpRes.Cookies.AddPermanentCookie(SessionFeature.PermanentSessionId, request.sspid); } if (request.ssid != null) { httpRes.Cookies.AddSessionCookie(SessionFeature.SessionId, request.ssid, (HostContext.Config.UseSecureCookies && Request.IsSecureConnection)); } } foreach (var op in operations) { if (!HostContext.Metadata.IsVisible(base.Request, op)) { continue; } var allVerbs = op.Actions.Concat( op.Routes.SelectMany(x => x.Verbs)) .SelectMany(x => x == ActionContext.AnyAction ? feature.DefaultVerbsForAny : new List <string> { x }) .ToSet(); var propertyTypes = new Dictionary <string, string>(StringComparer.OrdinalIgnoreCase); op.RequestType.GetSerializableFields() .Each(x => propertyTypes[x.Name] = x.FieldType.AsFriendlyName(feature)); op.RequestType.GetSerializableProperties() .Each(x => propertyTypes[x.Name] = x.PropertyType.AsFriendlyName(feature)); foreach (var route in op.Routes) { var routeVerbs = route.Verbs.Contains(ActionContext.AnyAction) ? feature.DefaultVerbsForAny.ToArray() : route.Verbs; var restRoute = route.ToRestRoute(); foreach (var verb in routeVerbs) { allVerbs.Remove(verb); //exclude handled verbs var routeData = restRoute.QueryStringVariables .Map(x => new PostmanData { key = x, value = "", type = "text", }) .ApplyPropertyTypes(propertyTypes); ret.Add(new PostmanRequest { collectionId = parentId, id = SessionExtensions.CreateRandomSessionId(), method = verb, url = Request.GetBaseUrl().CombineWith(restRoute.Path.ToPostmanPathVariables()), name = GetName(feature, request, op.RequestType, restRoute.Path), description = op.RequestType.GetDescription(), pathVariables = !HttpUtils.HasRequestBody(verb) ? restRoute.Variables.Concat(routeData.Select(x => x.key)) .ApplyPropertyTypes(propertyTypes) : null, data = HttpUtils.HasRequestBody(verb) ? routeData : null, dataMode = "params", headers = headers, version = 2, time = DateTime.UtcNow.ToUnixTimeMs(), }); } } var emptyRequest = op.RequestType.CreateInstance(); var virtualPath = emptyRequest.ToReplyUrlOnly(); var requestParams = propertyTypes .Map(x => new PostmanData { key = x.Key, value = x.Value, type = "text", }); ret.AddRange(allVerbs.Select(verb => new PostmanRequest { collectionId = parentId, id = SessionExtensions.CreateRandomSessionId(), method = verb, url = Request.GetBaseUrl().CombineWith(virtualPath), pathVariables = !HttpUtils.HasRequestBody(verb) ? requestParams.Select(x => x.key) .ApplyPropertyTypes(propertyTypes) : null, name = GetName(feature, request, op.RequestType, virtualPath), description = op.RequestType.GetDescription(), data = HttpUtils.HasRequestBody(verb) ? requestParams : null, dataMode = "params", headers = headers, version = 2, time = DateTime.UtcNow.ToUnixTimeMs(), })); } return(ret); }
public Task <TResponse> SendAsync <TResponse>(string httpMethod, string absoluteUrl, object request, CancellationToken token = default(CancellationToken)) { var client = GetHttpClient(); if (!HttpUtils.HasRequestBody(httpMethod) && request != null) { var queryString = QueryStringSerializer.SerializeToString(request); if (!string.IsNullOrEmpty(queryString)) { absoluteUrl += "?" + queryString; } } var response = ResultsFilter?.Invoke(typeof(TResponse), httpMethod, absoluteUrl, request); if (response is TResponse) { var tcs = new TaskCompletionSource <TResponse>(); tcs.SetResult((TResponse)response); return(tcs.Task); } if (token == default(CancellationToken)) { if (CancelTokenSource == null) { CancelTokenSource = new CancellationTokenSource(); } token = CancelTokenSource.Token; } var httpReq = CreateRequest(httpMethod, absoluteUrl, request); var sendAsyncTask = client.SendAsync(httpReq, token); if (typeof(TResponse) == typeof(HttpResponseMessage)) { return((Task <TResponse>)(object) sendAsyncTask); } return(sendAsyncTask .ContinueWith(responseTask => { var httpRes = responseTask.Result; if (httpRes.StatusCode == HttpStatusCode.Unauthorized) { if (RefreshToken != null) { var refreshDto = new GetAccessToken { RefreshToken = RefreshToken }; var uri = this.RefreshTokenUri ?? this.BaseUri.CombineWith(refreshDto.ToPostUrl()); return this.PostAsync <GetAccessTokenResponse>(uri, refreshDto) .ContinueWith(t => { if (t.IsFaulted) { var refreshEx = t.Exception.UnwrapIfSingleException() as WebServiceException; if (refreshEx != null) { throw new RefreshTokenException(refreshEx); } throw t.Exception; } var accessToken = t.Result?.AccessToken; if (string.IsNullOrEmpty(accessToken)) { throw new RefreshTokenException("Could not retrieve new AccessToken from: " + uri); } var refreshRequest = CreateRequest(httpMethod, absoluteUrl, request); if (this.GetTokenCookie() != null) { this.SetTokenCookie(accessToken); } else { refreshRequest.AddBearerToken(this.BearerToken = accessToken); } return client.SendAsync(refreshRequest, token).ContinueWith(refreshTask => ConvertToResponse <TResponse>(refreshTask.Result, httpMethod, absoluteUrl, refreshRequest, token), token).Unwrap(); }, token).Unwrap(); } if (UserName != null && Password != null && client.DefaultRequestHeaders.Authorization == null) { AddBasicAuth(client); httpReq = CreateRequest(httpMethod, absoluteUrl, request); sendAsyncTask = client.SendAsync(httpReq, token); return sendAsyncTask.ContinueWith(t => ConvertToResponse <TResponse>(t.Result, httpMethod, absoluteUrl, request, token), token).Unwrap(); } } return ConvertToResponse <TResponse>(httpRes, httpMethod, absoluteUrl, request, token); }, token).Unwrap()); }