protected HttpContent ExtractBodyFromRequestModel(DscRequest dscRequ) { HttpContent content = null; PropertyInfo fromBodyProperty = null; foreach (var pi in dscRequ.GetType().GetProperties()) { var attr = pi.GetCustomAttribute(typeof(FromBodyAttribute)) as FromBodyAttribute; if (attr != null) { fromBodyProperty = pi; break; } } if (fromBodyProperty != null) { // We test for a few principal property types that we can send directly as body, // content and otherwise we assume a custom model object that we serialize via JSON var required = fromBodyProperty.GetCustomAttribute(typeof(RequiredAttribute)) as RequiredAttribute; if (typeof(string).IsAssignableFrom(fromBodyProperty.PropertyType)) { var body = (string)fromBodyProperty.GetValue(dscRequ); if (body != null) { content = new StringContent(body); } } else if (typeof(byte[]).IsAssignableFrom(fromBodyProperty.PropertyType)) { var body = (byte[])fromBodyProperty.GetValue(dscRequ); if (body != null) { content = new ByteArrayContent(body); } } else if (typeof(Stream).IsAssignableFrom(fromBodyProperty.PropertyType)) { var body = (Stream)fromBodyProperty.GetValue(dscRequ); if (body != null) { content = new StreamContent(body); } } else { var body = fromBodyProperty.GetValue(dscRequ); if (body != null) { var bodySer = JsonConvert.SerializeObject(body, _jsonSerSettings); content = new StringContent(bodySer); } else if ((bool)required?.AllowEmptyStrings) { content = new StringContent(string.Empty); } } } return(content); }
protected async Task <IDisposable> SendDscAsync(DscPullConfig.ServerConfig server, HttpMethod verb, string route, DscRequest dscRequ, DscResponse dscResp) { if (LOG.IsEnabled(LogLevel.Trace)) { LOG.LogTrace(nameof(SendDscAsync)); } AssertInit(); dscRequ.ProtocolVersionHeader = "2.0"; var routeExpanded = route; if (dscRequ is DscAgentRequest) { var dscAgentRequ = (DscAgentRequest)dscRequ; routeExpanded = route.Replace("{AgentId}", dscAgentRequ.AgentId.ToString()); } var requUrl = new Uri(server.ServerUrl, routeExpanded); if (LOG.IsEnabled(LogLevel.Debug)) { LOG.LogDebug("Computed request URL: [{url}]", requUrl); } var httpHandler = new HttpClientHandler(); if (server.Proxy != null) { if (LOG.IsEnabled(LogLevel.Debug)) { LOG.LogDebug("Enabling Proxy: [{proxy}] supported=[{supported}]", server.Proxy, httpHandler.SupportsProxy); } httpHandler.UseProxy = true; httpHandler.Proxy = server.Proxy; } var requMessage = new HttpRequestMessage { Method = verb, RequestUri = requUrl, }; // By default, we only send JSON unless something else was specified var contentType = dscRequ.ContentTypeHeader; if (string.IsNullOrEmpty(contentType)) { contentType = DscContentTypes.JSON; } requMessage.Headers.Accept.Clear(); requMessage.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue(contentType)); ExtractFromRequestModel(dscRequ, requMessage); // See if we need to add RegKey authorization data if (!string.IsNullOrEmpty(server.RegistrationKey) && dscRequ.MsDateHeader == COMPUTE_MS_DATE_HEADER && requMessage.Content != null) { LOG.LogInformation("Computing RegKey Authorization"); // Shhh! This is the super-secret formula for computing an // Authorization challenge when using Reg Key Authentication // Details can be found at /references/regkey-authorization.md var msDate = DateTime.UtcNow.ToString(DscRequest.X_MS_DATE_FORMAT); requMessage.Headers.Remove(DscRequest.X_MS_DATE_HEADER); requMessage.Headers.Add(DscRequest.X_MS_DATE_HEADER, msDate); dscRequ.MsDateHeader = null; var macKey = Encoding.UTF8.GetBytes(server.RegistrationKey); using (var sha = SHA256.Create()) using (var mac = new HMACSHA256(macKey)) using (var ms = new MemoryStream()) { await requMessage.Content.CopyToAsync(ms); var body = ms.ToArray(); LOG.LogDebug("Computing hash over body content"); LOG.LogDebug("BODY:-----------------------------"); LOG.LogDebug($"<{Encoding.UTF8.GetString(body)}>"); LOG.LogDebug("-----------------------------:BODY"); var digest = sha.ComputeHash(body); var digB64 = Convert.ToBase64String(digest); LOG.LogDebug(" * digB64=" + digB64); var concat = $"{digB64}\n{msDate}"; LOG.LogDebug(" * concat=" + concat); var macSig = mac.ComputeHash(Encoding.UTF8.GetBytes(concat)); var sigB64 = Convert.ToBase64String(macSig); requMessage.Headers.Authorization = new AuthenticationHeaderValue("Shared", sigB64); } } // TODO: Eventually we'll address this improper usage pattern as described here: // https://aspnetmonsters.com/2016/08/2016-08-27-httpclientwrong/ HttpResponseMessage respMessage = null; using (var http = _clientFactory(httpHandler)) { respMessage = await http.SendAsync(requMessage); } respMessage.EnsureSuccessStatusCode(); if (dscResp == null) { return(null); } else { return(await ExtractToResponseModel(respMessage, dscResp)); } }
protected void ExtractFromRequestModel(DscRequest dscRequ, HttpRequestMessage requMessage) { // For POST or PUT requests, we search to see if any // property is supposed to be sent as the request body if (requMessage.Method == HttpMethod.Post || requMessage.Method == HttpMethod.Put) { requMessage.Content = ExtractBodyFromRequestModel(dscRequ); } // This will be resolved and populated on-demand // if there are any "FromRoute" model properties StringBuilder route = null; foreach (var pi in dscRequ.GetType().GetProperties()) { var fromHeader = pi.GetCustomAttribute(typeof(FromHeaderAttribute)) as FromHeaderAttribute; if (fromHeader != null) { var name = fromHeader.Name; if (string.IsNullOrEmpty(name)) { name = pi.Name; } var value = pi.GetValue(dscRequ); if (value == null) { continue; } if (!typeof(string).IsAssignableFrom(value.GetType())) { value = ConvertTo <string>(value); } if (LOG.IsEnabled(LogLevel.Debug)) { LOG.LogDebug("Extracting request header [{name}] from property [{property}]", name, pi.Name); } if (!(TryAddHeader(requMessage.Headers, name, (string)value, replace: true))) { if (!(TryAddHeader(requMessage.Content?.Headers, name, (string)value, replace: true))) { throw new Exception( /*SR*/ "unable to add header anywhere to request message") .WithData(nameof(name), name) .WithData(nameof(value), value); } else if (LOG.IsEnabled(LogLevel.Debug)) { LOG.LogDebug(" added as CONTENT header"); } else if (LOG.IsEnabled(LogLevel.Debug)) { LOG.LogDebug(" added as REQUEST header"); } } continue; } var fromRoute = pi.GetCustomAttribute(typeof(FromRouteAttribute)) as FromRouteAttribute; if (fromRoute != null) { if (route == null) { // This must be the first property that updates the route route = new StringBuilder(requMessage.RequestUri.ToString()); } var name = fromRoute.Name; if (string.IsNullOrEmpty(name)) { name = pi.Name; } var value = pi.GetValue(dscRequ); if (value == null) { // TODO: is this the right assumption??? value = string.Empty; } if (!typeof(string).IsAssignableFrom(value.GetType())) { value = ConvertTo <string>(value); } if (LOG.IsEnabled(LogLevel.Debug)) { LOG.LogDebug("Extracting route element [{name}] from property [{property}]", name, pi.Name); } // Replace all occurrences of the route element // name wrapped in curlies with this value route.Replace($"{{{name}}}", (string)value); } // TODO: Also need to do FromQuery } // If the route was modified, update the request's URL if (route != null) { requMessage.RequestUri = new Uri(route.ToString()); } }
protected async Task SendDscAsync(DscPullConfig.ServerConfig server, HttpMethod verb, string route, DscRequest dscRequ) { await SendDscAsync(server, verb, route, dscRequ, null); }