大请求、请求超时问题

news/2024/10/3 21:27:02

耗时很长的请求怎么处理?比如数据量大的。业务逻辑处理时间太久,以至于响应超时

这里的超时响应指的是ReadTimeOut,即发送请求内容完毕到接收响应数据开始的这段时间。普通HTTP请求可能在这段时间没有响应超时。

HTTP分块传输(Chunked Transfer Encoding)中每个数据块的到达都会刷新ReadTimeOut。服务器推送事件(SSE)中服务器会自动发送心跳消息刷新ReadTimeOut。由于这种分块或流式传输的方式每次消息处理的业务量和数据量较小,可以减少超时。

这两种只是让请求方尽快看到结果,数据出来一次就推送一次,并不能减少全部数据处理完毕的时间。而js可以收到一次回调我们的代码,打印或者处理一次,而不是收到全部所有数据后再将控制权交给我们的代码。要分批返回数据,就要求服务端的业务逻辑代码不要一次性处理所有数据,而是分批处理或查询。

http报文分块传输

HTTP/1.1 200 OK
Content-Type: application/json
Transfer-Encoding: chunked4A
[{"id": 1, "name": "Alice"}, {"id": 2, "name": "Bob"}, {"id": 3, "name": "Charlie"}]
4C
[{"id": 4, "name": "David"}, {"id": 5, "name": "Eve"}, {"id": 6, "name": "Frank"}]
42
[{"id": 7, "name": "Grace"}, {"id": 8, "name": "Helen"}, {"id": 9, "name": "Ian"}]
0
  • 添加Transfer-Encoding: chunked头部表明这是一个分块传输响应
  • 4A、4C、和42是各个块的字节大小(十六进制形式),分别对应第一、二、三块数据的长度。
  • JSON数据紧跟在块大小后。
  • 每个块后面跟一个CRLF。
  • 0后跟一个CRLF,表示数据结束。

这一个的问题在于服务器发送和浏览器接收是什么形式?什么表现?我需要试一下。

  • 服务端
