IdentityServer里有各种Endpoint,如TokenEndpoint,UserInfoEndpoint,Authorize Endpoint,Discovery Endpoint等等。Endpoint从字面意思来看是“终端节点"或者“终节点”的意思。无独有偶NetCore的路由也有Endpoint的概念。那么我们提出一个问题来,究竟什么是Endpoint?

先来看一段代码,如下所示:

public class Program
{
public static void Main(string[] args)
{
CreateHostBuilder(args).Build().Run();
} public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.ConfigureWebHostDefaults(webBuilder =>
{
webBuilder.ConfigureServices(svcs => svcs.AddRouting()).Configure( app => app.UseRouting().UseEndpoints(
//test1
//endpoints => endpoints.MapGet("weather/{city}/{days}", //test2
endpoints =>endpoints.MapGet("weather/{city}/{year}.{month}.{day}", WeatherForecast) ));
}); private static Dictionary<string, string> _cities = new Dictionary<string, string>() {
["001"]="Beijing",
["028"]="Chengdu",
["091"]="Suzhou" }; //RequestDelegate
public static async Task WeatherForecast(HttpContext context)
{
var city = (string)context.GetRouteData().Values["city"];
city = _cities[city]; //test1
//int days=int.Parse(context.GetRouteData().Values["days"].ToString());
//var report = new WeatherReport(city, days); //test2
int year = int.Parse(context.GetRouteData().Values["year"].ToString());
int month= int.Parse(context.GetRouteData().Values["month"].ToString());
int day=int.Parse(context.GetRouteData().Values["day"].ToString());
var report = new WeatherReport(city, new DateTime(year, month, day));
await RenderWeatherAsync(context, report);
} private static async Task RenderWeatherAsync(HttpContext context,WeatherReport report)
{
context.Response.ContentType = "text/html;charset=utf-8";
await context.Response.WriteAsync("<html><head><title>Weather</title></head><body>");
await context.Response.WriteAsync($"<h3>{report.City}</h3>");
foreach(var it in report.WeatherInfos)
{
await context.Response.WriteAsync($"{it.Key.ToString("yyyy-MM-dd")}: ");
await context.Response.WriteAsync($"{it.Value.LowTemperature}--{it.Value.HighTemperature} <br/>");
}
await context.Response.WriteAsync("</body></html>");
}
}

上面的代码很清晰,如果路由格式匹配”weather/{city}/{year}.{month}.{day}”,那么就调用WeatherForecast方法,这里的app.UseRouting().UseEndpoints就用到了Endpoint模式。要了解Endpoint,我们需要抽丝剥茧一步一步了解几个类的方法:

1.我们先看最里面的endpoints =>endpoints.MapGet("weather/{city}/{year}.{month}.{day}", WeatherForecast),这里实际上调用的是EndpointRouteBuilderExtensions类的MapGet方法,代码如下所示:

    /// <summary>
