. NETCore remote call

HttpClient
HttpClient is a special object. Although it inherits the IDisposable interface, it can be shared (or reused) and thread safe. From the project experience, it is recommended to reuse the HttpClient instance throughout the application life cycle, rather than instantiate one every time an RPC request occurs.
class Program
    {
        static void Main(string[] args)
        {
            HttpAsync();
            Console.WriteLine("Hello World!");
            Console.Read();
        }

        public static async void HttpAsync()
        {
            for (int i = 0; i < 10; i++)
            {
                using (var client = new HttpClient())
                {
                    var result = await client.GetAsync("http://www.baidu.com");
                    Console.WriteLine($"{i}:{result.StatusCode}");
                }
            }
        }
    }

 

 

 

Although the project has finished running, the connection still exists, with the status of "time"_ Wait "(keep waiting to see if there are any delayed packets to be transmitted.) , 240 seconds (4 minutes) before the connection is actually closed. For high concurrency scenarios, such as 1000 requests per second, each request uses HttpClient, which will stack 240000 tcp connections in 4 minutes, such a connection explosion will drag down the server. In order to avoid this pit, the usual workaround is to use static HttpClient, but it will bring another unknown problem. When the IP address corresponding to the host name requested by HttpClient changes, HttpClient will be kept in the dark until the application is restarted.

By default, under windows, time_ The wait state will keep the system connected for 240s.

This also leads to a pit I mentioned above: in the case of high concurrency, the connection is too late to release, the socket is exhausted, and a popular error will appear after exhaustion:


Solution reuse HttpClient




 

Ten become one.

Benefits:

1. As you can see, the original 10 connections have become one. (please don't mind that the target IP of the two examples is different -- SLB (load balancing) results in Baidu IP)

2. In addition, because of the reuse of HttpClient, each RPC request actually saves the time to create a channel, which is also a significant improvement in performance pressure testing. Once, because of this move, the TPS (system throughput) of the web project was increased from 600 to 2000 + in an instant, and the page request time was reduced from 1-3s to 100-300ms, which even made the test team members worship (including the fine tuning of some business codes, of course) , but after knowing the reason, the project performance improvement brought by a small change... Addictive:)

3. As for how to create a static HttpClient for reuse, you can create a "global" static object directly or through various DI frameworks according to the actual project.

Disadvantages:
1. Because it is a reused HttpClient, some public settings cannot be flexibly adjusted, such as the customization of request headers.
2. Because when HttpClient requests each url, the host ip corresponding to the url will be cached, which will lead to DNS update failure (TTL failure)
Is there any way to solve these problems of HttpClient? Until I met HttpClientFactory, I felt twice as happy to write code in an instant. I also felt that the children's shoes in the new era were really happy, and the holes the older generation stepped on could be "Perfectly" avoided.
 
Some uses:
public static async void HttpMul2Async()
        {
            //https://docs.microsoft.com/zh-cn/dotnet/api/system.net.http.httprequestmessage?redirectedfrom=MSDN&view=netframework-4.7.2
            var request = new HttpRequestMessage(HttpMethod.Get, "www.baidu.com");
            //request.RequestUri
            //request.Headers.Accept;
            //request.Headers.
            //HttpRequestHeaders hrh = new HttpRequestHeaders();
            //request.Method  
            //StreamContent sc = new StreamContent();
            MultipartFormDataContent mfdc = new MultipartFormDataContent();
            //mfdc.Add
            // mfdc.Headers

            //request.Content = 
            await _client.SendAsync(request);
            for (int i = 0; i < 10; i++)
            {
                var result = await _client.GetAsync("http://www.baidu.com");
                Console.WriteLine($"{i}:{result.StatusCode}");
            }
        }

 

Initial understanding of HttpClientFactory

Introduction:

ASP.NET New features in core 2.1. Installation package Microsoft.Extensions.Http 

Httpclientfactory is very efficient and can save system socket to the greatest extent.

Factory, as the name implies, HttpClientFactory is the factory of HttpClient. It has handled the management of HttpClient internally. It does not require us to release objects manually. At the same time, it supports custom request headers, DNS updates, etc.