[HttpGet]
public async IAsyncEnumerable<string> Get()
{var dataList = new[]{new { Id = 1, Name = "Alice" },new { Id = 2, Name = "Bob" },new { Id = 3, Name = "Charlie" }};foreach (var data in dataList){// 模拟数据处理延迟await Task.Delay(2000); // 模拟处理时间yield return $"ID: {data.Id}, Name: {data.Name}\n";}
}

服务端返回一个异步流。使用了IAsyncEnumerable<T>,kestrel就会为响应头添加分块字段。具体来说kestrel内部会使用await foreach迭代这个方法,等待每个数据块的生成,并一次次推送响应数据

  • 浏览器
async function fetchData() {try {const response = await fetch('/data');if (!response.ok) {throw new Error('Network response was not ok');}const reader = response.body.getReader();const decoder = new TextDecoder('utf-8');const list = document.getElementById('data-list');while (true) {const { value, done } = await reader.read();if (done) break;const textChunk = decoder.decode(value, { stream: true });const li = document.createElement('li');li.textContent = textChunk.trim();list.appendChild(li);}} catch (error) {console.error('Fetch Error:', error);}
}

看看实际运行效果,每次读取响应体都会有2秒延迟

image

看这个时间解析,第一次读取时,遇到第一个Task.Delay(2000),然后开始响应数据。绿色部分走完,浏览器得到响应第一部分数据,进入蓝色部分。

image

这种只能解决传输慢的问题,让接收方尽早看到数据,但不能加快全部数据响应完成时间。

SSE流式传输

流式传输服务端需要设置特定响应头,然后保持http连接,直接向响应中写数据和推送,而不是返回数据,释放连接。

  • 服务端
public async Task<IActionResult> Stream()
{HttpContext.Response.ContentType = "text/event-stream";HttpContext.Response.Headers.Add("Cache-Control", "no-cache");HttpContext.Response.Headers.Add("Connection", "keep-alive");// 周期性推数据while (true){// 推送模拟数据var message = $"data: {System.Text.Json.JsonSerializer.Serialize(new { message = "Hello, world!", timestamp = DateTime.UtcNow })}\n\n";await Response.WriteAsync(message);await Response.Body.FlushAsync();//1S间隔再推送await Task.Delay(1000);}
}
  • 浏览器
const eventSource = new EventSource('/api/sse/stream');eventSource.onmessage = function(event) {const message = JSON.parse(event.data);const messageElement = document.createElement('div');messageElement.textContent = `Message: ${message.message}, Timestamp: ${message.timestamp}`;document.getElementById('messages').appendChild(messageElement);
};eventSource.onerror = function(event) {console.error('Error:', event);
};

image

不过SSE只能在请求地址中增加参数,没法定义携带的请求头,比如Authorization。

HTTP范围请求

范围请求似乎不是我们手动直接处理,而是浏览器和服务器自动完成的。比如大文件下载断点续传。这种不在乎超时问题,似乎不应该纳入此次讨论范畴。

但是我好奇的是,范围请求的流程。浏览器如何决定下载一个压缩包时发送范围请求还是普通请求?浏览器再最开始如何知道范围大小?这似乎有个探测阶段才行,那么浏览器和服务器是如何互动的?如果有探测,那么服务器怎么知道这是一个探测请求,而不是一个下载请求?

确实有一个探测阶段,使用head方法,而不是常规的get post,仅获取文件大小信息而不下载内容。

  • 浏览器发送的 HEAD 请求
HEAD /example.txt HTTP/1.1
Host: example.com
  • 服务器响应
HTTP/1.1 200 OK
Accept-Ranges: bytes
Content-Length: 100
Content-Type: text/plain

但探测阶段并不总是存在。当我们点击一个链接,浏览器并不知道这是一个大文件。所以浏览器通常会发一个get请求,直接下载文件,并从头部了解并记录是否支持范围请求Accept-Ranges和文件总大小Content-Length,以便再暂停下载之后,再次点击下载时决定能否换成发送范围请求。

  • 对于静态文件,通常web服务器内置实现了范围请求的响应。
  • 对于由控制器接口提供的文件下载,需要我们自己实现这个action的范围下载逻辑,即取头部范围字段,计算偏移量,设置头部,响应码,返回相应部分数据。
    所以控制器接口考虑断点续传时,就要加一个range分支了。第一个分支供完整下载,第二个分支供范围下载
[HttpGet]
public IActionResult GetFile(string filePath)
{var fileInfo = new System.IO.FileInfo(filePath);var fileBytes = System.IO.File.ReadAllBytes(filePath);//范围请求分支if (Request.Headers.ContainsKey("Range")){var rangeHeader = HttpContext.Request.Headers["Range"].ToString();var range = rangeHeader.Replace("bytes=", "").Split('-');long start = long.Parse(range[0]);long end = range.Length > 1 ? long.Parse(range[1]) : fileInfo.Length - 1;if (start >= fileInfo.Length || end >= fileInfo.Length || start > end){return StatusCode(416); // Requested Range Not Satisfiable}var filePart = fileBytes.Skip((int)start).Take((int)(end - start + 1)).ToArray();HttpContext.Response.Headers.Add("Content-Range", $"bytes {start}-{end}/{fileInfo.Length}");HttpContext.Response.Headers.Add("Content-Length", filePart.Length.ToString());return File(filePart, "text/plain", enableRangeProcessing: true);}//完整下载分支return File(fileBytes, "text/plain");
}

如果要更完善一点,为某些下载器提供探测接口,那就还要实现一个head方法。但这可能是很少用到的。

[HttpHead]
public IActionResult HeadFile(string filePath)
{var fileInfo = new System.IO.FileInfo(filePath);Response.Headers["Content-Length"] = fileInfo.Length.ToString();return NoContent(); // 204 No Content
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.ryyt.cn/news/55172.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈,一经查实,立即删除!

相关文章

vue2项目中使用webworker(一):发送网络请求

背景 有的时候我们需要向后端发送多个网络请求,如果全部在主线程中操作的话页面会变得非常卡顿,我们可以使用webwoker来发送网络请求,一旦服务响应结果,我们再从子线程给主线程发送消息 步骤 默认情况下vue2是不支持webwoker。安装worker-loadernpm i -D worker-loadervue.…

nginx出现403错误的解决方法

nginx出现403错误的解决方法 2024/08/31 17:06:52 [error] 26005#26005: *11 "/root//frontend/dist/index.html" is forbidden (13: Permission denied), client: 220.196.160.53, server: 81.70.112.191, request: "GET / HTTP/1.1", host: "81.70…

机组复习

1.数字计算机如何分类?分类的依据是什么?①按存储量大小分类:分为巨型、大型、中型、小型机和微型机。②从传输方式和操作方式分类:分为串行机和并行机。③按电路组成分类:分为电子管、晶体管、集成电路、大规模集成电路。④按用途分类:分为通用计算机、数据处理机和控制…

搭建博客

Hexo+Git 安装Hexo hexo文档 1.先行条件 安装以下应用程序Node.js Git检验是否安装成功 win+r输入cmd进入终端出现版本号即安装成功 2.镜像安装Hexo 进入npm镜像站 在终端输入 npm install -g cnpm --registry=https://registry.npmmirror.com cnpm install -g hexo-cli 效果如…

ffmpeg合并视频

安装ffmpeg命令工具: 1.下载ffmpeg工具 【官网】 2.如果需要下载 7-zip解压该文件【官网】 3.解压后重名解压文件夹为ffmpeg 4.复制ffmpeg文件夹到 "C:\Program Files" 5.添加系统环境变量 “C:\Program Files\ffmpeg\bin” 6.可以通过 ffmpeg 查看7. 对于需要合并的…

ABC369

A link判断\(A\),\(B\)之间可不可以放一个数,如果可以就是\(3\)个,不行就是\(2\)个(左右),但是如果\(A\),\(B\)相等就只有一个。点击查看代码 #include<bits/stdc++.h>using namespace std;signed main(){int a,b;cin >> a >> b;int x = b-a;if(x != …

对最长路(和最短路)的一些思考

为了使得整篇文章显得更加人性化,咱们首先说一下最短路。 声明:不是讲解知识点不是讲解知识点不是讲解知识点不是讲解知识点不是讲解知识点,整篇文章建立在默认已经会的基础之上,然后提出一些个人见解。 最短路 此时的 SPFA 显得不再重要了(,咱们进入正题,说一下 dijkst…

闲话 831

非常厉害治程宝典看不下去了😭 被分析卡脖子了 还是来看看合订本吧家人们 初 见 端 倪灵 光 一 闪渐 入 佳 境炉 火 纯 青登 峰 造 极羽 化 成 仙完 全 胜 利YJX AK IOI