Single Instance of HttpClient with UseDefaultCredentials - asp.net

I have been trying to update my API call using the suggestion from here
to only have 1 instance of HttpClient. https://aspnetmonsters.com/2016/08/2016-08-27-httpclientwrong/#
This works fine and I have my do not use up all of the ports, however when I try to create a HttpClientHandler to pass along the default credentials my ports start being used up again. My API is setup to use Windows Auth for security reasons so I need to pass along the app pools credentials for a successful call.
Here are the 2 code blocks
public static class WebApiCallUtility
{
private static HttpClientHandler _handlerNoCred = new HttpClientHandler();
private static HttpClient _clientNoCred = new HttpClient(_handlerNoCred);
private static HttpClientHandler _handlerCred = new HttpClientHandler { UseDefaultCredentials = true };
private static HttpClient _clientCred = new HttpClient(_handlerCred);
//Working ports are not used up
public static HttpResponseMessage SendHttpGetRequestNoCred(string webApiUrl, string logSourceName, string subId)
{
_clientNoCred.DefaultRequestHeaders.Accept.Clear();
_clientNoCred.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
HttpResponseMessage result = _clientNoCred.GetAsync(webApiUrl).Result;
return result;
}
//No working tons of ports open hanging out with TIME_WAIT status
public static HttpResponseMessage SendHttpGetRequestCred(string webApiUrl, string logSourceName, string subId)
{
_clientCred.DefaultRequestHeaders.Accept.Clear();
_clientCred.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
HttpResponseMessage result = _clientCred.GetAsync(webApiUrl).Result;
return result;
}
}
Any help would be appreciated.
Thanks

I was able to revolve this issue buy using a different handler type
private static WebRequestHandler _handlerCred = new WebRequestHandler
{
UseDefaultCredentials = true,
UnsafeAuthenticatedConnectionSharing = true
};
private static HttpClient _clientCred = new HttpClient(_handlerCred);
I found this answer here Static HttpClient still creating TIME_WAIT tcp ports

Related

Azure App Service Add Custom Domain .Net Core

I am following a sample from https://github.com/davidebbo/AzureWebsitesSamples
private static ResourceManagementClient _resourceGroupClient;
private static WebSiteManagementClient _websiteClient;
private static AzureEnvironment _environment;
private static DnsManagementClient _dnsClient;
static string _ClientId = Startup.StaticConfig.GetValue<string>("Azure:ClientId");
static string _ClientKey = Startup.StaticConfig.GetValue<string>("Azure:ClientSecret");
static string _TenantId = Startup.StaticConfig.GetValue<string>("Azure:TenantId");
static string _SubscriptionId = Startup.StaticConfig.GetValue<string>("Azure:SubscriptionId");
static string _ResourceGroupName = Startup.StaticConfig.GetValue<string>("Azure:ResourceGroupName");
static string _AppName = Startup.StaticConfig.GetValue<string>("Azure:AppName");
public static string ResourceGroupName { get => _ResourceGroupName; set => _ResourceGroupName = value; }
public static async Task MainAsync()
{
// Set Environment - Choose between Azure public cloud, china cloud and US govt. cloud
_environment = AzureEnvironment.PublicEnvironments[EnvironmentName.AzureCloud];
// Get the credentials
TokenCloudCredentials cloudCreds = await GetCredsFromServicePrincipal();
var tokenCreds = new TokenCredentials(cloudCreds.Token);
//var loggingHandler = new LoggingHandler(new HttpClientHandler());
// Create our own HttpClient so we can do logging
var httpClient = new HttpClient();
// Use the creds to create the clients we need
_resourceGroupClient = new ResourceManagementClient(_environment.GetEndpointAsUri(AzureEnvironment.Endpoint.ResourceManager), tokenCreds );
_resourceGroupClient.SubscriptionId = cloudCreds.SubscriptionId;
_websiteClient = new WebSiteManagementClient(_environment.GetEndpointAsUri(AzureEnvironment.Endpoint.ResourceManager), tokenCreds);
_websiteClient.SubscriptionId = cloudCreds.SubscriptionId;
_dnsClient = new DnsManagementClient(tokenCreds);
AddCustomDomainToSite("mycustomdomain.com");
}
private static async Task<TokenCloudCredentials> GetCredsFromServicePrincipal()
{
// Quick check to make sure we're not running with the default app.config
if (_SubscriptionId[0] == '[')
{
throw new Exception("You need to enter your appSettings in app.config to run this sample");
}
var authority = String.Format("{0}{1}", _environment.Endpoints[AzureEnvironment.Endpoint.ActiveDirectory], _TenantId);
var authContext = new AuthenticationContext(authority);
var credential = new ClientCredential(_ClientId, _ClientKey);
var authResult = await authContext.AcquireTokenAsync(_environment.Endpoints[AzureEnvironment.Endpoint.ActiveDirectoryServiceEndpointResourceId], credential);
return new TokenCloudCredentials(_SubscriptionId, authResult.AccessToken);
}
static void AddCustomDomainToSite(string sDomainName)
{
Domain domain = new Domain();
_websiteClient.Domains.CreateOrUpdateAsync(_ResourceGroupName, "mycustomdomain.com", domain);
}
I am trying to add mycustomdomain.com to my Azure app service. When I execute the code _websiteClient.Domains.CreateOrUpdateAsync(_ResourceGroupName, "mycustomdomain.com", domain);, nothing happens. I do not get any errors, and I do not see the custom domain listed under Custom Domains in my app service.
I have already verified ownership of the domain, and I can add it to my app service via the portal, but I am trying to add it through C#. Can someone please help me?
Did you already check on "_websiteClient.WebApps.CreateOrUpdateHostNameBindingAsync(resourcegroup, appservicename, hostname, binding)"?
The binding i give as parameter is this one:
var binding = new HostNameBinding{CustomHostNameDnsRecordType = CustomHostNameDnsRecordType.CName};
For me this is working to add a custom domain to the app service.