/// Provides extension methods for <see cref="IEndpointRouteBuilder"/> to add endpoints.
/// </summary>
public static class EndpointRouteBuilderExtensions
{
// Avoid creating a new array every call
private static readonly string[] GetVerb = new[] { "GET" };
private static readonly string[] PostVerb = new[] { "POST" };
private static readonly string[] PutVerb = new[] { "PUT" };
private static readonly string[] DeleteVerb = new[] { "DELETE" };
private static readonly string[] PatchVerb = new[] { "PATCH" };
/// <summary>
/// Adds a <see cref="RouteEndpoint"/> to the <see cref="IEndpointRouteBuilder"/> that matches HTTP GET requests
/// for the specified pattern.
/// </summary>
/// <param name="endpoints">The <see cref="IEndpointRouteBuilder"/> to add the route to.</param>
/// <param name="pattern">The route pattern.</param>
/// <param name="requestDelegate">The delegate executed when the endpoint is matched.</param>
/// <returns>A <see cref="IEndpointConventionBuilder"/> that can be used to further customize the endpoint.</returns>
public static IEndpointConventionBuilder MapGet(
this IEndpointRouteBuilder endpoints,
string pattern,
RequestDelegate requestDelegate)
{
return MapMethods(endpoints, pattern, GetVerb, requestDelegate);
} /// <summary>
/// Adds a <see cref="RouteEndpoint"/> to the <see cref="IEndpointRouteBuilder"/> that matches HTTP POST requests
/// for the specified pattern.
/// </summary>
/// <param name="endpoints">The <see cref="IEndpointRouteBuilder"/> to add the route to.</param>
/// <param name="pattern">The route pattern.</param>
/// <param name="requestDelegate">The delegate executed when the endpoint is matched.</param>
/// <returns>A <see cref="IEndpointConventionBuilder"/> that can be used to further customize the endpoint.</returns>
public static IEndpointConventionBuilder MapPost(
this IEndpointRouteBuilder endpoints,
string pattern,
RequestDelegate requestDelegate)
{
return MapMethods(endpoints, pattern, PostVerb, requestDelegate);
} /// <summary>
/// Adds a <see cref="RouteEndpoint"/> to the <see cref="IEndpointRouteBuilder"/> that matches HTTP PUT requests
/// for the specified pattern.
/// </summary>
/// <param name="endpoints">The <see cref="IEndpointRouteBuilder"/> to add the route to.</param>
/// <param name="pattern">The route pattern.</param>
/// <param name="requestDelegate">The delegate executed when the endpoint is matched.</param>
/// <returns>A <see cref="IEndpointConventionBuilder"/> that can be used to further customize the endpoint.</returns>
public static IEndpointConventionBuilder MapPut(
this IEndpointRouteBuilder endpoints,
string pattern,
RequestDelegate requestDelegate)
{
return MapMethods(endpoints, pattern, PutVerb, requestDelegate);
} /// <summary>
/// Adds a <see cref="RouteEndpoint"/> to the <see cref="IEndpointRouteBuilder"/> that matches HTTP DELETE requests
/// for the specified pattern.
/// </summary>
/// <param name="endpoints">The <see cref="IEndpointRouteBuilder"/> to add the route to.</param>
/// <param name="pattern">The route pattern.</param>
/// <param name="requestDelegate">The delegate executed when the endpoint is matched.</param>
/// <returns>A <see cref="IEndpointConventionBuilder"/> that can be used to further customize the endpoint.</returns>
public static IEndpointConventionBuilder MapDelete(
this IEndpointRouteBuilder endpoints,
string pattern,
RequestDelegate requestDelegate)
{
return MapMethods(endpoints, pattern, DeleteVerb, requestDelegate);
} /// <summary>
/// Adds a <see cref="RouteEndpoint"/> to the <see cref="IEndpointRouteBuilder"/> that matches HTTP PATCH requests
/// for the specified pattern.
/// </summary>
/// <param name="endpoints">The <see cref="IEndpointRouteBuilder"/> to add the route to.</param>
/// <param name="pattern">The route pattern.</param>
/// <param name="requestDelegate">The delegate executed when the endpoint is matched.</param>
/// <returns>A <see cref="IEndpointConventionBuilder"/> that can be used to further customize the endpoint.</returns>
public static IEndpointConventionBuilder MapPatch(
this IEndpointRouteBuilder endpoints,
string pattern,
RequestDelegate requestDelegate)
{
return MapMethods(endpoints, pattern, PatchVerb, requestDelegate);
} /// <summary>
/// Adds a <see cref="RouteEndpoint"/> to the <see cref="IEndpointRouteBuilder"/> that matches HTTP requests
/// for the specified HTTP methods and pattern.
/// </summary>
/// <param name="endpoints">The <see cref="IEndpointRouteBuilder"/> to add the route to.</param>
/// <param name="pattern">The route pattern.</param>
/// <param name="requestDelegate">The delegate executed when the endpoint is matched.</param>
/// <param name="httpMethods">HTTP methods that the endpoint will match.</param>
/// <returns>A <see cref="IEndpointConventionBuilder"/> that can be used to further customize the endpoint.</returns>
public static IEndpointConventionBuilder MapMethods(
this IEndpointRouteBuilder endpoints,
string pattern,
IEnumerable<string> httpMethods,
RequestDelegate requestDelegate)
{
if (httpMethods == null)
{
throw new ArgumentNullException(nameof(httpMethods));
} var builder = endpoints.Map(RoutePatternFactory.Parse(pattern), requestDelegate);
builder.WithDisplayName($"{pattern} HTTP: {string.Join(", ", httpMethods)}");
builder.WithMetadata(new HttpMethodMetadata(httpMethods));
return builder;
} /// <summary>
/// Adds a <see cref="RouteEndpoint"/> to the <see cref="IEndpointRouteBuilder"/> that matches HTTP requests
/// for the specified pattern.
/// </summary>
/// <param name="endpoints">The <see cref="IEndpointRouteBuilder"/> to add the route to.</param>
/// <param name="pattern">The route pattern.</param>
/// <param name="requestDelegate">The delegate executed when the endpoint is matched.</param>
/// <returns>A <see cref="IEndpointConventionBuilder"/> that can be used to further customize the endpoint.</returns>
public static IEndpointConventionBuilder Map(
this IEndpointRouteBuilder endpoints,
string pattern,
RequestDelegate requestDelegate)
{
return Map(endpoints, RoutePatternFactory.Parse(pattern), requestDelegate);
} /// <summary>
/// Adds a <see cref="RouteEndpoint"/> to the <see cref="IEndpointRouteBuilder"/> that matches HTTP requests
/// for the specified pattern.
/// </summary>
/// <param name="endpoints">The <see cref="IEndpointRouteBuilder"/> to add the route to.</param>
/// <param name="pattern">The route pattern.</param>
/// <param name="requestDelegate">The delegate executed when the endpoint is matched.</param>
/// <returns>A <see cref="IEndpointConventionBuilder"/> that can be used to further customize the endpoint.</returns>
public static IEndpointConventionBuilder Map(
this IEndpointRouteBuilder endpoints,
RoutePattern pattern,
RequestDelegate requestDelegate)
{
if (endpoints == null)
{
throw new ArgumentNullException(nameof(endpoints));
} if (pattern == null)
{
throw new ArgumentNullException(nameof(pattern));
} if (requestDelegate == null)
{
throw new ArgumentNullException(nameof(requestDelegate));
} const int defaultOrder = 0; //实例化RouteEndpointBuilder
var builder = new RouteEndpointBuilder(
requestDelegate,
pattern,
defaultOrder)
{
DisplayName = pattern.RawText ?? pattern.DebuggerToString(),
}; // Add delegate attributes as metadata
var attributes = requestDelegate.Method.GetCustomAttributes(); // This can be null if the delegate is a dynamic method or compiled from an expression tree
if (attributes != null)
{
foreach (var attribute in attributes)
{
builder.Metadata.Add(attribute);
}
} //Adds a RouteEndpoint to the IEndpointRouteBuilder that matches HTTP requests
/// for the specified pattern.
///可以理解为:将某种格式的路由pattern添加至IEndpointRouteBuilder的DataSource属性,为match做准备
var dataSource = endpoints.DataSources.OfType<ModelEndpointDataSource>().FirstOrDefault();
if (dataSource == null)
{
dataSource = new ModelEndpointDataSource();
endpoints.DataSources.Add(dataSource);
} return dataSource.AddEndpointBuilder(builder);
} /// <summary>
/// Adds a <see cref="RouteEndpoint"/> to the <see cref="IEndpointRouteBuilder"/> that matches HTTP GET requests
/// for the specified pattern.
/// </summary>
/// <param name="endpoints">The <see cref="IEndpointRouteBuilder"/> to add the route to.</param>
/// <param name="pattern">The route pattern.</param>
/// <param name="handler">The delegate executed when the endpoint is matched.</param>
/// <returns>A <see cref="RouteHandlerBuilder"/> that can be used to further customize the endpoint.</returns>
public static RouteHandlerBuilder MapGet(
this IEndpointRouteBuilder endpoints,
string pattern,
Delegate handler)
{
return MapMethods(endpoints, pattern, GetVerb, handler);
} /// <summary>
/// Adds a <see cref="RouteEndpoint"/> to the <see cref="IEndpointRouteBuilder"/> that matches HTTP POST requests
/// for the specified pattern.
/// </summary>
/// <param name="endpoints">The <see cref="IEndpointRouteBuilder"/> to add the route to.</param>
/// <param name="pattern">The route pattern.</param>
/// <param name="handler">The delegate executed when the endpoint is matched.</param>
/// <returns>A <see cref="RouteHandlerBuilder"/> that can be used to further customize the endpoint.</returns>
public static RouteHandlerBuilder MapPost(
this IEndpointRouteBuilder endpoints,
string pattern,
Delegate handler)
{
return MapMethods(endpoints, pattern, PostVerb, handler);
} /// <summary>
/// Adds a <see cref="RouteEndpoint"/> to the <see cref="IEndpointRouteBuilder"/> that matches HTTP PUT requests
/// for the specified pattern.
/// </summary>
/// <param name="endpoints">The <see cref="IEndpointRouteBuilder"/> to add the route to.</param>
/// <param name="pattern">The route pattern.</param>
/// <param name="handler">The delegate executed when the endpoint is matched.</param>
/// <returns>A <see cref="RouteHandlerBuilder"/> that can be used to further customize the endpoint.</returns>
public static RouteHandlerBuilder MapPut(
this IEndpointRouteBuilder endpoints,
string pattern,
Delegate handler)
{
return MapMethods(endpoints, pattern, PutVerb, handler);
} /// <summary>
/// Adds a <see cref="RouteEndpoint"/> to the <see cref="IEndpointRouteBuilder"/> that matches HTTP DELETE requests
/// for the specified pattern.
/// </summary>
/// <param name="endpoints">The <see cref="IEndpointRouteBuilder"/> to add the route to.</param>
/// <param name="pattern">The route pattern.</param>
/// <param name="handler">The delegate executed when the endpoint is matched.</param>
/// <returns>A <see cref="RouteHandlerBuilder"/> that can be used to further customize the endpoint.</returns>
public static RouteHandlerBuilder MapDelete(
this IEndpointRouteBuilder endpoints,
string pattern,
Delegate handler)
{
return MapMethods(endpoints, pattern, DeleteVerb, handler);
} /// <summary>
/// Adds a <see cref="RouteEndpoint"/> to the <see cref="IEndpointRouteBuilder"/> that matches HTTP PATCH requests
/// for the specified pattern.
/// </summary>
/// <param name="endpoints">The <see cref="IEndpointRouteBuilder"/> to add the route to.</param>
/// <param name="pattern">The route pattern.</param>
/// <param name="handler">The <see cref="Delegate" /> executed when the endpoint is matched.</param>
/// <returns>A <see cref="RouteHandlerBuilder"/> that can be used to further customize the endpoint.</returns>
public static RouteHandlerBuilder MapPatch(
this IEndpointRouteBuilder endpoints,
string pattern,
Delegate handler)
{
return MapMethods(endpoints, pattern, PatchVerb, handler);
} /// <summary>
/// Adds a <see cref="RouteEndpoint"/> to the <see cref="IEndpointRouteBuilder"/> that matches HTTP requests
/// for the specified HTTP methods and pattern.
/// </summary>
/// <param name="endpoints">The <see cref="IEndpointRouteBuilder"/> to add the route to.</param>
/// <param name="pattern">The route pattern.</param>
/// <param name="handler">The delegate executed when the endpoint is matched.</param>
/// <param name="httpMethods">HTTP methods that the endpoint will match.</param>
/// <returns>A <see cref="RouteHandlerBuilder"/> that can be used to further customize the endpoint.</returns>
public static RouteHandlerBuilder MapMethods(
this IEndpointRouteBuilder endpoints,
string pattern,
IEnumerable<string> httpMethods,
Delegate handler)
{
if (httpMethods is null)
{
throw new ArgumentNullException(nameof(httpMethods));
} var disableInferredBody = false;
foreach (var method in httpMethods)
{
disableInferredBody = ShouldDisableInferredBody(method);
if (disableInferredBody is true)
{
break;
}
} var builder = endpoints.Map(RoutePatternFactory.Parse(pattern), handler, disableInferredBody);
// Prepends the HTTP method to the DisplayName produced with pattern + method name
builder.Add(b => b.DisplayName = $"HTTP: {string.Join(", ", httpMethods)} {b.DisplayName}");
builder.WithMetadata(new HttpMethodMetadata(httpMethods));
return builder; static bool ShouldDisableInferredBody(string method)
{
// GET, DELETE, HEAD, CONNECT, TRACE, and OPTIONS normally do not contain bodies
return method.Equals(HttpMethods.Get, StringComparison.Ordinal) ||
method.Equals(HttpMethods.Delete, StringComparison.Ordinal) ||
method.Equals(HttpMethods.Head, StringComparison.Ordinal) ||
method.Equals(HttpMethods.Options, StringComparison.Ordinal) ||
method.Equals(HttpMethods.Trace, StringComparison.Ordinal) ||
method.Equals(HttpMethods.Connect, StringComparison.Ordinal);
}
} /// <summary>
/// Adds a <see cref="RouteEndpoint"/> to the <see cref="IEndpointRouteBuilder"/> that matches HTTP requests
/// for the specified pattern.
/// </summary>
/// <param name="endpoints">The <see cref="IEndpointRouteBuilder"/> to add the route to.</param>
/// <param name="pattern">The route pattern.</param>
/// <param name="handler">The delegate executed when the endpoint is matched.</param>
/// <returns>A <see cref="RouteHandlerBuilder"/> that can be used to further customize the endpoint.</returns>
public static RouteHandlerBuilder Map(
this IEndpointRouteBuilder endpoints,
string pattern,
Delegate handler)
{
return Map(endpoints, RoutePatternFactory.Parse(pattern), handler);
} /// <summary>
/// Adds a <see cref="RouteEndpoint"/> to the <see cref="IEndpointRouteBuilder"/> that matches HTTP requests
/// for the specified pattern.
/// </summary>
/// <param name="endpoints">The <see cref="IEndpointRouteBuilder"/> to add the route to.</param>
/// <param name="pattern">The route pattern.</param>
/// <param name="handler">The delegate executed when the endpoint is matched.</param>
/// <returns>A <see cref="RouteHandlerBuilder"/> that can be used to further customize the endpoint.</returns>
public static RouteHandlerBuilder Map(
this IEndpointRouteBuilder endpoints,
RoutePattern pattern,
Delegate handler)
{
return Map(endpoints, pattern, handler, disableInferBodyFromParameters: false);
} /// <summary>
/// Adds a specialized <see cref="RouteEndpoint"/> to the <see cref="IEndpointRouteBuilder"/> that will match
/// requests for non-file-names with the lowest possible priority.
/// </summary>
/// <param name="endpoints">The <see cref="IEndpointRouteBuilder"/> to add the route to.</param>
/// <param name="handler">The delegate executed when the endpoint is matched.</param>
/// <returns>A <see cref="RouteHandlerBuilder"/> that can be used to further customize the endpoint.</returns>
/// <remarks>
/// <para>
/// <see cref="MapFallback(IEndpointRouteBuilder, Delegate)"/> is intended to handle cases where URL path of
/// the request does not contain a file name, and no other endpoint has matched. This is convenient for routing
/// requests for dynamic content to a SPA framework, while also allowing requests for non-existent files to
/// result in an HTTP 404.
/// </para>
/// <para>
/// <see cref="MapFallback(IEndpointRouteBuilder, Delegate)"/> registers an endpoint using the pattern
/// <c>{*path:nonfile}</c>. The order of the registered endpoint will be <c>int.MaxValue</c>.
/// </para>
/// </remarks>
public static RouteHandlerBuilder MapFallback(this IEndpointRouteBuilder endpoints, Delegate handler)
{
if (endpoints == null)
{
throw new ArgumentNullException(nameof(endpoints));
} if (handler == null)
{
throw new ArgumentNullException(nameof(handler));
} return endpoints.MapFallback("{*path:nonfile}", handler);
} /// <summary>
/// Adds a specialized <see cref="RouteEndpoint"/> to the <see cref="IEndpointRouteBuilder"/> that will match
/// the provided pattern with the lowest possible priority.
/// </summary>
/// <param name="endpoints">The <see cref="IEndpointRouteBuilder"/> to add the route to.</param>
/// <param name="pattern">The route pattern.</param>
/// <param name="handler">The delegate executed when the endpoint is matched.</param>
/// <returns>A <see cref="RouteHandlerBuilder"/> that can be used to further customize the endpoint.</returns>
/// <remarks>
/// <para>
/// <see cref="MapFallback(IEndpointRouteBuilder, string, Delegate)"/> is intended to handle cases where no
/// other endpoint has matched. This is convenient for routing requests to a SPA framework.
/// </para>
/// <para>
/// The order of the registered endpoint will be <c>int.MaxValue</c>.
/// </para>
/// <para>
/// This overload will use the provided <paramref name="pattern"/> verbatim. Use the <c>:nonfile</c> route constraint
/// to exclude requests for static files.
/// </para>
/// </remarks>
public static RouteHandlerBuilder MapFallback(
this IEndpointRouteBuilder endpoints,
string pattern,
Delegate handler)
{
if (endpoints == null)
{
throw new ArgumentNullException(nameof(endpoints));
} if (pattern == null)
{
throw new ArgumentNullException(nameof(pattern));
} if (handler == null)
{
throw new ArgumentNullException(nameof(handler));
} var conventionBuilder = endpoints.Map(pattern, handler);
conventionBuilder.WithDisplayName("Fallback " + pattern);
conventionBuilder.Add(b => ((RouteEndpointBuilder)b).Order = int.MaxValue);
return conventionBuilder;
} private static RouteHandlerBuilder Map(
this IEndpointRouteBuilder endpoints,
RoutePattern pattern,
Delegate handler,
bool disableInferBodyFromParameters)
{
if (endpoints is null)
{
throw new ArgumentNullException(nameof(endpoints));
} if (pattern is null)
{
throw new ArgumentNullException(nameof(pattern));
} if (handler is null)
{
throw new ArgumentNullException(nameof(handler));
} const int defaultOrder = 0; var routeParams = new List<string>(pattern.Parameters.Count);
foreach (var part in pattern.Parameters)
{
routeParams.Add(part.Name);
} var routeHandlerOptions = endpoints.ServiceProvider?.GetService<IOptions<RouteHandlerOptions>>(); var options = new RequestDelegateFactoryOptions
{
ServiceProvider = endpoints.ServiceProvider,
RouteParameterNames = routeParams,
ThrowOnBadRequest = routeHandlerOptions?.Value.ThrowOnBadRequest ?? false,
DisableInferBodyFromParameters = disableInferBodyFromParameters,
}; var requestDelegateResult = RequestDelegateFactory.Create(handler, options); var builder = new RouteEndpointBuilder(
requestDelegateResult.RequestDelegate,
pattern,
defaultOrder)
{
DisplayName = pattern.RawText ?? pattern.DebuggerToString(),
}; // REVIEW: Should we add an IActionMethodMetadata with just MethodInfo on it so we are
// explicit about the MethodInfo representing the "handler" and not the RequestDelegate? // Add MethodInfo as metadata to assist with OpenAPI generation for the endpoint.
builder.Metadata.Add(handler.Method); // Methods defined in a top-level program are generated as statics so the delegate
// target will be null. Inline lambdas are compiler generated method so they can
// be filtered that way.
if (GeneratedNameParser.TryParseLocalFunctionName(handler.Method.Name, out var endpointName)
|| !TypeHelper.IsCompilerGeneratedMethod(handler.Method))
{
endpointName ??= handler.Method.Name;
builder.DisplayName = $"{builder.DisplayName} => {endpointName}";
} // Add delegate attributes as metadata
var attributes = handler.Method.GetCustomAttributes(); // Add add request delegate metadata
foreach (var metadata in requestDelegateResult.EndpointMetadata)
{
builder.Metadata.Add(metadata);
} // This can be null if the delegate is a dynamic method or compiled from an expression tree
if (attributes is not null)
{
foreach (var attribute in attributes)
{
builder.Metadata.Add(attribute);
}
} var dataSource = endpoints.DataSources.OfType<ModelEndpointDataSource>().FirstOrDefault();
if (dataSource is null)
{
dataSource = new ModelEndpointDataSource();
endpoints.DataSources.Add(dataSource);
} return new RouteHandlerBuilder(dataSource.AddEndpointBuilder(builder));
}
}
}

