深度优先

这个家伙好懒,除了文章什么都没留下

0%

Google 机器翻译: https://elanderson.net/2019/12/log-requests-and-responses-in-asp-net-core-3/

这篇文章将是 对ASP.NET Core 中的 日志请求和响应 的更新,该 文章不再适用于更现代的ASP.NET Core版本。 在大多数情况下,该帖子将与原始帖子完全匹配,但代码位已更新。

作为尝试进行调试的一部分,我需要一种记录请求和响应的方法。 编写中间件似乎是解决此问题的一种好方法。 结果也比我预期的处理请求和响应机构要复杂得多。

中间件

在ASP.NET Core中, 中间件 是组成HTTP管道的组件,该HTTP管道处理应用程序的请求和响应。 每个被调用的中间件都可以选择对请求进行一些处理,然后再在线调用下一个中间件。 在执行从调用返回到下一个中​​间件之后,就有机会对响应进行处理。

应用程序的HTTP管道是在Startup 类 的 Configure 函数中 设置的 。Run ,Map 和Use 是可用的三种中间件类型。Run 只能用于终止管道。Map 用于管道分支。Use 似乎是最常见的中间件类型,它进行一些处理并直接调用下一个中间件。 有关更多详细信息,请参见官方 文档

创建中间件

中间件可以直接在 Configure 函数中 实现为lambda ,但更典型的是,它可以实现为使用 IApplicationBuilder 上的扩展方法添加到管道中的类 。 本示例将使用类路由。

本示例是一个中间件,它使用ASP.NET Core的内置日志记录请求和响应。 创建一个名为 LoggerMiddleware 的类 。

该类将需要一个带有两个参数的构造函数,这两个参数都将由ASP.NET Core的依赖项注入系统提供。 第一个是 RequestDelegate,它将成为 管道中的下一个中间件。 第二个是 ILoggerFactory 的实例,该实例 将用于创建记录器。 所述 RequestDelegate 存储到类级别 _next 变量和 的LoggerFactory 用于创建存储到类电平的记录器 _logger 变量。

1
2
3
4
5
6
7
8
9
10
11
12
public class LoggerMiddleware
{
private readonly RequestDelegate _next;
private readonly ILogger _logger;
public LoggerMiddleware(RequestDelegate next,
ILoggerFactory loggerFactory)
{
_next = next;
_logger = loggerFactory
.CreateLogger<LoggerMiddleware>();
}
}

添加一个Invoke函数,当管道运行中间件时将调用该函数。下面的函数除了调用管道中的下一个中间件外什么也不做。

1
2
3
4
5
6
7
8
public async Task Invoke(HttpContext context)
{
//code dealing with the request

await _next(context);

//code dealing with the response
}

接下来,添加一个静态类以简化将中间件添加到应用程序管道中的过程。这与内置中间件使用的模式相同。

1
2
3
4
5
6
7
public static class LoggerMiddlewareExtensions
{
public static IApplicationBuilder UseRequestResponseLogging(this IApplicationBuilder builder)
{
return builder.UseMiddleware<LoggerMiddleware>();
}
}

添加到管道

要将新的中间件添加到管道中,请打开 Startup.cs文件,并将以下行添加 到 Configure 函数中。

1
app.UseRequestResponseLogging();

请记住,中间件的添加顺序可能会影响应用程序的行为。 由于该帖子正在处理的中间件是日志记录,因此我将其放置在管道的开始位置附近。

记录请求和响应

现在我们新的中间件的设置工作已经完成,我们将回到其 调用 功能。 如前所述,这最终比我预期的要复杂得多,但是值得庆幸的是,我发现 Sul Aga 做到了 这一点 , 这确实帮助我解决了我所遇到的问题,并对这篇文章的原始版本提出了很多反馈。

有关此帖子原始版本的反馈之一是关于 潜在的内存泄漏和使用可回收内存流 。 首先,添加对 Microsoft.IO.RecyclableMemoryStream包 的NuGet引用 。 接下来,我们将添加一个类级变量来保存一个 RecyclableMemoryStreamManager 的实例,该实例 将在构造函数中创建。 以下是更新的类视图,其中包含这些更改以及对 Invoke 函数和日志记录方法的存根的 更改 。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public class LoggerMiddleware
{
private readonly RequestDelegate _next;
private readonly ILogger _logger;
private readonly RecyclableMemoryStreamManager _recyclableMemoryStreamManager;

public LoggerMiddleware(RequestDelegate next,
ILoggerFactory loggerFactory)
{
_next = next;
_logger = loggerFactory
.CreateLogger<LoggerMiddleware>();
_recyclableMemoryStreamManager = new RecyclableMemoryStreamManager();
}

public async Task Invoke(HttpContext context)
{
await LogRequest(context);
await LogResponse(context);
}

private async Task LogRequest(HttpContext context) {}
private async Task LogResponse(HttpContext context) {}
}

