C#WebApi同一个接口同一个会话会调用多次,如何判断是否是会话重复请求

戢翔 2021-05-01 09:13:14
如下代码,WebApi提供一个下载接口,但是每次只访问一次这个接口,这个接口会重复触发多次。导致我写日志的时候,原本只访问一次下载接口,同一个会话写了很多次的相同下载记录。请问有什么标识可以判断是否重复请求同一个会话么?
[HttpGet]
public HttpResponseMessage DownFile([FromUri] string fileGuid, [FromUri] string fileName, [FromUri] string sign)
{
var file = Path.Combine(ConfigHelper.GetInventoryFileSaveDirectory(), HttpUtility.UrlDecode(fileGuid), HttpUtility.UrlDecode(fileName));
FileStream stream = new FileStream(file, FileMode.Open, FileAccess.Read);
{
HttpResponseMessage result = new HttpResponseMessage(System.Net.HttpStatusCode.OK);
result.Content = new StreamContent(stream);
result.Content.Headers.ContentType = new System.Net.Http.Headers.MediaTypeHeaderValue("application/octet-stream");
result.Content.Headers.ContentDisposition = new System.Net.Http.Headers.ContentDispositionHeaderValue("attachment")
{
FileName = HttpUtility.UrlEncode(Path.GetFileName(file))
};
//记录下载日志,同一个会话会多次调用

return result;
}
}
...全文
3003 56 打赏 收藏 举报
写回复
56 条回复
切换为时间正序
请发表友善的回复…
发表回复
xuzuning 2021-05-18
http 206 一般通过 字段 Content-Ranges: 告知本次发送的数据的长度信息 而客户端在请求中用 HTTP_RANGE 字段告知已经获取的据的长度信息 写文件或流的方法都会返回本次写入的字节数 很难想象你连协议的内容都可以不清楚就能开发程序
  • 打赏
  • 举报
回复
戢翔 2021-05-18
引用 50 楼 xuzuning 的回复:
断点续传 是通过 HTTP状态码206 实现的 你总不至于将 206 状态吗,也视为重复请求吧? 难道每次的内容不一样也视而不见
麻烦问一下,服务端能拿到客户端请求Request的状态码么?
  • 打赏
  • 举报
回复
戢翔 2021-05-18
引用 50 楼 xuzuning 的回复:
断点续传 是通过 HTTP状态码206 实现的 你总不至于将 206 状态吗,也视为重复请求吧? 难道每次的内容不一样也视而不见
我主要是没办法判断用户什么时候把流读取完了,因为用户把文件流读取完了,才表示把整个文件下载完毕,我才好做记录。
  • 打赏
  • 举报
回复
xuzuning 2021-05-18
断点续传 是通过 HTTP状态码206 实现的 你总不至于将 206 状态吗,也视为重复请求吧? 难道每次的内容不一样也视而不见
  • 打赏
  • 举报
回复
戢翔 2021-05-18
结贴前统一回复一下,首先谢谢大家的回答。同构大家的回答,我查阅了断点续传的资料,大体明白是断点续传导致的重复进入接口,所以我通过Request.Headers.Range == null,这个来判断是否是断点续传,就可以达到区分下载的目的(当然这个不能保证下载完毕,只能说客户端调用了这个接口)。 以下是我查阅的资料,写的比较详细,大家共同学习吧。 https://www.cnblogs.com/CreateMyself/p/6063646.htmlASP.NET WebAPi之断点续传下载(上) https://www.cnblogs.com/CreateMyself/p/6078052.htmlASP.NET WebAPi之断点续传下载(中) https://www.cnblogs.com/CreateMyself/p/6079692.htmlASP.NET WebAPi之断点续传下载(下)
  • 打赏
  • 举报
回复
xuzuning 2021-05-18
计算剩余的长度,最后一次性发送,这是你的权力,不必等待客户端回应
  • 打赏
  • 举报