在这里我们重点看看下面签名的方法:

 /// <summary>
/// Adds a <see cref="RouteEndpoint"/> to the <see cref="IEndpointRouteBuilder"/> that matches HTTP requests
/// for the specified pattern.
/// </summary>
/// <param name="endpoints">The <see cref="IEndpointRouteBuilder"/> to add the route to.</param>
/// <param name="pattern">The route pattern.</param>
/// <param name="requestDelegate">The delegate executed when the endpoint is matched.</param>
/// <returns>A <see cref="IEndpointConventionBuilder"/> that can be used to further customize the endpoint.</returns>
public static IEndpointConventionBuilder Map(
this IEndpointRouteBuilder endpoints,
RoutePattern pattern,
RequestDelegate requestDelegate)
{
if (endpoints == null)
{
throw new ArgumentNullException(nameof(endpoints));
} if (pattern == null)
{
throw new ArgumentNullException(nameof(pattern));
} if (requestDelegate == null)
{
throw new ArgumentNullException(nameof(requestDelegate));
} const int defaultOrder = 0; //实例化RouteEndpointBuilder
var builder = new RouteEndpointBuilder(
requestDelegate,
pattern,
defaultOrder)
{
DisplayName = pattern.RawText ?? pattern.DebuggerToString(),
}; // Add delegate attributes as metadata
var attributes = requestDelegate.Method.GetCustomAttributes(); // This can be null if the delegate is a dynamic method or compiled from an expression tree
if (attributes != null)
{
foreach (var attribute in attributes)
{
builder.Metadata.Add(attribute);
}
} //Adds a RouteEndpoint to the IEndpointRouteBuilder that matches HTTP requests
/// for the specified pattern.
///可以理解为:将某种格式的路由pattern添加至IEndpointRouteBuilder的DataSource属性,为match做准备
var dataSource = endpoints.DataSources.OfType<ModelEndpointDataSource>().FirstOrDefault();
if (dataSource == null)
{
dataSource = new ModelEndpointDataSource();
endpoints.DataSources.Add(dataSource);
} return dataSource.AddEndpointBuilder(builder);
}