首先,我们将看一下LogRequest函数及其使用的辅助函数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
private async Task LogRequest(HttpContext context)
{
context.Request.EnableBuffering();

await using var requestStream = _recyclableMemoryStreamManager.GetStream();
await context.Request.Body.CopyToAsync(requestStream);
_logger.LogInformation($"Http Request Information:{Environment.NewLine}" +
$"Schema:{context.Request.Scheme} " +
$"Host: {context.Request.Host} " +
$"Path: {context.Request.Path} " +
$"QueryString: {context.Request.QueryString} " +
$"Request Body: {ReadStreamInChunks(requestStream)}");
context.Request.Body.Position = 0;
}

private static string ReadStreamInChunks(Stream stream)
{
const int readChunkBufferLength = 4096;

stream.Seek(0, SeekOrigin.Begin);

using var textWriter = new StringWriter();
using var reader = new StreamReader(stream);

var readChunk = new char[readChunkBufferLength];
int readChunkLength;

do
{
readChunkLength = reader.ReadBlock(readChunk,
0,
readChunkBufferLength);
textWriter.Write(readChunk, 0, readChunkLength);
} while (readChunkLength > 0);

return textWriter.ToString();
}

使此功能起作用并允许读取请求正文的关键是 context.Request.EnableBuffering() ,它使我们能够从流的开头进行读取。 该功能的其余部分非常简单。

下一个函数是 LogResponse ,它用于使用 _next(context)等待 管道中的下一个中间件 ,然后在其余管道运行后记录响应主体。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
private async Task LogResponse(HttpContext context)
{
var originalBodyStream = context.Response.Body;

await using var responseBody = _recyclableMemoryStreamManager.GetStream();
context.Response.Body = responseBody;

await _next(context);

context.Response.Body.Seek(0, SeekOrigin.Begin);
var text = await new StreamReader(context.Response.Body).ReadToEndAsync();
context.Response.Body.Seek(0, SeekOrigin.Begin);

_logger.LogInformation($"Http Response Information:{Environment.NewLine}" +
$"Schema:{context.Request.Scheme} " +
$"Host: {context.Request.Host} " +
$"Path: {context.Request.Path} " +
$"QueryString: {context.Request.QueryString} " +
$"Response Body: {text}");

await responseBody.CopyToAsync(originalBodyStream);
}

如您所见,读取响应主体的技巧是将正在使用的流替换为新的 MemoryStream ,然后将数据复制回原始主体流。 我不知道这会对性能有多大影响,因此请确保在生产环境中使用它之前先研究它如何扩展。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Logging;
using Microsoft.IO;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading.Tasks;

namespace demo.Extensions
{
public class LoggerMiddleware
{
private readonly RequestDelegate _next;
private readonly ILogger _logger;
private readonly RecyclableMemoryStreamManager _recyclableMemoryStreamManager;

public LoggerMiddleware(RequestDelegate next, ILoggerFactory loggerFactory)
{
_next = next;
_logger = loggerFactory.CreateLogger<LoggerMiddleware>();
_recyclableMemoryStreamManager = new RecyclableMemoryStreamManager();
}

public async Task Invoke(HttpContext context)
{
await LogRequest(context);
await LogResponse(context);
}

private async Task LogRequest(HttpContext context)
{
context.Request.EnableBuffering();
using (var requestStream = _recyclableMemoryStreamManager.GetStream())
{
await context.Request.Body.CopyToAsync(requestStream);
if (context.Request.Path.Value.StartsWith("/api/"))
{
_logger.LogError($"Http Request Information:{Environment.NewLine}" +
$"Schema:{context.Request.Scheme} " +
$"Host: {context.Request.Host} " +
$"Path: {context.Request.Path} " +
$"QueryString: {context.Request.QueryString} " +
$"Request Body: {ReadStreamInChunks(requestStream)}");
}
context.Request.Body.Position = 0;
};
}

private static string ReadStreamInChunks(Stream stream)
{
const int readChunkBufferLength = 4096;
stream.Seek(0, SeekOrigin.Begin);
using (var textWriter = new StringWriter())
{
using (var reader = new StreamReader(stream))
{
var readChunk = new char[readChunkBufferLength];
int readChunkLength;
do
{
readChunkLength = reader.ReadBlock(readChunk, 0, readChunkBufferLength);
textWriter.Write(readChunk, 0, readChunkLength);
} while (readChunkLength > 0);
return textWriter.ToString();
};
};
}

private async Task LogResponse(HttpContext context)
{
var originalBodyStream = context.Response.Body;
using (var responseBody = _recyclableMemoryStreamManager.GetStream())
{
context.Response.Body = responseBody;
await _next(context);
context.Response.Body.Seek(0, SeekOrigin.Begin);
var text = await new StreamReader(context.Response.Body).ReadToEndAsync();
context.Response.Body.Seek(0, SeekOrigin.Begin);

if (context.Request.Path.Value.StartsWith("/api/"))
{
_logger.LogError($"Http Response Information:{Environment.NewLine}" +
$"Schema:{context.Request.Scheme} " +
$"Host: {context.Request.Host} " +
$"Path: {context.Request.Path} " +
$"QueryString: {context.Request.QueryString} " +
$"Response Body: {text}");
}

await responseBody.CopyToAsync(originalBodyStream);
};
}
}
}