回复
戢翔 2021-05-18
引用 53 楼 xuzuning 的回复:
http 206 一般通过 字段 Content-Ranges: 告知本次发送的数据的长度信息 而客户端在请求中用 HTTP_RANGE 字段告知已经获取的据的长度信息 写文件或流的方法都会返回本次写入的字节数 很难想象你连协议的内容都可以不清楚就能开发程序
谢谢,我也是才开始接触写webapi,所以遇到不会的才在网上查资料的。麻烦咨询一下,我通过Request.Headers.Range == null可以判断是否是第一次请求,后面每次的断点续传都能拿到Form,但是To是null。我无法确定或者可以通过监听什么事件能知道客户端下载流完毕了呢?
  • 打赏
  • 举报
回复
正怒月神 2021-05-17
引用 21 楼 戢翔 的回复:
[quote=引用 13 楼 正怒月神 的回复:]你先确定,是用户点击了下载多次? 还是你日志上,需要把多次下载合并成一次?
用户在浏览器只输入一次下载地址,然后服务端的这个接口会被调用多次。我的目的是用户只下载一次就只记录一次,因为多次进入这个接口都是重复的。[/quote] 那我觉得你需要的,只是“幂等”。
  • 打赏
  • 举报
回复
wanghui0380 2021-05-17
哎,你反复重复说那么几句话,无助你解决问题。想解决问题,请好好的认真的看回复,不要人家一回复,你看都不看就反复引用,然后跟一句“我是这样啊,我就是这样啊,我还是这样啊”-----这要这样回复100年都无法解决问题 先看人家怎么处理断点续传问题 https://www.cnblogs.com/Leo_wl/p/8467796.html 你通过断点续传的代码就可以知道到底是不是续传的 然后我们在处理每次httpconext断开后,文件是否到结尾了就行。这个我暂时不想做中间件,我先可以考虑在 HttpContext.Response.CompleteAsync 这块做,或者在 HttpContext.Response.OnCompleted里做。(具体代码我不写了,我先抛个砖)
  • 打赏
  • 举报
回复
戢翔 2021-05-17
引用 35 楼 闪耀星星 的回复:
需要自己做业务逻辑处理,加锁同步调用,每次调用产生新的请求ID
哎,你都没有看清我的需求。
  • 打赏
  • 举报
回复
戢翔 2021-05-17
引用 48 楼 戢翔 的回复:
[quote=引用 47 楼 wanghui0380 的回复:]https://github.com/dotnet/aspnetcore/blob/fdabaf00a6720d852ec3120b7bbe4b14de2f3ccf/src/Mvc/Mvc.Core/src/Infrastructure/FileStreamResultExecutor.cs#L28 来看看微软的源代码,很明显他会构造一个action到pipeline上,同时FileResultExecutorBase基类负责处理那篇博文的头部range分析 最后他自己异步写入 return WriteFileAsync(context.HttpContext, result.FileStream, range, rangeLength); 现在问题来了,如果是你要求的浏览器的暂停,继续这个没有问题,我们可以通过等待这个流结束作为判定依据,但是如果是迅雷那种多线程下载的,他本身就是并行分块,只单纯说等待结束,貌似还差条件 理论上还需要 把 result.FileStream,的posion和file.length 做一次比较才能最终确定是输出到尾部了(貌似还不成,因为他并行下载,也许第一个分块他就从尾部取了,哎,这个貌似还得用东西把他记着了)
好吧[/quote]谢谢你了
  • 打赏
  • 举报
回复
戢翔 2021-05-17
引用 47 楼 wanghui0380 的回复:
https://github.com/dotnet/aspnetcore/blob/fdabaf00a6720d852ec3120b7bbe4b14de2f3ccf/src/Mvc/Mvc.Core/src/Infrastructure/FileStreamResultExecutor.cs#L28 来看看微软的源代码,很明显他会构造一个action到pipeline上,同时FileResultExecutorBase基类负责处理那篇博文的头部range分析 最后他自己异步写入 return WriteFileAsync(context.HttpContext, result.FileStream, range, rangeLength); 现在问题来了,如果是你要求的浏览器的暂停,继续这个没有问题,我们可以通过等待这个流结束作为判定依据,但是如果是迅雷那种多线程下载的,他本身就是并行分块,只单纯说等待结束,貌似还差条件 理论上还需要 把 result.FileStream,的posion和file.length 做一次比较才能最终确定是输出到尾部了(貌似还不成,因为他并行下载,也许第一个分块他就从尾部取了,哎,这个貌似还得用东西把他记着了)
好吧
  • 打赏
  • 举报