FeignClient configuration in ASP.Net

I am trying to create microservices using Spring-boot Java and SteelToe ASP.NET
Step-1: I created a full service using Java (A service with UI and API. It is hosted on PCF). The API has ClassesControler defined inside.
Step-2: Create a microservice using ASP.NET, SteelToe. Register the service in Eureka and make it discoverable using Zuul.
Step-3: Use the Interface, Service approach to access the JAVA microservice(s)
namespace employee-client.Service
{
public interface IRelayService
{
Task<HttpResponseMessage> getClassesList(string relativeUrl = "/api/v1/classes");
}
}
Service with Implementation for Interface:
namespace employee-client.Service
{
public class RelayService : IRelayService
{
DiscoveryHttpClientHandler _handler;
string _accessToken;
private const string BASE_URL = "https://www.example.com";
public QortaService(IDiscoveryClient client, string accessToken)
{
_handler = new DiscoveryHttpClientHandler(client);
_accessToken = accessToken;
}
public async Task<HttpResponseMessage> getClassesList(string relativeUrl)
{
string classesUrl= BASE_URL + relativeUrl;
HttpClient client = GetClient();
HttpRequestMessage request = new HttpRequestMessage();
request.RequestUri = new Uri(classesUrl);
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", _accessToken);
return await client.SendAsync(request, HttpCompletionOption.ResponseContentRead);
}
private HttpClient GetClient()
{
var client = new HttpClient(_handler, false);
return client;
}
}
}
I came up with this approach based on the example in SteelToe but I hate hardcoding the BASE_URL.
Question: I very much like the #FeignClient annotation approach used in Java. Any ideas about how I can access an existing microservice in a better way. If so, an example would be much appreciated
Edit:
I modified the question to make more clear.
The flow of traffic is from Java Service to .NET service. .NET service requests for a list of classes from the controller in JAVA service (ClassesController.java)
I'm unclear which direction traffic is flowing in your scenario, but I think you're saying the .NET application is trying to call the Java application. The code you're using is from before HttpClientFactory was introduced and is a bit clunkier than what's possible now in general. Steeltoe can be used with HttpClientFactory for a better overall experience.
Steeltoe has debug logging available to confirm the results of service lookup if you set logging:loglevel:Steeltoe.Common.Discovery = true in your application config.
You didn't mention specifically what isn't working, but I'm guessing you're getting a 404 since it looks like your code will create a request path looking like https://fortuneService/api/fortunes/random/api/v1/classes
If you're looking for something like Feign in .NET, you could try out DHaven.Faux
For others who are looking for the same:
namespace employee-client.Service
{
public class RelayService : IRelayService
{
private const string CLASSES_API_SERVICEID = "classes-api";
IDiscoveryClient _discoveryClient;
DiscoveryHttpClientHandler _handler;
string _accessToken;
public RelayService(IDiscoveryClient discoveryClient, string accessToken)
{
_discoveryClient = discoveryClient;
_handler = new DiscoveryHttpClientHandler(client);
_accessToken = accessToken;
}
public async Task<HttpResponseMessage> getClassesList()
{
var classesApiInstances = _discoveryClient.GetInstances(CLASSES_API_SERVICEID);
Uri classesApiUri = classesApiInstances[0].Uri;
string classesUrl= classesApiUri.AbsoluteUri + relativeUrl;
HttpClient httpClient = GetClient();
HttpRequestMessage request = new HttpRequestMessage();
request.RequestUri = new Uri(classesUrl);
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", _accessToken);
return await httpClient.SendAsync(request, HttpCompletionOption.ResponseContentRead);
}
private HttpClient GetClient()
{
var client = new HttpClient(_handler, false);
return client;
}
}
}