According to Microsoft source code analysis, HttpClient inherits from HttpMessageInvoker, which is essentially HttpClientHandler.

HttpClient created by HttpClientFactory, that is, HttpClientHandler. However, these httpclients are put into the "pool". The factory will automatically determine whether to create or reuse each time it creates. (default life cycle is 2min)

If you still can't understand it, you can refer to the relationship between Task and Thread. When you met the problem of HttpClient, you always wondered when Microsoft would officially launch an HttpClient Factory. Although it took so many years until. NET CORE 2.1, it was also exciting.

Recommended articles

use:
I ASP.NET CORE MVC
1. Register httpclient
 public class Startup
    {
        public Startup(IConfiguration configuration)
        {
            Configuration = configuration;
        }

        public IConfiguration Configuration { get; }

        // This method gets called by the runtime. Use this method to add services to the container.
        public void ConfigureServices(IServiceCollection services)
        {
            //other codes
            
            services.AddHttpClient ("client_ 1 ", config = > / / name = client specified here_ 1. It is convenient for us to take this example later
            {
                config.BaseAddress= new Uri("http://client_1.com");
                config.DefaultRequestHeaders.Add("header_1","header_1");
            });
            services.AddHttpClient("client_2",config=>
            {
                config.BaseAddress= new Uri("http://client_2.com");
                config.DefaultRequestHeaders.Add("header_2","header_2");
            }).SetHandlerLifetime(TimeSpan.FromMinutes(5));;
            services.AddHttpClient();

            //other codes
            services.AddMvc().AddFluentValidation();
        }
      }

2. Use

public class TestController : ControllerBase
    {
        private readonly IHttpClientFactory _httpClient;
        public TestController(IHttpClientFactory httpClient)
        {
            _httpClient = httpClient;
        }

        public async Task<ActionResult> Test()
        {
            var client = _httpClient.CreateClient("client_1"); //Reuse in Startup Defined in client_1 Of httpclient
            var result = await client.GetStringAsync("/page1.html");

            var client2 = _httpClient.CreateClient(); //Create a new one HttpClient
            var result2 = await client.GetStringAsync("http://www.site.com/XXX.html");

            return null;
        }
    }

 

2, Custom request class

1. Define http request class

public class SampleClient
{
    public HttpClient Client { get; private set; }
    
    public SampleClient(HttpClient httpClient)
    {
        httpClient.BaseAddress = new Uri("https://api.SampleClient.com/");
        httpClient.DefaultRequestHeaders.Add("Accept", "application/json");
        httpClient.DefaultRequestHeaders.Add("User-Agent", "HttpClientFactory-Sample");
        Client = httpClient;
    }
}

2. Injection

services.AddHttpClient<SampleClient>();

3. Call

public class ValuesController : Controller
{
    private readonly SampleClient  _sampleClient;;
  
    public ValuesController(SampleClient  sampleClient)
    {
        _sampleClient = sampleClient;
    }
  
    [HttpGet]
    public async Task<ActionResult> Get()
    {
        string result = await  _sampleClient.client.GetStringAsync("/");
        return Ok(result);
    }
}

3, Implementation of custom request interface

1. Define request interface, request class

public interface ISampleClient
{
    Task<string> GetData();
}
 
public class SampleClient : ISampleClient
{
    private readonly HttpClient _client;
 
    public SampleClient(HttpClient httpClient)
    {
        httpClient.BaseAddress = new Uri("https://api.SampleClient.com/");
        httpClient.DefaultRequestHeaders.Add("Accept", "application/json");
        httpClient.DefaultRequestHeaders.Add("User-Agent", "HttpClientFactory-Sample");
        _client = httpClient;
    }
 
    public async Task<string> GetData()
    {
        return await _client.GetStringAsync("/");
    }
}

When BaseAddress is set, you can write the desired address directly when you request the address.

2. Injection

services.AddHttpClient<ISampleClient, SampleClient>();

3. Call

public class ValuesController : Controller
{
    private readonly ISampleClient  _sampleClient;;
     
    public ValuesController(ISampleClient  sampleClient)
    {
        _sampleClient = sampleClient;
    }
     