原文地址:https://www.cnblogs.com/bbgs-xc/p/12961204.html

简介

Dockerfile是用来构建Docker镜像的构建文件,是由一系列命令和参数构成的脚本

构建三步: DockerFile文件 ==> docker build ==> docker run

Dockerfile可参考官方Dockerfile文件

构建过程解析

Dockerfile内容基础知识:

a>每条保留字指令都必须为大写字母且后面要跟随至少一个参数

b>指令按照从上到下,顺序执行

c>#表示注释

d>每条指令都会创建一个新的镜像层,并对镜像进行提交

Docker执行Dockerfile的大致流程:

a>docker从基础镜像运行一个容器

b>执行一条指令并对容器作出修改

c>执行类似docker commit的操作提交一个新的镜像

d>docker再基于刚提交的镜像运行一个新容器

e>执行dockerfile中的下一条指令直到所有指令都执行完成

Dockerfile保留字指令:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
FROM:基础镜像,当前镜像是基于那个镜像的。

MAINTAINER:作者  邮箱。

RUN:容器构建时需要运行的命令。

EXPOSE:暴露端口号 启动容器的时候还需使用 –p 指定端口。

WORKDIR:指定在创建容器后,终端默认登录后进来的工作目录,一个落脚点,默认根目录,通常绝对路径,CMD ENTRYPOINT 都会在这个目录下执行。

ENV:构建镜像的过程中设置环境变量, 构建和运行时都有效可以使用$引用。

ADD:将宿主机目录下的文件拷贝到镜像且会自动处理URL和 解压tar 压缩包。

COPY:拷贝文件和目录到镜像中,将从构建上下文目录中<源路径>的文件/复制到新的一层镜像内<目标路径>的位置。语法:COPY src dest  COPY [“src”, “desc”]。

VOLUME:容器数据卷, 数据保存和持久化。

USER:指定该镜像由什么用户运行  不指定由root 运行。

CMD:指定容器启动时要运行默认的命令,Dockerfile中可以有多个cmd , 但只有最后一个生效。

ENTERPOINT:指定容器启动时要运行的命令。在docker run 后面追加参数
ONBUILD:触发器, 当构建一个被继承的dockerfile时运行命令, 父镜像在被子继承后的父镜像的onbuild 被触发。

Dockerfile的案例总结

自定义mycentos镜像(需要安装有vim和ifconfig命令)

a.定义Dockerfile文件

1
2
3
4
5
6
7
8
9
10
11
12
FROM centos
#定义作者用邮箱
MAINTAINER xc<xxyy@163.com>
ENV DEFAULTDIR /usr/local
#落脚点
WORKDIR $DEFAULTDIR
#安装vim和net-tools
RUN yum -y install vim
RUN yum -y install net-tools
#容器暴露端口
EXPOSE 80
CMD /bin/bash

b.构建build

1
2
3
4
#如果为当前路径下的Dockerfile文件可以省略-f
docker build -f /mydocker/Dockerfile -t mycentos:1.0 .
#查看构建历史
docker history mycentos:1.0

c.运行

1
docker run -it -d mycentos:1.0

CMD与ENTRYPOINT区别

a.Dockerfile

1
2
3
4
FROM centos
RUN yum install -y curl
#CMD [ "curl", "-s", "http://ip.cn" ]
ENTRYPOINT [ "curl", "-s", "http://ip.cn" ]

b.构建docker build -t myip .

c.运行:docker run myip -i (CMD命令构建报错,-i命令会覆盖curl -s命令;ENTRYPOINT命令会打出头信息)

自定义tomcat

a.当前目录下文件

b.Dockerfile

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
FROM centos
ENV MY_PATH /usr/local
WORKDIR $MY_PATH
COPY 1.txt $MY_PATH/contain.txt
#添加jdk和tomcat
ADD apache-tomcat-7.0.75.tar.gz $MY_PATH
ADD jdk-8u121-linux-x64.tar.gz $MY_PATH

RUN yum -y install vim

