- Java锁的逻辑(结合对象头和ObjectMonitor)
- 还在用饼状图?来瞧瞧这些炫酷的百分比可视化新图形(附代码实现)⛵
- 自动注册实体类到EntityFrameworkCore上下文,并适配ABP及ABPVNext
- 基于Sklearn机器学习代码实战
前几天有群友在群里问如何在我之前的文章 《ASP.NET Core WebApi返回结果统一包装实践》 的时候有点疑问,主要的疑问点就是关于Respouse的读取的问题。在之前的文章 《深入探究ASP.NET Core读取Request.Body的正确方式》 曾分析过关于Request的读取问题,需要读取Response的场景同样经常遇到,比如读取输出信息或者包装一下输出结果等。无独有偶Response的读取同样存在类似的问题,本文我们便来分析一下如何进行Response的Body读取.
我们在日常的使用中是如何读取流呢?很简单,直接使用 StreamReader 去读取,方式如下 。
public override void OnResultExecuted(ResultExecutedContext context)
{
//操作流之前恢复一下操作位
context.HttpContext.Response.Body.Position = 0;
StreamReader stream = new StreamReader(context.HttpContext.Response.Body);
string body = stream.ReadToEnd();
_logger.LogInformation("body content:" + body);
context.HttpContext.Response.Body.Position = 0;
base.OnResultExecuted(context);
}
代码很简单,直接读取即可,可是这样读取是有问题的会抛出异常 System.ArgumentException:“Stream was not readable.” 异常信息就是的意思是当前Stream不可读,也就是Respouse的Body是不可以被读取的。关于StreamReader到底和Stream有啥关联,我们在之前的文章 深入探究ASP.NET Core读取Request.Body的正确方式 一文中有过源码分析,这里就不在赘述了,有兴趣的同学可以自行翻阅,强烈建议在阅读本文之前可以看一下那篇文章,方便更容易了解。 如何解决上面的问题呢?方式也很简单,比如你想在你的程序中保证Response的Body都是可读的,你可以定义一个中间件解决这个问题.
public static IApplicationBuilder UseResponseBodyRead(this IApplicationBuilder app)
{
return app.Use(async (context, next) =>
{
//获取原始的Response Body
var originalResponseBody = context.Response.Body;
try
{
//声明一个MemoryStream替换Response Body
using var swapStream = new MemoryStream();
context.Response.Body = swapStream;
await next(context);
//重置标识位
context.Response.Body.Seek(0, SeekOrigin.Begin);
//把替换后的Response Body复制到原始的Response Body
await swapStream.CopyToAsync(originalResponseBody);
}
finally
{
//无论异常与否都要把原始的Body给切换回来
context.Response.Body = originalResponseBody;
}
});
}
本质就是先用一个可操作的Stream比如咱们这里的 MemoryStream 替换默认的ResponseBody,让后续对ResponseBody的操作都是针对新的ResponseBody进行操作,完成之后把替换后的ResponseBody复制到原始的ResponseBody。最终无论异常与否都要把原始的Body给切换回来。需要注意的是,这个中间件的位置尽量要放在比较靠前的位置注册,至少也要保证在你所有要操作ResponseBody之前的位置注册。如下所示 。
var app = builder.Build();
app.UseResponseBodyRead();
通过上面我们了解到了ResponseBody是不可以被读取的,至于为什么呢,这个我们需要通过相关源码了解一下。通过 HttpContext 类的源码我们可以看到相关定义 。
public abstract class HttpContext
{
public abstract HttpResponse Response { get; }
}
这里看到 HttpContext 本身是个抽象类,看一下它的属性 HttpResponse 类的定义也是一个抽象类 。
public abstract class HttpResponse
{
}
由上面可知 Response 属性是抽象的,所以抽象类 HttpResponse 必然包含一个子类去实现它,否则没办法直接操作相关方法。这里我们介绍一个网站 https://source.dot.net 用它可以更轻松的阅读微软类库的源码,比如CLR、ASP.NET Core、EF Core等等,双击一个类或者属性方法可以查找引用和定义它们的地方,非常方便,它的源码都是最新版本的,来源就是GitHub上的相关仓库。找到实例化 HttpResponse 的为位置在 HttpContext 的子类 DefaultHttpContext 类中[ 点击查看源码👈 ] 。
public sealed class DefaultHttpContext : HttpContext
{
private readonly DefaultHttpRequest _request;
private readonly DefaultHttpResponse _response;
public DefaultHttpContext(IFeatureCollection features)
{
_features.Initalize(features);
_request = new DefaultHttpRequest(this);
_response = new DefaultHttpResponse(this);
}
public override HttpRequest Request => _request;
public override HttpResponse Response => _response;
}
防止大家比较绕解释一下,因为 HttpContext 是抽象类,它包含了抽象属性 HttpResponse 类型的属性 Response ,所以 HttpContext 必然有子类去集成它,由于 HttpResponse 也是抽象类,所以也必须包含了子类去继承它.
通过上面的代码我们可以看到 HttpResponse 的子类为 DefaultHttpResponse 类。找到类中 Body 属性定义的地方[ 点击查看源码👈 ]看一下具体实现 。
internal sealed class DefaultHttpResponse : HttpResponse
{
private static readonly Func<IFeatureCollection, IHttpResponseBodyFeature?> _nullResponseBodyFeature = f => null;
private readonly DefaultHttpContext _context;
private FeatureReferences<FeatureInterfaces> _features;
public DefaultHttpResponse(DefaultHttpContext context)
{
_context = context;
_features.Initalize(context.Features);
}
//在FeatureReferences<FeatureInterfaces>中取出ResponseBody的交互操作IHttpResponseBodyFeature
private IHttpResponseBodyFeature HttpResponseBodyFeature => _features.Fetch(ref _features.Cache.ResponseBody, _nullResponseBodyFeature)!;
//Body本身是Stream它是抽象类
public override Stream Body
{
//在IHttpResponseBodyFeature实例中查找Stream
get { return HttpResponseBodyFeature.Stream; }
set
{
var otherFeature = _features.Collection.GetRequiredFeature<IHttpResponseBodyFeature>();
if (otherFeature is StreamResponseBodyFeature streamFeature
&& streamFeature.PriorFeature != null
&& object.ReferenceEquals(value, streamFeature.PriorFeature.Stream))
{
_features.Collection.Set(streamFeature.PriorFeature);
return;
}
_features.Collection.Set<IHttpResponseBodyFeature>(new StreamResponseBodyFeature(value, otherFeature));
}
}
}
Body本身是Stream但是Stream是抽象类,但是这里并没有对Stream的子类直接进行定义,而是引入了 IHttpResponseBodyFeature 去和Stream交互,主要原因还是因为ResponseBody涉及到一个交互体系,比如包含PipeWriter、SendFile等操作。所以这里我们只能顺着 IHttpResponseBodyFeature 的操作找到相关的实现类,通过查找引用关系我找到了实现类 HttpProtocol [ 点击查看源码👈 ]我们看一下它的定义 。
internal partial class HttpProtocol : IFeatureCollection,
IHttpRequestFeature,
IHttpResponseFeature,
IHttpResponseBodyFeature,
IRouteValuesFeature,
IEndpointFeature,
IHttpRequestIdentifierFeature,
IHttpRequestTrailersFeature,
IHttpExtendedConnectFeature,
IHttpUpgradeFeature,
IRequestBodyPipeFeature,
IHttpConnectionFeature,
IHttpRequestLifetimeFeature,
IHttpBodyControlFeature,
IHttpMaxRequestBodySizeFeature,
IHttpRequestBodyDetectionFeature,
IHttpWebTransportFeature,
IBadRequestExceptionFeature
{
internal protected IHttpResponseBodyFeature? _currentIHttpResponseBodyFeature;
private void FastReset()
{
//省略一部分代码
_currentIHttpResponseBodyFeature = this;
//省略一部分代码
}
}
它实现了很多接口,其中包含了 IHttpResponseBodyFeature 接口和 IFeatureCollection 接口,这两个接口在 DefaultHttpResponse 类中都有涉猎,是Response输出的交互类,可以理解为Response类是门面,实际的操作都是调用的具体类。我们可以分析一下包含获取具体类型实例的操作,第一个便是它的 索引器 操作 。
internal protected IHttpResponseBodyFeature? _currentIHttpResponseBodyFeature;
object? IFeatureCollection.this[Type key]
{
get
{
object? feature = null;
//省略一部分代码
if (key == typeof(IHttpResponseBodyFeature))
{
feature = _currentIHttpResponseBodyFeature;
}
//省略一部分代码
return feature ?? ConnectionFeatures?[key];
}
set
{
_featureRevision++;
//省略一部分代码
if (key == typeof(IHttpResponseBodyFeature))
{
_currentIHttpResponseBodyFeature = (IHttpResponseBodyFeature?)value;
}
//省略一部分代码
}
}
它本身也提供Get和Set相关的类来操作和获取具体的相关的类型 。
TFeature? IFeatureCollection.Get<TFeature>() where TFeature : default
{
TFeature? feature = default;
if (typeof(TFeature) == typeof(IHttpResponseBodyFeature))
{
feature = Unsafe.As<IHttpResponseBodyFeature?, TFeature?>(ref _currentIHttpResponseBodyFeature);
}
return feature;
}
void IFeatureCollection.Set<TFeature>(TFeature? feature) where TFeature : default
{
_featureRevision++;
if (typeof(TFeature) == typeof(IHttpResponseBodyFeature))
{
_currentIHttpResponseBodyFeature = Unsafe.As<TFeature?, IHttpResponseBodyFeature?>(ref feature);
}
}
为什么会这样的,相信大家已经猜到了 HttpProtocol 实现了很多的接口,意味着它有很多接口的能力。提供的这几个方法可以根据类型快速的获取想得到的实例。因为在 HttpProtocol 定义了许多变量承载它实现的接口的变量来承载当前实例,所以在 DefaultHttpResponse 看到了类似缓存的效果获取具体接口的对应实例。我们知道了 HttpProtocol 实现了 IHttpResponseBodyFeature 接口,所以我们在 HttpProtocol 类中查找给 IHttpResponseBodyFeature的Stream 属性赋值的地方即可,通过上面 HttpProtocol 类的定义方式我们可以看到它是 partial 也就是部分类,在另一个部分类中找到了赋值的地方[ 点击查看源码👈 ] 。
Stream IHttpResponseBodyFeature.Stream => ResponseBody;
PipeWriter IHttpResponseBodyFeature.Writer => ResponseBodyPipeWriter;
Stream IHttpResponseFeature.Body
{
get => ResponseBody;
set => ResponseBody = value;
}
通过这个代码我们可以看到 IHttpResponseBodyFeature.Stream 来自 ResponseBody 属性,找到给 HttpProtocol属性ResponseBody 赋值的地方[ 点击查看源码👈 ] 。
protected BodyControl? _bodyControl;
public Stream ResponseBody { get; set; } = default!;
public PipeWriter ResponseBodyPipeWriter { get; set; } = default!;
public void InitializeBodyControl(MessageBody messageBody)
{
if (_bodyControl == null)
{
_bodyControl = new BodyControl(bodyControl: this, this);
}
(RequestBody, ResponseBody, RequestBodyPipeReader, ResponseBodyPipeWriter) = _bodyControl.Start(messageBody);
}
上面的代码我们可以看到 ResponseBody 定义和赋值的地方,我们可以看到给 ResponseBody 赋值来自 BodyControl 实例的 Start方法里 这个方法传递的是 当前HttpProtocol实例 ,所以直接找到 BodyControl.Start方法 定义的地方[ 点击查看源码👈 ]查看实现 。
internal sealed class BodyControl
{
//HttpResponseStream
private readonly HttpResponseStream _response;
private readonly HttpResponsePipeWriter _responseWriter;
private readonly HttpRequestPipeReader _requestReader;
private readonly HttpRequestStream _request;
public BodyControl(IHttpBodyControlFeature bodyControl, IHttpResponseControl responseControl)
{
_requestReader = new HttpRequestPipeReader();
_request = new HttpRequestStream(bodyControl, _requestReader);
_responseWriter = new HttpResponsePipeWriter(responseControl);
//实例化HttpResponseStream的地方
_response = new HttpResponseStream(bodyControl, _responseWriter);
}
public (Stream request, Stream response, PipeReader reader, PipeWriter writer) Start(MessageBody body)
{
//省略代码
if (body.RequestUpgrade)
{
//默认走不到暂时忽略
}
else if (body.ExtendedConnect)
{
//默认走不到暂时忽略
}
else
{
//默认走到这里
return (_request, _response, _requestReader, _responseWriter);
}
}
}
好了,饶了这么多的弯,我们水落石出了找到了 HttpResponse.Body 的最终来源来自 HttpResponseStream 类的实例。所以结论就是HttpResponse的Body是HttpResponseStream实例。总结一下 。
IHttpResponseBodyFeature
实例中获取 Stream
属性,这个类负责是ResponseBody相关的交互。 HttpProtocol
,这是一个部分类。在这里 IHttpResponseBodyFeature.Stream
属性来自 HttpProtocol类ResponseBody
属性。 HttpProtocol类ResponseBody
属性赋值来自 BodyControl的Start方法
,它返回的是 BodyControl
类的 _response
属性,这个属性的是 HttpResponseStream
类型的。 HttpResponse.Body
也就是Stream类型的,来自 HttpResponseStream
类的实例。 上面饶了这么大的圈找到了 HttpResponse.Body 实例的类型 HttpResponseStream 类,找到类定义的地方看一下里面的实现[ 点击查看源码👈 ] 。
internal sealed partial class HttpResponseStream : Stream
{
//说明不支持读,如果想知道流是否可读可以使用这个属性先判断
public override bool CanRead => false;
//流不可查找
public override bool CanSeek => false;
//支持写
public override bool CanWrite => true;
//不能获取流的长度否则抛出异常
public override long Length => throw new NotSupportedException(SR.net_noseek);
//不可读取和设置位置否则抛出异常
public override long Position
{
get => throw new NotSupportedException(SR.net_noseek);
set => throw new NotSupportedException(SR.net_noseek);
}
//不支持设置Seek否则抛出异常
public override long Seek(long offset, SeekOrigin origin) => throw new NotSupportedException(SR.net_noseek);
//不支持Length否则抛出异常
public override void SetLength(long value) => throw new NotSupportedException(SR.net_noseek);
//不支持读取操作否则抛出异常
public override int Read(byte[] buffer, int offset, int size) => throw new InvalidOperationException(SR.net_writeonlystream);
//不支持读读相关的操作
public override IAsyncResult BeginRead(byte[] buffer, int offset, int size, AsyncCallback? callback, object? state)
{
throw new InvalidOperationException(SR.net_writeonlystream);
}
public override int EndRead(IAsyncResult asyncResult) => throw new InvalidOperationException(SR.net_writeonlystream);
//省略写相关方法和释放相关的方法,只看设计到读相关的地方
}
通过 HttpResponseStream 类的定义我们可以看到, HttpResponseStream 本身是 Stream 抽象类的子类。涉及到读相关的方法是直接抛出异常,也就是最开始我们直接读取 HttpResponse.Body 读取直接抛出异常的原因。不仅仅是读取的方法不可用Postion、Length、Seek相关的方法都是不可操作的,操作了都会抛出异常.
从ASP.NET Core6.0之后开始,推出了HTTP日志记录功能,使用方式如下 。
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddHttpLogging(logging =>
{
logging.LoggingFields = HttpLoggingFields.ResponseBody;
logging.RequestBodyLogLimit = 4096;
});
var app = builder.Build();
app.UseHttpLogging();
不过我们通过上面看到了 HttpResponse.Body 默认情况下是不可以读取的,但是输出Http日志时候是可以读取 ResponseBody 的,所以我们可以看一下里面的相关实现,在 HttpLoggingMiddleware 中间件里,因为这个中间件里涉及到Http日志记录的相关逻辑实现,而ResponseBody只是其中的一个选项,所以咱们只关注这一部分的实现[ 点击查看源码👈 ] 。
ResponseBufferingStream? responseBufferingStream = null;
IHttpResponseBodyFeature? originalBodyFeature = null;
try
{
//获取原始的response
var response = context.Response;
if (options.LoggingFields.HasFlag(HttpLoggingFields.ResponseBody))
{
//保存原始的IHttpResponseBodyFeature也就是上面提到的ResponseBody交互类
originalBodyFeature = context.Features.Get<IHttpResponseBodyFeature>()!;
//实例化ResponseBufferingStream
responseBufferingStream = new ResponseBufferingStream(originalBodyFeature,
options.ResponseBodyLogLimit,
_logger,
context,
options.MediaTypeOptions.MediaTypeStates,
options);
//用ResponseBufferingStream实例替换原始ResponseBody
response.Body = responseBufferingStream;
//将responseBufferingStream设置到当前的IHttpResponseBodyFeature
context.Features.Set<IHttpResponseBodyFeature>(responseBufferingStream);
}
await _next(context);
//输出日志
if (requestBufferingStream?.HasLogged == false)
{
requestBufferingStream.LogRequestBody();
}
if (responseBufferingStream != null)
{
var responseBody = responseBufferingStream.GetString(responseBufferingStream.Encoding);
if (!string.IsNullOrEmpty(responseBody))
{
_logger.ResponseBody(responseBody);
}
}
}
finally
{
responseBufferingStream?.Dispose();
if (originalBodyFeature != null)
{
//还原原始的IHttpResponseBodyFeature
context.Features.Set(originalBodyFeature);
}
}
通过上面的代码我们可以看到,其实也是实现了类似的操作,用 ResponseBufferingStream 替换掉原始的 HttpResponseStream 类型,替换的逻辑要在中间件执行 next() 之前,操作完成之后也就是执行了 next() 之后再把原始的 IHttpResponseBodyFeature 替换回来,有关具体的 ResponseBufferingStream 实现方式咱们这里不做详细描述了,不是本文重点.
ResponseBufferingStream 的实现并不是使用 MemoryStream 这种可读取的流替换掉默认的HttpResponseStream, ResponseBufferingStream 的 LogRequestBody() 方法使用 ILogger 输出日志并没有直接去读取Stream,而是反其道重写了Stream的 Write() 方法,因为对HttpResponseBody实例 HttpResponseStream 的输出写操作本质是调用Stream的 Write() 方法,重写了 Write() 方法之后会把写入的内容记录到 Buffer 中, LogRequestBody() 方法通过读取Buffer中的内容得到字符串,使用ILogger输出日志.
在之前的讨论中有许多小伙伴对用 MemoryStream 替换ResponseBody存在一个疑惑,就是既然已经替换掉了,一直用 MemoryStream 不就好了嘛,为啥还要把ResponseBody原始值记录下来,结束后再替换回来。这个疑问咋一听确实也没毛病,但是等大致了解了它的使用过程之后才恍然大悟,原来是这么回事,在这里咱们就看一下为啥会是这样。 首先说一下结论,如果把ResponseBody替换为 MemoryStream 之后,不对原始的ResponseBody进行操作的话,在这个中间件(类似上面说的到的UseResponseBodyRead中间件)之后的操作,可能是后续的其它中间件或者是各种终结点比如Controller的Action亦或者是MinimalApi的Map方法等,是可以读取和写入值的,也就是在替换中间件的范围内,也就是大家经常说的套娃模式,被它套进去的是一直生效的,没任何问题, 终结点本身也是中间件 。下面这张图相信大家经常看到 打个比方如果我的 UseResponseBodyRead 中间件是图里的Middleware1把ResponseBody替换为 MemoryStream ,那么后续的操作比如Middleware2和Middleware3还有后续的终结点之类的读取ResponseBody是完全没有问题的。但是最终Http的输出结果肯定是不符合预期的,这主要涉及到 HttpResponseStream.Write() 的问题,我们知道最终我们输出的结果会体现在 Write() 方法上[ 点击查看源码👈 ],核心代码如下所示 。
internal sealed class HttpResponseStream : Stream
{
private readonly HttpResponsePipeWriter _pipeWriter;
private readonly IHttpBodyControlFeature _bodyControl;
public HttpResponseStream(IHttpBodyControlFeature bodyControl, HttpResponsePipeWriter pipeWriter)
{
_bodyControl = bodyControl;
_pipeWriter = pipeWriter;
}
//重写Stream的Write操作
public override void Write(byte[] buffer, int offset, int count)
{
if (!_bodyControl.AllowSynchronousIO)
{
throw new InvalidOperationException(CoreStrings.SynchronousWritesDisallowed);
}
//调用WriteAsync方法
WriteAsync(buffer, offset, count, default).GetAwaiter().GetResult();
}
public override Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
{
//本质调用了HttpResponsePipeWriter的写方法
return _pipeWriter.WriteAsync(new ReadOnlyMemory<byte>(buffer, offset, count), cancellationToken).GetAsTask();
}
)
通过上面我们可以看到 HttpResponseStream 的 Write() 方法本质是调用了 HttpResponsePipeWriter 的 WriteAsync() 方法,HttpResponseStream本身不存储写入的数据。而 HttpResponsePipeWriter 实例的构建是在 BodyControl 类中上面咱们已经粘贴过实例化的源码了,可自行翻阅上去看看 HttpResponsePipeWriter 类的定义相关。所以上面把ResponseBody替换为 MemoryStream ,最终的结果要体现在 HttpResponseStream 实例中,否则的话没有办法正常输出。可以用一个伪代码例子演示一下这个原理 。
Order order1 = new Order
{
Address = "北京市海淀区"
};
SetOrder(order1);
Console.WriteLine($"最后地址:{order1.Address}");
public void SetOrder(Order order2)
{
order2 = new Order
{
Address = "上海市闵行区"
};
Console.WriteLine($"设置地址:{order2.Address}");
}
这个示例中即使 SetOrder 方法中设置了新的Address,但是脱离了 SetOrder 方法作用域后,外面的最后地址依然是 北京市海淀区 。在调用 SetOrder 进入方法的时候order1和方法形参order2都指向的是 Address = "北京市海淀区" ,在SetOrder方法内部完成实例化之后order2指向的是 Address = "上海市闵行区" ,但是order1依然指向的是 Address = "北京市海淀区" ,因为引用传递形参本身只是存储的引用地址,更换了引用地址就和原来的地址脱钩了,如果想让内外行为一直必须要体现到原始值上面去。我们替换 ResponseBody 的时候也是同理,最终Write本质还是要依赖 HttpResponseStream 里的 HttpResponsePipeWriter 属性,但是 MemoryStream 可没有 HttpResponsePipeWriter 。你可能会有疑问,我上面也没把 MemoryStream 结果 Write() 到 HttpResponseStream 里去啊?但是上面使用了 CopyToAsync 方法与原始的的ResponseBody类型 HttpResponseStream 交互,CopyToAsync方法本质就是在调用 WriteAsync() 方法,口说无凭直接上代码[ 点击查看源码👈 ],核心代码如下所示 。
public virtual Task CopyToAsync(Stream destination, int bufferSize, CancellationToken cancellationToken)
{
//省略一部分代码
return Core(this, destination, bufferSize, cancellationToken);
static async Task Core(Stream source, Stream destination, int bufferSize, CancellationToken cancellationToken)
{
//使用了对象池复用空间
byte[] buffer = ArrayPool<byte>.Shared.Rent(bufferSize);
try
{
int bytesRead;
while ((bytesRead = await source.ReadAsync(new Memory<byte>(buffer), cancellationToken).ConfigureAwait(false)) != 0)
{
//最终也是调用的目标流的WriteAsync方法
await destination.WriteAsync(new ReadOnlyMemory<byte>(buffer, 0, bytesRead), cancellationToken).ConfigureAwait(false);
}
}
finally
{
ArrayPool<byte>.Shared.Return(buffer);
}
}
}
本文主要讲解了如何读取 ResponseBody ,默认情况下是不可以读取的,需要我们使用了中间件结合 MemoryStream 自行处理一下,同时我们结合和Http日志记录中间件里的处理方式对比了一下,最终答疑了为了要把替换的结果还得继续体现在原始的 ResponseBody 上面去,整体来说这方面还是相对容易理解的,只是找起来可能比较麻烦。大致总结一下 。
HttpResponseStream
这个类重写了Stream的Read相关的方法,但是实现是抛出异常的,所以我们需要可读的类来替换默认的操作, MemoryStream
可以辅助实现。 UseHttpLogging
中间件也可以读取ResponseBody里的结果,但是它是使用的重写Stream的Write相关的方法,在Write方法里使用Buffer记录了写过的数据,然后通过 GetString()
方法读取Buffer里的内容实现记录要输出的值。 MemoryStream
解决的是我们在写代码过程中对ResponseBody的读取或写入操作,但是程序处理完之后要把 MemoryStream
的结果在体现到 HttpResponseStream
中去,否则虽然程序中读取写入Body没问题,但是输出的结果会出问题。 说句题外话, ChatGTP 的发布对人们心里的冲击还是挺大的,因为它表现出来的强大效果让人眼前一亮,很多博主和企业也借此风口寻找新的出路,甚至有人会担心会不会被替代失业。个人以为新的技术大行其道必然会带来新的产业,新的产业的新的岗位同时也是需要更多的人参与进来。所以保持对新事物的好奇心多多参与。工具不会替代人,能替代人的是会使用工具的人.
最后此篇关于由ASP.NETCore读取Response.Body引发的思考的文章就讲到这里了,如果你想了解更多关于由ASP.NETCore读取Response.Body引发的思考的内容请搜索CFSDN的文章或继续浏览相关文章,希望大家以后支持我的博客! 。
Transformer 模型是 AI 系统的基础。已经有了数不清的关于 "Transformer 如何工作" 的核心结构图表。 但是这些图表没有提供任何直观的计算该
我有一个很大的索引定义,索引需要很长时间。我怀疑主要问题是由生成的许多 LEFT OUTER JOIN 引起的。 我看到了 this question ,但找不到有关使用 source: :query
我目前是FP的学生。当我查看不同函数式语言提供的不同语法时,我在 Elm 示例代码中遇到了一个模式。我对此很好奇。 这是示例代码 myList = [{foo = "bar1"},{foo = "ba
我正在尝试使用 HTML 和 CSS 进行响应式设计,这是我的问题: 如果你只调整窗口大小,布局适合,如果你只缩放,布局再次适合,我没有问题,但是如果在调整窗口大小时缩放,布局会中断一点。 找到有关此
我正在寻找模型项目。项目属于公司,因此列表中的所有项目都可以附加相同的公司。结果列表示例: CompanyA - ProjectA CompanyA - ProjectO CompanyA - Pro
我使用的编程语言是 Java。我一直在过渡到有些困难的 C++。 “难懂”不在学习中,更多的是“用 C++ 思考”。 我看到很多人说你应该先学习C(我在技术上已经知道了),然后我看到有人说不要跳过C直
我正在整理一个存储库类型的 rails 3 站点。 我安装了 Thinking Sphinx 并在我的网站上工作,因为我可以输入类似 localhost:3000/articles?search=te
我正在使用 sphinx 搜索 2 个模型及其关联。我正在使用增量索引。在开发模式 (Ubuntu) 下,我的开发箱上一切正常。但是,在暂存盒生产环境中,当我创建新记录时,我需要再次构建索引以使新创建
我已将 delta 列添加到我的表中: class AddDeltaIndexToCimgs < ActiveRecord::Migration def change add_column
我安装了think sphinx,运行 rake ts:index 后,无法配置开发文件。文件已创建,但它是空的。 Generating configuration to /Users/lexi87/
这很奇怪。我有 3 个模型(A、B、C)。当 crontab 运行它时,我们工作得很好。 最近,我在为模型 A 索引新条目时遇到问题。 当我手动调用 rake ts:index RAILS_ENV=p
如果我的搜索包含类别(外键)和可选文本,我是否应该使用 Thinking sphinx 来“搜索”未提交搜索字符串(仅提交类别)的地方? 最佳答案 这实际上取决于您的用例。举例来说,假设您有博客文章,
以下代码来自Thinking in C++。作者提到“由于 operator[] 是内联的,您可以使用这种方法来保证不会发生数组边界违规,然后删除传送代码的 require()。”这里指的是内联函数的
这是我确定的: Delta 索引在开发中运行良好 当我推送到生产服务器时,Delta 索引不起作用,并且 searchd.log 中没有记录任何操作 我正在运行 Phusion Passenger,并
最近接了一个项目,性质比较独特,想请教大家一些建议。 我分别使用 asp.net/SQL Server 和 php/mysql。我从来没有把它们混为一谈。但是,我当前的项目要求我在使用 SQL Ser
我目前正忙于学习 Ruby 和 Rails,并且由于我有基于 C 语言的背景,Ruby 的一些概念是新的并且有些陌生。对我来说特别具有挑战性的是适应处理常见问题的“Ruby 方式”,因此我经常发现自己
每次我尝试在 Mac OSX Snow Leopard 上运行带有 Rails 3 的 Sphinx 2.0.1 时,我都会收到以下错误: Failed to start searchd daemon
我正在努力提高我的 TDD/OO 技能,但每次我尝试使用 TDD 来影响设计时,我都会遇到从哪里开始的障碍。 这是我的用例/故事: Identify a subset of clients that
得到这个 HTML: Un Deux Trois mother 在页面中,但是 children,select 和 input 都是动态生成的 试着用类似
我以前使用过 javascript,但现在我开始使用 angularjs,但我对如何做基本的事情感到困惑。 例如:在 JavaScript 中: //here is how i create a c
我是一名优秀的程序员,十分优秀!