在这个方法里有一个实例化RouteEndpointBuilder的语句:

  var builder = new RouteEndpointBuilder(
requestDelegate,
pattern,
defaultOrder)
{
DisplayName = pattern.RawText ?? pattern.DebuggerToString(),
};

其中RouteEndpointBuilder从字面意思看得出,就是RouteEndpoint的创建者,我们看下它的代码:

 public sealed class RouteEndpointBuilder : EndpointBuilder
{
/// <summary>
/// Gets or sets the <see cref="RoutePattern"/> associated with this endpoint.
/// </summary>
public RoutePattern RoutePattern { get; set; } /// <summary>
/// Gets or sets the order assigned to the endpoint.
/// </summary>
public int Order { get; set; } /// <summary>
/// Constructs a new <see cref="RouteEndpointBuilder"/> instance.
/// </summary>
/// <param name="requestDelegate">The delegate used to process requests for the endpoint.</param>
/// <param name="routePattern">The <see cref="RoutePattern"/> to use in URL matching.</param>
/// <param name="order">The order assigned to the endpoint.</param>
public RouteEndpointBuilder(
RequestDelegate requestDelegate,
RoutePattern routePattern,
int order)
{
RequestDelegate = requestDelegate;
RoutePattern = routePattern;
Order = order;
} //生成Route
/// <inheritdoc />
public override Endpoint Build()
{
if (RequestDelegate is null)
{
throw new InvalidOperationException($"{nameof(RequestDelegate)} must be specified to construct a {nameof(RouteEndpoint)}.");
} var routeEndpoint = new RouteEndpoint(
RequestDelegate,
RoutePattern,
Order,
new EndpointMetadataCollection(Metadata),
DisplayName); return routeEndpoint;
}
}