回复
wanghui0380 2021-05-17
https://github.com/dotnet/aspnetcore/blob/fdabaf00a6720d852ec3120b7bbe4b14de2f3ccf/src/Mvc/Mvc.Core/src/Infrastructure/FileStreamResultExecutor.cs#L28 来看看微软的源代码,很明显他会构造一个action到pipeline上,同时FileResultExecutorBase基类负责处理那篇博文的头部range分析 最后他自己异步写入 return WriteFileAsync(context.HttpContext, result.FileStream, range, rangeLength); 现在问题来了,如果是你要求的浏览器的暂停,继续这个没有问题,我们可以通过等待这个流结束作为判定依据,但是如果是迅雷那种多线程下载的,他本身就是并行分块,只单纯说等待结束,貌似还差条件 理论上还需要 把 result.FileStream,的posion和file.length 做一次比较才能最终确定是输出到尾部了(貌似还不成,因为他并行下载,也许第一个分块他就从尾部取了,哎,这个貌似还得用东西把他记着了)
  • 打赏
  • 举报
回复
戢翔 2021-05-17
引用 44 楼 wanghui0380 的回复:
哎,又来了,无论别人回复啥,你这里总是“我就是这样,我就要这样” 1.我不知道到底谁告诉你,什么webapi不能用using??博客园么?? 2.我想知道,那篇博文最后一句话:“FileResult已经默认实现了断点续传么”看见了么 3.我想知道,你们写代码,去完全只听博客园的么?博客园告诉你们不能这样,不能那样,你们就只听博客园得,代码运行过没有?没运行过,你就天天凭着博客园得不能这样,不能那样,去判定一个代码能否达到要求,然后就说这样不可以啊--博客园说的
不能用using那个是我测试出来的啊,运行了代码测试出来的啊,所以我才让你
引用 45 楼 wanghui0380 的回复:
请自己想 为啥我用的是async Task 为啥这里是await file.ExecuteResultAsync(this.ControllerContext); 为啥这个方法需要你把ControllerContext传递进去 很简单,File.ExecuteResultAsync 是一个异步操作,而这个异步操作会对 Request进行拦截判定,进行一个异步的流写入。这个对象会被插入到pipeline里,他不会再次进入action里(因为他在pipeline里的位置比action还高,当整个流被客户端tcp确认后,会发个信号然后回调到你代码) 当然整个过程,对于你说浏览器那种单线程暂停,继续是没啥问题的,但对于迅雷这种多线程并行请求的我到有些疑问
麻烦发一下this.File的方法呢。另外请问一下,我这个版本要求是net4.0的,能使用你那个异步方法么?
  • 打赏
  • 举报
回复
wanghui0380 2021-05-17
请自己想 为啥我用的是async Task 为啥这里是await file.ExecuteResultAsync(this.ControllerContext); 为啥这个方法需要你把ControllerContext传递进去 很简单,File.ExecuteResultAsync 是一个异步操作,而这个异步操作会对 Request进行拦截判定,进行一个异步的流写入。这个对象会被插入到pipeline里,他不会再次进入action里(因为他在pipeline里的位置比action还高,当整个流被客户端tcp确认后,会发个信号然后回调到你代码) 当然整个过程,对于你说浏览器那种单线程暂停,继续是没啥问题的,但对于迅雷这种多线程并行请求的我到有些疑问
  • 打赏
  • 举报
回复
wanghui0380 2021-05-17
哎,又来了,无论别人回复啥,你这里总是“我就是这样,我就要这样” 1.我不知道到底谁告诉你,什么webapi不能用using??博客园么?? 2.我想知道,那篇博文最后一句话:“FileResult已经默认实现了断点续传么”看见了么 3.我想知道,你们写代码,去完全只听博客园的么?博客园告诉你们不能这样,不能那样,你们就只听博客园得,代码运行过没有?没运行过,你就天天凭着博客园得不能这样,不能那样,去判定一个代码能否达到要求,然后就说这样不可以啊--博客园说的
  • 打赏
  • 举报