ENV JAVA_HOME $MY_PATH/jdk1.8.0_121
ENV TOMCAT_HOME $MY_PATH/apache-tomcat-7.0.75
ENV PATH $PATH:$JAVA_HOME/bin:$TOMCAT_HOME/:$CATALINA_HOME/lib:$CATALINA_HOME/bin

EXPOSE 8080

CMD $TOMCAT_HOME/bin/startup.sh && tail -F $TOMCAT_HOME/logs/catalina.out

c.构建

1
docker build -t mytomcat .

d.启动

1
2
docker run -d -p 8090:8080 --name myt -v /mydocker/mytomcat/test:/usr/local/apache-tomcat-7.0.75/webapps/test \
-v /mydocker/mytomcat/logs/:/usr/local/apache-tomcat-7.0.75/logs --privileged=true mytomcat

则可以在宿主机上之间获取访问容器内日志同时test目录下的项目也可对应容器内项目的class文件

https://www.cnblogs.com/tylertang/p/10690774.html

原因:
数据库中的两个表是主从表关系,但是没有建外键,而表的id用的是数据库的自增整数,导致在使用EF导入主从表数据时,需要先保存主表数据,取到
主表的自增id后才能插入从表数据,这样循环之下,数据插入速度非常慢。

经过查询得知:
即使在数据库中没有建立外键关系,也可以在EF中通过关系建议主从表关系,从而达到批量导入主从表数据的目的。

具体实现:
首先model中需要添加主从表的关系属性

主表

1
2
3
4
5
6
7
8
9
10
11
12
[Table("DataHubEmployee")]
public partial class DataHubEmployee : SecuredEntity
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int pkDataHubEmployee { get; set; }
public int fkDataHubBatch { get; set; }
public int? OriginalBatchId { get; set; }
public int EmployeeId { get; set; }
public string ClientCode { get; set; }
public virtual ICollection<DataHubDependant> DataHubDependants { get; set; }
}

从表

1
2
3
4
5
6
7
8
9
10
11
12
13
[Table("DataHubDependant")]
public partial class DataHubDependant : SecuredEntity
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int pkDataHubDependant { get; set; }
public int? OriginalBatchId { get; set; }
public string ClientCode { get; set; }
public int fkDataHubEmployee { get; set; }
public string EmployeeId { get; set; }
public string FullName { get; set; }
public virtual DataHubEmployee DataHubEmployee { get; set; }
}

然后EF的DbContext中的OnModelCreating对实体的外键关联进行注册

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Properties<decimal>().Configure(c => c.HasPrecision(18, 6));
//add model mapping for auto process
modelBuilder.Entity<DataHubDependant>()
.HasRequired(d => d.DataHubEmployee)
.WithMany(e => e.DataHubDependants)
.HasForeignKey(d => d.fkDataHubEmployee);

modelBuilder.Entity<DependantChangeLog>()
.HasRequired(d => d.EmployeeChangeLog)
.WithMany(e => e.DependantChangeLogs)
.HasForeignKey(d => d.fkEmployeeChangeLog);
//close auto migration
Database.SetInitializer<ClientDbContext>(null);
base.OnModelCreating(modelBuilder);
}

说明:这里可以通过.HasForeignKey(d => d.fkDataHubEmployee);来指定从表中的哪个字段是外键。如果不指定,EF会自动生产一个包含外键的新列。

批量插入数据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
foreach (var dataHubEmployeeDTO in dataHubEmployeeDtoList)
{
if (batchType == "MonthlyData" && dataHubEmployeeDTO.Status.ToUpper() == "Terminated".ToUpper())
{
continue;
}
DataHubEmployee dataHubEmployee = new DataHubEmployee
{
EmployeeId = dataHubEmployeeDTO.EmployeeId,
fkDataHubBatch = dataHubBatch.pkDataHubBatch,
OriginalBatchId = batchId,
ClientCode = dataHubEmployeeDTO.ClientCode,

DataHubDependants = new List<DataHubDependant>()
};

//插入重表数据
//获取当前批员工列表
foreach (var dependant in dataHubEmployeeDTO.DependantList)
{
var dataHubDependant = new DataHubDependant
{
OriginalBatchId = batchId,
ClientCode = dataHubEmployeeDTO.ClientCode,
fkDataHubEmployee = dataHubEmployee.pkDataHubEmployee,
EmployeeId = dataHubEmployeeDTO.ID,
FullName = dependant.FullName,
Identification = dependant.Identification,
BirthDate = dependant.BirthDate,
Gender = dependant.Gender,
Nationality = dependant.Nationality,

};
dataHubEmployee.DataHubDependants.Add(dataHubDependant);
}
clientDbContext.DataHubEmployee.Add(dataHubEmployee);
}

clientDbContext.SaveChanges();

这样就可以在批量插入数据的时候,自动填充主表的自增ID到从表。