Unable able to communicate between two services

I have created two Web API in Asp.net Core one is Wrapper Service to be deployed on DMZ Server and other is core service which have access to our DB Non DMZ. The problem i am facing is that i am unable to communicate between two services.
Both are running on local machine through dockers. When i try to hit core service running on url: https://localhost:56788/Rewards thorugh HttpClient/WebRequest i got message
No connection could be made because the target machine actively refused it.
DMZ Controller Logic
[Route("[controller]")]
[ApiController]
public class RewardsController : ControllerBase
{
[HttpGet]
public string Get()
{
string response = string.Empty;
//using (var client = new HttpClient())
//{
// client.BaseAddress = new Uri("http://localhost:5000/");
// //HTTP GET
// var responseTask = client.GetAsync("Rewards");
// responseTask.Wait();
// var result = responseTask.Result;
// if (result.IsSuccessStatusCode)
// {
// response = result.ToString();
// }
// else //web api sent error response
// {
// //log response status here..
// response = "Error";
// }
//}
string sURL = "https://localhost:56788/Rewards";
WebRequest wrPostURL = WebRequest.Create(sURL);
wrPostURL.Method = "GET";
ServicePointManager.ServerCertificateValidationCallback += (sender, cert, chain, sslPolicyErrors) => true;
using (HttpWebResponse webresponse = wrPostURL.GetResponse() as HttpWebResponse)
{
Encoding enc = System.Text.Encoding.GetEncoding("utf-8");
StreamReader loResponseStream = new StreamReader(webresponse.GetResponseStream(), enc);
var jsonResponse = loResponseStream.ReadToEnd();
loResponseStream.Close();
webresponse.Close();
}
return response;
}
}
NON-DMZ Controller Logic
[Route("[controller]")]
[ApiController]
public class RewardsController : ControllerBase
{
[HttpGet]
public string Get()
{
return "Hello";
}
}
Update
This problem is due to dockers. When i deploy both APIs to IIS then it will work fine but i have to do it with Dockers
The problem occurs due to the fact that i was calling localhost to communicate with other API running on another container. When we call localhost the first container start looking for the service that is running on same container. By using VM/Local Machine IP instead of localhost the problem can be avoided.

How to change client TLS preferences in Java?