回复
戢翔 2021-05-17
引用 42 楼 戢翔 的回复:
[quote=引用 41 楼 wanghui0380 的回复:]在换一下,手动调用没必要返回东西
  [HttpGet]
        public async Task Get()
        {
            var fileinfo = new FileInfo(@"E:\迅雷下载\MicrosoftOffice2016.zip");

            using (FileStream fs = fileinfo.OpenRead())
            {
                var file = this.File(fs, "application/octet-stream");
                try
                {
                    await file.ExecuteResultAsync(this.ControllerContext);
                    logger.LogInformation("下载完毕");

                }
                catch (Exception e)
                {
                    logger.LogInformation("下载失败");
                }

                
            }


        }
能发一下this.File的方法体么?[/quote]另外说一下,using (FileStream fs = fileinfo.OpenRead())你这个写法有问题,webapi不能调用流释放,不然会导致下载错误,你可以试一下,因为webapi会调用完毕会自动释放,它也自动做了断点续传的功能。所以我才会有疑问,用户手动下载,暂停、继续,会重复的进入该接口,那么如果你没有判断断点续传的话,你这段代码I必然会重复的记录日志。
  • 打赏
  • 举报
回复
戢翔 2021-05-17
引用 41 楼 wanghui0380 的回复:
在换一下,手动调用没必要返回东西
  [HttpGet]
        public async Task Get()
        {
            var fileinfo = new FileInfo(@"E:\迅雷下载\MicrosoftOffice2016.zip");

            using (FileStream fs = fileinfo.OpenRead())
            {
                var file = this.File(fs, "application/octet-stream");
                try
                {
                    await file.ExecuteResultAsync(this.ControllerContext);
                    logger.LogInformation("下载完毕");

                }
                catch (Exception e)
                {
                    logger.LogInformation("下载失败");
                }

                
            }


        }
能发一下this.File的方法体么?
  • 打赏
  • 举报
回复
wanghui0380 2021-05-17
在换一下,手动调用没必要返回东西
  [HttpGet]
        public async Task Get()
        {
            var fileinfo = new FileInfo(@"E:\迅雷下载\MicrosoftOffice2016.zip");

            using (FileStream fs = fileinfo.OpenRead())
            {
                var file = this.File(fs, "application/octet-stream");
                try
                {
                    await file.ExecuteResultAsync(this.ControllerContext);
                    logger.LogInformation("下载完毕");

                }
                catch (Exception e)
                {
                    logger.LogInformation("下载失败");
                }

                
            }


        }
  • 打赏
  • 举报
回复
wanghui0380 2021-05-17
在文章最后,人家提了FileResult已经支持,那么直接用就是,我随手写了一个测试
 [ApiController]
    [Route("[controller]")]
    public class TestController : ControllerBase
    {
        private readonly ILogger<TestController> logger;

        public TestController(ILogger<TestController> logger)
        {


            this.logger = logger;
        }

        [HttpGet]
        public async Task<ActionResult> Get()
        {
            var fileinfo = new FileInfo(@"E:\迅雷下载\MicrosoftOffice2016.zip");

            using (FileStream fs = fileinfo.OpenRead())
            {
                var file = this.File(fs, "application/octet-stream");
                try
                {
                    await file.ExecuteResultAsync(this.ControllerContext);
                    logger.LogInformation("下载完毕");

                }
                catch (Exception e)
                {
                    logger.LogInformation("下载失败");
                }

                return file;
            }


        }




    }
  • 打赏
  • 举报
回复
加载更多回复(36)
发帖
C#

10.8w+

社区成员

.NET技术 C#
社区管理员
  • C#
  • Web++
  • by_封爱
加入社区
帖子事件
创建了帖子
2021-05-01 09:13
社区公告

让您成为最强悍的C#开发者