再来看下Endpoint的代码:

   /// <summary>
/// Represents a logical endpoint in an application.
/// </summary>
public class Endpoint
{
/// <summary>
/// Creates a new instance of <see cref="Endpoint"/>.
/// </summary>
/// <param name="requestDelegate">The delegate used to process requests for the endpoint.</param>
/// <param name="metadata">
/// The endpoint <see cref="EndpointMetadataCollection"/>. May be null.
/// </param>
/// <param name="displayName">
/// The informational display name of the endpoint. May be null.
/// </param>
public Endpoint(
RequestDelegate? requestDelegate,
EndpointMetadataCollection? metadata,
string? displayName)
{
// All are allowed to be null
RequestDelegate = requestDelegate;
Metadata = metadata ?? EndpointMetadataCollection.Empty;
DisplayName = displayName;
} /// <summary>
/// Gets the informational display name of this endpoint.
/// </summary>
public string? DisplayName { get; } /// <summary>
/// Gets the collection of metadata associated with this endpoint.
/// </summary>
public EndpointMetadataCollection Metadata { get; } /// <summary>
/// Gets the delegate used to process requests for the endpoint.
/// </summary>
public RequestDelegate? RequestDelegate { get; } /// <summary>
/// Returns a string representation of the endpoint.
/// </summary>
public override string? ToString() => DisplayName ?? base.ToString();
}

从Build()方法看得出,它利用路由匹配的RoutePattern,生成一个RouteEndpoint实例。RouteEndpoint叫做路由终点,继承自Endpoint。Endpoint有一个重要的RequestDelegate属性,用来处理当前的请求。看到这里,我们开始推断:所谓的Endpoint,无非就是用要匹配的pattern,构造一个RouteEndpoint的过程,其中RouteEndpoint继承自Endpoint。NetCore无非就是利用这个RouteEndpoint来匹配当前的Url,如果匹配得上,就执行RequestDelegate所代表的方法,上文就是WeatherForecast方法,如果匹配不上,则不执行WeatherForecast方法,仅此而已。