I'm trying to make a POST request to an endpoint in Java, and when I try to send the request, I get the following error:
Caused by: javax.net.ssl.SSLHandshakeException: The server selected protocol version TLS10 is not accepted by client preferences [TLS13, TLS12]
This is what I have so far
Map<Object, Object> data = new HashMap<>();
data.put("username","foo");
data.put("password","bar");
String url = "https://google.com";
HttpRequest request = HttpRequest.newBuilder()
.POST(buildFormDataFromMap(data))
.uri(URI.create(url))
.build();
try{
HttpResponse<String> response = httpClient.send(request,
HttpResponse.BodyHandlers.ofString());
System.out.println(response.statusCode());
System.out.println(response.body());
} catch (Exception e){
e.printStackTrace();
}
Then when I run the code, the error gets thrown when sending the request/making the response object. My question is, if the TLS preferences are different for the server than the client, how can I change the preferences within Java so it can still make the request?
To solve this problem in jdk 11, I had to create an javax.net.ssl.SSLParameters object to enable "TLSv1", etc:
SSLParameters sslParameters = new SSLParameters();
sslParameters.setProtocols(new String[]{"TLSv1", "TLSv1.1", "TLSv1.2", "TLSv1.3"});
Then create the HttpClient and add the sslParamters object:
HttpClient httpClient = HttpClient.newBuilder()
.sslParameters(sslParameters)
.build();
If you also want to disable hostname verification, add following code BEFORE HttpClient initialization;
final Properties props = System.getProperties();
props.setProperty("jdk.internal.httpclient.disableHostnameVerification", Boolean.TRUE.toString());
Also you can add a new TrustManager to trust all certificates (self signed).
To do so, add following code into your Class:
TrustManager[] trustAllCerts = new TrustManager[] {
new X509TrustManager() {
public java.security.cert.X509Certificate[] getAcceptedIssuers() {
return new X509Certificate[0];
}
public void checkClientTrusted(
java.security.cert.X509Certificate[] certs, String authType) {
}
public void checkServerTrusted(
java.security.cert.X509Certificate[] certs, String authType) {
}
}
};
After this, you have to create an SSLContext object and add the TrustManger object:
SSLContext sslContext = SSLContext.getInstance("TLS");
sslContext.init(null, trustAllCerts, new java.security.SecureRandom());
And finally alter the HttpClient initialization like this:
httpClient = HttpClient.newBuilder()
.sslContext(sslContext)
.sslParameters(sslParameters)
.build()
Here is a complete Class example:
import java.net.http.HttpClient;
import java.security.KeyManagementException;
import java.security.NoSuchAlgorithmException;
import java.security.cert.X509Certificate;
import java.util.Properties;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLParameters;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;
public class HttpSSLClient {
private SSLContext sslContext;
private SSLParameters sslParameters;
private HttpClient httpClient;
public HttpSSLClient() throws KeyManagementException, NoSuchAlgorithmException {
sslParameters = new SSLParameters();
sslParameters.setProtocols(new String[]{"TLSv1", "TLSv1.1", "TLSv1.2", "TLSv1.3"});
sslContext = SSLContext.getInstance("TLS");
sslContext.init(null, trustAllCerts, new java.security.SecureRandom());
final Properties props = System.getProperties();
props.setProperty("jdk.internal.httpclient.disableHostnameVerification", Boolean.TRUE.toString());
httpClient = HttpClient.newBuilder()
.sslContext(sslContext)
.sslParameters(sslParameters)
.build();
}
public HttpClient getHttplClient() {
return httpClient;
}
TrustManager[] trustAllCerts = new TrustManager[] {
new X509TrustManager() {
public java.security.cert.X509Certificate[] getAcceptedIssuers() {
return new X509Certificate[0];
}
public void checkClientTrusted(
java.security.cert.X509Certificate[] certs, String authType) {
}
public void checkServerTrusted(
java.security.cert.X509Certificate[] certs, String authType) {
}
}
};
}
You can use the getHttplClient() function while calling your HttpRequest.
I had the same issue and this solution does not work for me.
Instead I saw this answer Android Enable TLSv1.2 in OKHttp and I tried this code:
ConnectionSpec spec = new ConnectionSpec
.Builder(ConnectionSpec.MODERN_TLS)
.tlsVersions(TlsVersion.TLS_1_2,TlsVersion.TLS_1_0,TlsVersion.TLS_1_1,TlsVersion.TLS_1_3).build();
client =client.newBuilder().connectionSpecs(Collections.singletonList(spec)).build();
And it worked for me:)
I think mmo's answer should be highlighted in bold. I had similar issue, but found out that the open-jdk jvm I was using has TLSv1 and TLSv1.1 as disabled in the jdk.tls.disabledAlgorithms line in java.security. So as soon as I removed it and restarted the JVM, I was able to connect usingthe older TLS protocols.
But please pay ATTENTION, This is not advisable in Production since it degrades the secure communication. So I'd say change it if you want at YOUR OWN RISK!!!

How to invoke a post when using HubController<T>?

I can't find much documentation on the new HubController<T> so maybe I'm going about this wrong. This is what I have:
public class StatusController : HubController<StatusHub>
{
private string _status = "";
public string Get()
{
return _status;
}
public void Post(string status)
{
_status = status;
// Call StatusChanged on SignalR clients listening to the StatusHub
Clients.All.StatusChanged(status);
}
}
public class StatusHub : Hub { }
This is how I'm attempting to create the hub proxy:
var hubConnection = new HubConnection("http://localhost:51076/");
var statusHubProxy = hubConnection.CreateHubProxy("StatusHub");
statusHubProxy.On<string>("StatusChanged", status => Console.WriteLine("New Status: {0}", status));
await hubConnection.Start();
How do I call the Post method of my controller? This is where I'm getting an exception:
await statusHubProxy.Invoke("Post", "Test Status");
HubController<T> just provides some basic plumbing that gets you access to the resources that are associated with the specific hub type (e.g. Clients) that you want to work with. Calling it has nothing to do with invoking the actual hub itself, so you don't use the hub client API, it's just straight HTTP calls. Without HubController<T> you would have to reach out to SignalR's GlobalHost.Configuration.GetHubContext<T>() yourself to find the IHubContext for your hub type.
So, you can call your StatusController::Post method with any of the standard .NET HTTP APIs: HttpClient, WebClient or HttpWebRequest.

Resources