    [HttpGet]
    public async Task<ActionResult> Get()
    {
        string result = await _sampleClient.GetData();
        return Ok(result);
    }
}

 

HttpClientFactory advanced

Core functions:

• manage the life cycle of the internal HttpMessageHandler (manage socket links), flexibly respond to resource issues and DNS refresh issues

Support named and typed configuration, centralized management of configuration, and avoid conflict

• flexible outbound request pipeline configuration, easy to manage request lifecycle

• built in pipeline outermost and innermost loggers with Information and Trace output

Core objects:

• HttpClient

• HttpMessageHandler

• SocketsHttpHandler

• DelegatingHandler

• IHttpClientFactory

• IHttpClientBuilde

Pipe model:

 

 

Similar to the design pattern of middleware. The DelegatingHandler in the middle is middleware processing. The built-in middleware LoggingScopeHttp MesageHandler is located in the outer layer for logging. The innermost LoggingHttp MessageHandler records the inner log. In the middle are middleware that can be customized.

SocketsHttpHandler is where the real request is.

User defined pipeline

 

public class RequestIdDelegatingHandler : DelegatingHandler
    {
        protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
        {
            //Process request
            request.Headers.Add("x-guid", Guid.NewGuid().ToString());

            var result = await base.SendAsync(request, cancellationToken); //Call internal handler

            //Process response

            return result;
       
Request pipeline definition

 

public void ConfigureServices(IServiceCollection services)
        {
            services.AddMvc().AddControllersAsServices();

            services.AddHttpClient();
            services.AddScoped<OrderServiceClient>();
            services.AddSingleton<RequestIdDelegatingHandler>();
            services.AddHttpClient("NamedOrderServiceClient", client =>
            {
                client.DefaultRequestHeaders.Add("client-name", "namedclient");
                client.BaseAddress = new Uri("https://localhost:5003");
            }).SetHandlerLifetime(TimeSpan.FromMinutes(20))
            .AddHttpMessageHandler(provider => provider.GetService<RequestIdDelegatingHandler>());
            

            services.AddScoped<NamedOrderServiceClient>();
            services.AddHttpClient<TypedOrderServiceClient>(client =>
            {
                client.BaseAddress = new Uri("https://localhost:5003");
            });

        }
Register custom request pipeline

.AddHttpMessageHandler(provider => provider.GetService<RequestIdDelegatingHandler>());

 

 

Create HttpClient

1. Factory mode

public class OrderServiceClient
    {
        IHttpClientFactory _httpClientFactory;

        public OrderServiceClient(IHttpClientFactory httpClientFactory)
        {
            _httpClientFactory = httpClientFactory;
        }


        public async Task<string> Get()
        {
            var client = _httpClientFactory.CreateClient();

            //use client launch HTTP request
            return await client.GetStringAsync("https://localhost:5003/OrderService");
        }
    }
_httpClientFactory.CreateClient();

 

2. Named client mode

public void ConfigureServices(IServiceCollection services)
        {
            services.AddMvc().AddControllersAsServices();

            services.AddHttpClient();
            services.AddScoped<OrderServiceClient>();
            services.AddSingleton<RequestIdDelegatingHandler>();
            services.AddHttpClient("NamedOrderServiceClient", client =>
            {
                client.DefaultRequestHeaders.Add("client-name", "namedclient");
                client.BaseAddress = new Uri("https://localhost:5003");
            }).SetHandlerLifetime(TimeSpan.FromMinutes(20))
            .AddHttpMessageHandler(provider => provider.GetService<RequestIdDelegatingHandler>());
            

            services.AddScoped<NamedOrderServiceClient>();
            services.AddHttpClient<TypedOrderServiceClient>(client =>
            {
                client.BaseAddress = new Uri("https://localhost:5003");
            });

        }
View Code

services.AddHttpClient("NamedOrderServiceClient",

The first parameter is the name, and the second is the default configuration.

Where to get httpclient

A kind of httpClientFactory.CreateClient("name");

Advantage: named clients can be configured with different clients for different services. Different clients have different http default configurations. Everyone's socket s are managed separately.

3. Type client mode (Recommended Practice)

In essence, the name is the only difference between the name of the httpclient and the named client. The advantage does not need the string name to get.

Client definition

public class TypedOrderServiceClient
    {
        HttpClient _client;
        public TypedOrderServiceClient(HttpClient client)
        {
            _client = client;
        }


        public async Task<string> Get()
        {
           return  await _client.GetStringAsync("/OrderService"); //Use relative path to access
        }
    }

Service injection

services.AddHttpClient<TypedOrderServiceClient>(client =>
            {
                client.BaseAddress = new Uri("https://localhost:5003");
            });

 --------------------------------------------------------------------------------------------------------------------------------------------------------------

Next, gRPC

Grpc

What is Grpc:

https://baijiahao.baidu.com/s?id=1633335936037018920&wfr=spider&for=pc

Remote call framework

google initiated and open source

Grpc features:

• provide the implementation of almost all mainstream languages and break the language gap

The client can use one language, the server can use one language

• based on HTTP/2, open protocol, widely supported, easy to implement and integrate

• Protocol Buffers serialization is used by default, with much better performance than RESTful Json

Mature tool chain, convenient code generation, out of the box use

It supports two-way flow request and response, and is friendly to batch processing and low latency scenarios

 

Feel it. The soap experience is coming

Grpc support for. NET

• provide native framework implementation based on HttpClient

• provide native ASP.NET Core integration Library

• provide complete code generation tools

• Visual Studio and Visual StuidoCode provide intelligent prompt of proto file

. NET server reference package

Grpc.AspNetCore

. NET client reference package

• Google.Protobuf

Package of serialization protocol

• Grpc.Net.Client

Client package

• Grpc.Net.ClientFactory

Introducing httpclientfactory

• Grpc.Tools

A package of command line tools is provided to generate our client and server code based on the. proto file

. proto file

• define package and library name

• define service

• define I / O model "message“

 

This file can generate server code and client code

gRPC exception handling

• use Grpc.Core.RpcException

• use Grpc.Core.Interceptors.Interceptor

gRPC and HTTPS certificate

• use of self-made certificates

• use unencrypted HTTP2

Introduction to proto file

. proto file

syntax = "proto3";

option csharp_namespace = "GrpcServices";

package GrpcServices;

service OrderGrpc {
    rpc CreateOrder(CreateOrderCommand) returns (CreateOrderResult);
}


message CreateOrderCommand {
    string buyerId = 1;
    int32 productId = 2;
    double unitPrice = 3;
    double discount = 4;
    int32 units = 5;
}

message CreateOrderResult {
    int32 orderId = 1;
}

syntax = "proto3";

Using proto 3 protocol

 

option csharp_namespace = "GrpcServices";

Indicates that the namespace is GrpcServices

 

package GrpcServices;

The package defines a scope to prevent naming conflicts between different message types. Just like the name of the line assembly

 

 

 

service OrderGrpc {
rpc CreateOrder(CreateOrderCommand) returns (CreateOrderResult);
}

Define a service name OrderGrpc. The service has a method called CreateOrder

 

message CreateOrderCommand {
string buyerId = 1;
int32 productId = 2;
double unitPrice = 3;
double discount = 4;
int32 units = 5;
}





message CreateOrderResult {
int32 orderId = 1;
}

The data model is called message. The 1, 2, 3 and 4 in the data model are the field serialization order, which is also used to match the field name.

-------------------------------------------------------------------------------------------------------------------------------------------

When we finish a. proto, it will automatically generate the corresponding code for us. You can see the generated code and the configuration file for comparison.

 

https://www.jianshu.com/p/da7ed5914088

https://www.cnblogs.com/tohxyblog/p/8974763.html

Server definition

syntax = "proto3";

option csharp_namespace = "GrpcServer";

package orderser;

service OrderGrpc {
    rpc CreateOrder(CreateOrderCommand) returns (CreateOrderResult);
}


message CreateOrderCommand {
    string buyerId = 1;
    int32 productId = 2;
    double unitPrice = 3;
    double discount = 4;
    int32 units = 5;
}

message CreateOrderResult {
    int32 orderId = 1;
}
proto definition

 

public class OrderService : OrderGrpc.OrderGrpcBase
    {
        private readonly ILogger<GreeterService> _logger;
        public OrderService(ILogger<GreeterService> logger)
        {
            _logger = logger;
        }

        public override Task<CreateOrderResult> CreateOrder(CreateOrderCommand request, ServerCallContext context)
        {
            throw new System.Exception("order error");

            //Add the internal logic of order creation, input and store the order information in the database
            return Task.FromResult(new CreateOrderResult { OrderId = 24 });
        }
    }
service

 

public void ConfigureServices(IServiceCollection services)
        {
            services.AddGrpc(
                option => {
                    //Production environment shut down
                    option.EnableDetailedErrors = false;
                    //Exception Interceptor 
                    option.Interceptors.Add<ExceptionInterceptor>();
                }
                );
        }
Service injection

 

app.UseEndpoints(endpoints =>
            {
                //Endpoint map
                endpoints.MapGrpcService<GreeterService>();
                endpoints.MapGrpcService<OrderService>();

                endpoints.MapGet("/", async context =>
                {
                    await context.Response.WriteAsync("Communication with gRPC endpoints must be made through a gRPC client. To learn how to create a client, visit: https://go.microsoft.com/fwlink/?linkid=2086909");
                });
            });
middleware

Project Download

Client definition

The client belongs to the caller, and the client belongs to any program. The main purpose is to generate gRPC code and call gRPC just like local method. (the language of client and server can be different)

Console call

 

Reference package Google.Protobuf             Grpc.Net.Client             Grpc.Tools

Project Download

Add the grpc configuration file, which can be copied from the server. Set the application of the project to the file

<ItemGroup>
    <Protobuf Include="Protos\greet.proto" GrpcServices="Client" />
    <Protobuf Include="Protos\order.proto" GrpcServices="Client" />
  </ItemGroup>

call

static async Task Main(string[] args)
        {
            var channel = GrpcChannel.ForAddress("https://localhost:5001");
            var client = new Greeter.GreeterClient(channel);
            var reply = await client.SayHelloAsync(new HelloRequest { Name = "Siberian Wolf" });
            Console.WriteLine("Greeter Service return data: " + reply.Message);
            Console.ReadKey();


            Console.WriteLine("Hello World!");
        }

WEB project call

 

Project Download

Introduction package Google.Protobuf             Grpc.Net.Client             Grpc.Tools         Grpc.Net.ClientFactory            Grpc.AspNetCore

Add the grpc configuration file, which can be copied from the server. Set the application of the project to the file

<ItemGroup>
    <Protobuf Include="Protos\greet.proto" GrpcServices="Client" />
    <Protobuf Include="Protos\order.proto" GrpcServices="Client" />
  </ItemGroup>

Service injection

Like addhttpclient, addgrpcclient

services.AddGrpcClient<OrderGrpc.OrderGrpcClient>(options =>
            {
                options.Address = new Uri("https://localhost:5001");
            });

Grpc uses http2, which is based on https. So how can we change to HTTP? This can use grpc without configuring i certificate.

AppContext.SetSwitch("System.Net.Http.SocketsHttpHandler.Http2UnencryptedSupport", true); //Allow unencrypted HTTP/2 agreement
            services.AddGrpcClient<OrderGrpc.OrderGrpcClient>(options =>
            {
                options.Address = new Uri("http://localhost:5002");
            })

There is also the handling of sub signing certificates

 

 

Controller call

[Route("api/[controller]")]
    [ApiController]
    public class GRPCClientController : ControllerBase
    {
        private readonly OrderGrpcClient _orderclient;
        public GRPCClientController(OrderGrpcClient orderclient)
        {
            _orderclient = orderclient;
        }

        [HttpGet]
        public async Task<IActionResult> Get()
        {
            var r = _orderclient.CreateOrder(new CreateOrderCommand { BuyerId = "abc" });
            return Ok(r.OrderId);
        }
    }

 

Server exception

public void ConfigureServices(IServiceCollection services)
        {
            services.AddGrpc(options =>
            {
                options.EnableDetailedErrors = true;
                options.Interceptors.Add<ExceptionInterceptor>();
            });
        }

Tags: socket Google DNS JSON

Posted on Tue, 23 Jun 2020 01:02:20 -0400 by socio