2.为了验证,我们再来看 app.UseRouting().UseEndpoints(),实际上调用的是EndpointRoutingApplicationBuilderExtensions类的两个方法:

   public static class EndpointRoutingApplicationBuilderExtensions
{
private const string EndpointRouteBuilder = "__EndpointRouteBuilder";
private const string GlobalEndpointRouteBuilderKey = "__GlobalEndpointRouteBuilder"; /// <summary>
/// Adds a <see cref="EndpointRoutingMiddleware"/> middleware to the specified <see cref="IApplicationBuilder"/>.
/// </summary>
/// <param name="builder">The <see cref="IApplicationBuilder"/> to add the middleware to.</param>
/// <returns>A reference to this instance after the operation has completed.</returns>
/// <remarks>
/// <para>
/// A call to <see cref="UseRouting(IApplicationBuilder)"/> must be followed by a call to
/// <see cref="UseEndpoints(IApplicationBuilder, Action{IEndpointRouteBuilder})"/> for the same <see cref="IApplicationBuilder"/>
/// instance.
/// </para>
/// <para>
/// The <see cref="EndpointRoutingMiddleware"/> defines a point in the middleware pipeline where routing decisions are
/// made, and an <see cref="Endpoint"/> is associated with the <see cref="HttpContext"/>. The <see cref="EndpointMiddleware"/>
/// defines a point in the middleware pipeline where the current <see cref="Endpoint"/> is executed. Middleware between
/// the <see cref="EndpointRoutingMiddleware"/> and <see cref="EndpointMiddleware"/> may observe or change the
/// <see cref="Endpoint"/> associated with the <see cref="HttpContext"/>.
/// </para>
/// </remarks>
public static IApplicationBuilder UseRouting(this IApplicationBuilder builder)
{
if (builder == null)
{
throw new ArgumentNullException(nameof(builder));
} VerifyRoutingServicesAreRegistered(builder); IEndpointRouteBuilder endpointRouteBuilder;
if (builder.Properties.TryGetValue(GlobalEndpointRouteBuilderKey, out var obj))
{
endpointRouteBuilder = (IEndpointRouteBuilder)obj!;
// Let interested parties know if UseRouting() was called while a global route builder was set
builder.Properties[EndpointRouteBuilder] = endpointRouteBuilder;
}
else
{
endpointRouteBuilder = new DefaultEndpointRouteBuilder(builder);
builder.Properties[EndpointRouteBuilder] = endpointRouteBuilder;
} //先交由EndpointRoutingMiddleware中间件匹配Endpoint,然后交由下面的EndpointMiddleware处理
return builder.UseMiddleware<EndpointRoutingMiddleware>(endpointRouteBuilder);
} /// <summary>
/// Adds a <see cref="EndpointMiddleware"/> middleware to the specified <see cref="IApplicationBuilder"/>
/// with the <see cref="EndpointDataSource"/> instances built from configured <see cref="IEndpointRouteBuilder"/>.
/// The <see cref="EndpointMiddleware"/> will execute the <see cref="Endpoint"/> associated with the current
/// request.
/// </summary>
/// <param name="builder">The <see cref="IApplicationBuilder"/> to add the middleware to.</param>
/// <param name="configure">An <see cref="Action{IEndpointRouteBuilder}"/> to configure the provided <see cref="IEndpointRouteBuilder"/>.</param>
/// <returns>A reference to this instance after the operation has completed.</returns>
/// <remarks>
/// <para>
/// A call to <see cref="UseEndpoints(IApplicationBuilder, Action{IEndpointRouteBuilder})"/> must be preceded by a call to
/// <see cref="UseRouting(IApplicationBuilder)"/> for the same <see cref="IApplicationBuilder"/>
/// instance.
/// </para>
/// <para>
/// The <see cref="EndpointRoutingMiddleware"/> defines a point in the middleware pipeline where routing decisions are
/// made, and an <see cref="Endpoint"/> is associated with the <see cref="HttpContext"/>. The <see cref="EndpointMiddleware"/>
/// defines a point in the middleware pipeline where the current <see cref="Endpoint"/> is executed. Middleware between
/// the <see cref="EndpointRoutingMiddleware"/> and <see cref="EndpointMiddleware"/> may observe or change the
/// <see cref="Endpoint"/> associated with the <see cref="HttpContext"/>.
/// </para>
/// </remarks>
public static IApplicationBuilder UseEndpoints(this IApplicationBuilder builder, Action<IEndpointRouteBuilder> configure)
{
if (builder == null)
{
throw new ArgumentNullException(nameof(builder));
} if (configure == null)
{
throw new ArgumentNullException(nameof(configure));
} VerifyRoutingServicesAreRegistered(builder); VerifyEndpointRoutingMiddlewareIsRegistered(builder, out var endpointRouteBuilder); configure(endpointRouteBuilder); // Yes, this mutates an IOptions. We're registering data sources in a global collection which
// can be used for discovery of endpoints or URL generation.
//
// Each middleware gets its own collection of data sources, and all of those data sources also
// get added to a global collection.
var routeOptions = builder.ApplicationServices.GetRequiredService<IOptions<RouteOptions>>();
foreach (var dataSource in endpointRouteBuilder.DataSources)
{
if (!routeOptions.Value.EndpointDataSources.Contains(dataSource))
{
routeOptions.Value.EndpointDataSources.Add(dataSource);
}
} //交由EndpointMiddleware处理
return builder.UseMiddleware<EndpointMiddleware>();
} private static void VerifyRoutingServicesAreRegistered(IApplicationBuilder app)
{
// Verify if AddRouting was done before calling UseEndpointRouting/UseEndpoint
// We use the RoutingMarkerService to make sure if all the services were added.
if (app.ApplicationServices.GetService(typeof(RoutingMarkerService)) == null)
{
throw new InvalidOperationException(Resources.FormatUnableToFindServices(
nameof(IServiceCollection),
nameof(RoutingServiceCollectionExtensions.AddRouting),
"ConfigureServices(...)"));
}
} private static void VerifyEndpointRoutingMiddlewareIsRegistered(IApplicationBuilder app, out IEndpointRouteBuilder endpointRouteBuilder)
{
if (!app.Properties.TryGetValue(EndpointRouteBuilder, out var obj))
{
var message =
$"{nameof(EndpointRoutingMiddleware)} matches endpoints setup by {nameof(EndpointMiddleware)} and so must be added to the request " +
$"execution pipeline before {nameof(EndpointMiddleware)}. " +
$"Please add {nameof(EndpointRoutingMiddleware)} by calling '{nameof(IApplicationBuilder)}.{nameof(UseRouting)}' inside the call " +
$"to 'Configure(...)' in the application startup code.";
throw new InvalidOperationException(message);
} endpointRouteBuilder = (IEndpointRouteBuilder)obj!; // This check handles the case where Map or something else that forks the pipeline is called between the two
// routing middleware.
if (endpointRouteBuilder is DefaultEndpointRouteBuilder defaultRouteBuilder && !object.ReferenceEquals(app, defaultRouteBuilder.ApplicationBuilder))
{
var message =
$"The {nameof(EndpointRoutingMiddleware)} and {nameof(EndpointMiddleware)} must be added to the same {nameof(IApplicationBuilder)} instance. " +
$"To use Endpoint Routing with 'Map(...)', make sure to call '{nameof(IApplicationBuilder)}.{nameof(UseRouting)}' before " +
$"'{nameof(IApplicationBuilder)}.{nameof(UseEndpoints)}' for each branch of the middleware pipeline.";
throw new InvalidOperationException(message);
}
}
}

2-1首先看UseRouting()方法,这个方法其实就是调用EndpointRoutingMiddleware中间件进行Endpoint的匹配,我们可以看下它的几个方法:

internal sealed partial class EndpointRoutingMiddleware
{
private const string DiagnosticsEndpointMatchedKey = "Microsoft.AspNetCore.Routing.EndpointMatched"; private readonly MatcherFactory _matcherFactory;
private readonly ILogger _logger;
private readonly EndpointDataSource _endpointDataSource;
private readonly DiagnosticListener _diagnosticListener;
private readonly RequestDelegate _next; private Task<Matcher>? _initializationTask; public EndpointRoutingMiddleware(
MatcherFactory matcherFactory,
ILogger<EndpointRoutingMiddleware> logger,
IEndpointRouteBuilder endpointRouteBuilder,
DiagnosticListener diagnosticListener,
RequestDelegate next)
{
if (endpointRouteBuilder == null)
{
throw new ArgumentNullException(nameof(endpointRouteBuilder));
} _matcherFactory = matcherFactory ?? throw new ArgumentNullException(nameof(matcherFactory));
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
_diagnosticListener = diagnosticListener ?? throw new ArgumentNullException(nameof(diagnosticListener));
_next = next ?? throw new ArgumentNullException(nameof(next)); _endpointDataSource = new CompositeEndpointDataSource(endpointRouteBuilder.DataSources);
} public Task Invoke(HttpContext httpContext)
{
// There's already an endpoint, skip matching completely
var endpoint = httpContext.GetEndpoint();
if (endpoint != null)
{
Log.MatchSkipped(_logger, endpoint);
return _next(httpContext);
} // There's an inherent race condition between waiting for init and accessing the matcher
// this is OK because once `_matcher` is initialized, it will not be set to null again.
var matcherTask = InitializeAsync();
if (!matcherTask.IsCompletedSuccessfully)
{
return AwaitMatcher(this, httpContext, matcherTask);
} var matchTask = matcherTask.Result.MatchAsync(httpContext);
if (!matchTask.IsCompletedSuccessfully)
{
return AwaitMatch(this, httpContext, matchTask);
} return SetRoutingAndContinue(httpContext); // Awaited fallbacks for when the Tasks do not synchronously complete
static async Task AwaitMatcher(EndpointRoutingMiddleware middleware, HttpContext httpContext, Task<Matcher> matcherTask)
{
var matcher = await matcherTask;
//根据httpContext进行匹配
await matcher.MatchAsync(httpContext);
await middleware.SetRoutingAndContinue(httpContext);
} static async Task AwaitMatch(EndpointRoutingMiddleware middleware, HttpContext httpContext, Task matchTask)
{
await matchTask;
await middleware.SetRoutingAndContinue(httpContext);
} } [MethodImpl(MethodImplOptions.AggressiveInlining)]
private Task SetRoutingAndContinue(HttpContext httpContext)
{
// If there was no mutation of the endpoint then log failure
var endpoint = httpContext.GetEndpoint();
if (endpoint == null)
{
Log.MatchFailure(_logger);
}
else
{
// Raise an event if the route matched
if (_diagnosticListener.IsEnabled() && _diagnosticListener.IsEnabled(DiagnosticsEndpointMatchedKey))
{
// We're just going to send the HttpContext since it has all of the relevant information
_diagnosticListener.Write(DiagnosticsEndpointMatchedKey, httpContext);
} Log.MatchSuccess(_logger, endpoint);
} return _next(httpContext);
} // Initialization is async to avoid blocking threads while reflection and things
// of that nature take place.
//
// We've seen cases where startup is very slow if we allow multiple threads to race
// while initializing the set of endpoints/routes. Doing CPU intensive work is a
// blocking operation if you have a low core count and enough work to do.
private Task<Matcher> InitializeAsync()
{
var initializationTask = _initializationTask;
if (initializationTask != null)
{
return initializationTask;
} return InitializeCoreAsync();
} private Task<Matcher> InitializeCoreAsync()
{
var initialization = new TaskCompletionSource<Matcher>(TaskCreationOptions.RunContinuationsAsynchronously);
var initializationTask = Interlocked.CompareExchange(ref _initializationTask, initialization.Task, null);
if (initializationTask != null)
{
// This thread lost the race, join the existing task.
return initializationTask;
} // This thread won the race, do the initialization.
try
{
//用EndpointDataSource初始化matcher
//EndpointDataSource来源于IEndpointRouteBuilder的DataSource属性
//见EndpointRouteBuilderExtensions类的201行
var matcher = _matcherFactory.CreateMatcher(_endpointDataSource); _initializationTask = Task.FromResult(matcher); // Complete the task, this will unblock any requests that came in while initializing.
initialization.SetResult(matcher);
return initialization.Task;
}
catch (Exception ex)
{
// Allow initialization to occur again. Since DataSources can change, it's possible
// for the developer to correct the data causing the failure.
_initializationTask = null; // Complete the task, this will throw for any requests that came in while initializing.
initialization.SetException(ex);
return initialization.Task;
}
} private static partial class Log
{
public static void MatchSuccess(ILogger logger, Endpoint endpoint)
=> MatchSuccess(logger, endpoint.DisplayName); [LoggerMessage(1, LogLevel.Debug, "Request matched endpoint '{EndpointName}'", EventName = "MatchSuccess")]
private static partial void MatchSuccess(ILogger logger, string? endpointName); [LoggerMessage(2, LogLevel.Debug, "Request did not match any endpoints", EventName = "MatchFailure")]
public static partial void MatchFailure(ILogger logger); public static void MatchSkipped(ILogger logger, Endpoint endpoint)
=> MatchingSkipped(logger, endpoint.DisplayName); [LoggerMessage(3, LogLevel.Debug, "Endpoint '{EndpointName}' already set, skipping route matching.", EventName = "MatchingSkipped")]
private static partial void MatchingSkipped(ILogger logger, string? endpointName);
}
}

从Invoke方法看得出来,它根据当前的HttpContext进行Endpoint的匹配,如果当前的HttpContext路由格式匹配成功,那么将当前HttpContext传递给下一个中间件处理,这个从SetRoutingAndContinue方法看得出来。

2-2其次看下UseEndpoints()方法,这个方法就是调用EndpointMiddleware中间件,对上面匹配成功的HttpContext进行处理,并调用HttpContext的EndPoint的RequestDelegate处理当前请求。我们重点看下它的Invoke方法,重点关注var requestTask = endpoint.RequestDelegate(httpContext):

  public Task Invoke(HttpContext httpContext)
{
var endpoint = httpContext.GetEndpoint();
if (endpoint?.RequestDelegate != null)
{
if (!_routeOptions.SuppressCheckForUnhandledSecurityMetadata)
{
if (endpoint.Metadata.GetMetadata<IAuthorizeData>() != null &&
!httpContext.Items.ContainsKey(AuthorizationMiddlewareInvokedKey))
{
ThrowMissingAuthMiddlewareException(endpoint);
} if (endpoint.Metadata.GetMetadata<ICorsMetadata>() != null &&
!httpContext.Items.ContainsKey(CorsMiddlewareInvokedKey))
{
ThrowMissingCorsMiddlewareException(endpoint);
}
} Log.ExecutingEndpoint(_logger, endpoint); try
{
//调用HttpContext的Endpoint的RequestDelegate方法处理当前请求
var requestTask = endpoint.RequestDelegate(httpContext);
if (!requestTask.IsCompletedSuccessfully)
{
return AwaitRequestTask(endpoint, requestTask, _logger);
}
}
catch (Exception exception)
{
Log.ExecutedEndpoint(_logger, endpoint);
return Task.FromException(exception);
} Log.ExecutedEndpoint(_logger, endpoint);
return Task.CompletedTask;
} return _next(httpContext); static async Task AwaitRequestTask(Endpoint endpoint, Task requestTask, ILogger logger)
{
try
{
await requestTask;
}
finally
{
Log.ExecutedEndpoint(logger, endpoint);
}
}
}

总结:从上面的分析,我们粗略的了解了netcore路由的Endpoint模式其实就是一种用匹配模式构建的终端节点,它主要用来对HttpContext进行路由的匹配,如果匹配成功,则执行Endpoint上的RequestDelegate方法。

NetCore路由的Endpoint模式的更多相关文章

  1. 前端路由两种模式:hash、history

    随着 ajax 的使用越来越广泛,前端的页面逻辑开始变得越来越复杂,特别是spa的兴起,前端路由系统随之开始流行. 从用户的角度看,前端路由主要实现了两个功能(使用ajax更新页面状态的情况下): 记 ...

  2. 【RabbitMQ】4、三种Exchange模式——订阅、路由、通配符模式

    前两篇博客介绍了两种队列模式,这篇博客介绍订阅.路由和通配符模式,之所以放在一起介绍,是因为这三种模式都是用了Exchange交换机,消息没有直接发送到队列,而是发送到了交换机,经过队列绑定交换机到达 ...

  3. 如何去除vue项目中的 # — vue路由的History模式

    前言 在创建的 router 对象中,如果不配置 mode,就会使用默认的 hash 模式,该模式下会将路径格式化为 #! 开头. 添加 mode: 'history' 之后将使用 HTML5 his ...

  4. Vue 的路由实现 Hash模式 和 History模式

    Hash 模式: Hash 模式的工作原理是onhashchange事件,Window对象可以监听这个事件... 可以通过改变路径的哈希值,来实现历史记录的保存,发生变化的hash 都会被浏览器给保存 ...

  5. 数据源管理 | 主从库动态路由,AOP模式读写分离

    本文源码:GitHub·点这里 || GitEE·点这里 一.多数据源应用 1.基础描述 在相对复杂的应用服务中,配置多个数据源是常见现象,例如常见的:配置主从数据库用来写数据,再配置一个从库读数据, ...

  6. Vue路由配置history模式

    我的博客: https://github.com/Daotin/fe-notes/issues vue需要node.js吗? 你可以用 script 标签的形式引入vue.min.js 这样的,不需要 ...

  7. Vue路由的hash模式与history模式的区别?

    1.首先router有两种模式:hash模式(默认).history模式(需配置mode: 'history') hash和history的区别?   hash                    ...

  8. Vue2+Koa2+Typescript前后端框架教程--03后端路由和三层模式配置

    昨天将Koa2的基础框架和自动编译调试重启服务完成,今天开始配置路由和搭建基础的三层架构模式. 路由中间件:koa-router,即路由导航,就是我们平时使用最广泛的get/post方法执行的URL路 ...

  9. .net core i上 K8S(五).netcore程序的hostip模式

    上一章讲了pod的管理,今天再分享一个pod的访问方式 1.Pod的HostIP模式 Pod的HostIP模式,可以通过宿主机访问pod内的服务,创建yaml文件如下 apiVersion: v1 k ...

随机推荐

  1. unity---脚本创建按钮

    脚本创建按钮 新建文件夹 Resources 方便引用图片 在文件Resources中新建Images,并且下载一个图片 没有图片,按钮内容无法显示 图片需要处理一下 Textrue Type 改为 ...

  2. socket套接字补充、操作系统发展史、进程

    目录 socket套接字之UDP协议 操作系统的发展史 手工操作 批处理系统 联机批处理系统 脱机批处理系统 多道技术 进程理论 并发与并行 同步与异步 阻塞与非阻塞 同步异步与阻塞非阻塞总结 soc ...

  3. 无法启动报,To install it, you can run: npm install --save @/components/xxxx.vue

    运行的过程中后台报错 npm install --save @/components/xxx.vue 重装了node_modules依然没有用. 其实是组件路径写错了 总结 以后出现提醒安装那个vue ...

  4. Vue路由的安装

    1.在Vue ui中插件中找到添加vue-router 2.安装以后,项目中的会自动完成配置. 3.在store中的index.js配置路由页面以及路径.

  5. Kafka 负载均衡在 vivo 的落地实践

    ​vivo 互联网服务器团队-You Shuo 副本迁移是Kafka最高频的操作,对于一个拥有几十万个副本的集群,通过人工去完成副本迁移是一件很困难的事情.Cruise Control作为Kafka的 ...

  6. CabloyJS的微信API对接模块:当前支持微信公众号和微信小程序

    Cabloy-微信是什么 Cabloy-微信是基于CabloyJS全栈业务开发框架开发的微信接口模块,当前整合了微信公众号和微信小程序的接口,达到开箱即用的使用效果.在Cabloy-微信的基础上,可以 ...

  7. 一文掌握软件安全必备技术 SAST

    上一篇文章中,我们讨论了软件供应链的概念并了解到近年来软件供应链安全事件层出不穷.为了保障软件供应链安全,我们需要了解网络安全领域中的一些主要技术.本篇文章将介绍其中一个重要技术--SAST. 当开发 ...

  8. JVM学习笔记-从底层了解程序运行(二)

    解决JVM运行中的问题 一个案例理解常用工具 测试代码: /** * 从数据库中读取信用数据,套用模型,并把结果进行记录和传输 */ public class T15_FullGC_Problem01 ...

  9. Python Socket Sever

    1. Server code 1 # !/usr/bin/env python 2 # coding:utf-8 3 import multiprocessing 4 import socket 5 ...

  10. SAP FICO 常用table

    Table 描 述 "Table Type" "Application Class" "Data Class" Description &q ...