首页 > 其他分享 >大请求、请求超时问题

大请求、请求超时问题

时间:2024-08-31 23:14:06浏览次数:9  
标签:const 请求 问题 响应 Length new 浏览器 超时

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

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

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

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

http报文分块传输

HTTP/1.1 200 OK
Content-Type: application/json
Transfer-Encoding: chunked

4A
[{"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
}

标签:const,请求,问题,响应,Length,new,浏览器,超时
From: https://www.cnblogs.com/ggtc/p/18389918

相关文章

  • vue2项目中使用webworker(一):发送网络请求
    背景有的时候我们需要向后端发送多个网络请求,如果全部在主线程中操作的话页面会变得非常卡顿,我们可以使用webwoker来发送网络请求,一旦服务响应结果,我们再从子线程给主线程发送消息步骤默认情况下vue2是不支持webwoker。安装worker-loadernpmi-Dworker-loadervue.conf......
  • LOJ #6089. 小 Y 的背包计数问题 题解
    Description小Y有一个大小为\(n\)的背包,并且小Y有\(n\)种物品。对于第\(i\)种物品,共有\(i\)个可以使用,并且对于每一个\(i\)物品,体积均为\(i\)。求小Y把该背包装满的方案数为多少,答案对于\(23333333\)取模。定义两种不同的方案为:当且仅当至少存在一种物品的......
  • Redis常见问题总结
    Redis常见问题总结参考小林codingRedis常见问题总结Redis集群架构所产生的问题及如何处理Java全栈知识体系Redis集群搭建目录1.认识Redis2.Redis应用场景3.Redis持久化4.Redis设计认识RedisRedis是一种开源的内存数据结构存储工具,用作分布式内存中的键值数据库......
  • # yyds干货盘点 # 盘点一个Python正则表达式问题
    大家好,我是皮皮。一、前言前几天在Python最强王者交流群【大锤子】问了一个Python正则表达式处理的问题,这里拿出来给大家分享下。下图是代码:二、实现过程这个问题确看上去是正则表达式的问题,这里【杯酒】提出问题并给出建议:使用+号,就能匹配所有符合条件的文字,而不是第一段。不过后......
  • 关于at32f415 free rtos下使用flash储存数据flash db库在写入数据库操作时,写入次数大
    由于f415的扇区每2k是一块扇区,所以在此处.blk_size=n*1024, //Flash块/扇区大小(因为STM32F2各块大小不均匀,所以擦除粒度为最大块的大小:128K)这个代码中,需要m==2,同理,需要查看你的单片机每个扇区的大小是多少,如果一个扇区的大小是4k,则此处需要填写的是由于f415的扇区每2......
  • 多线程篇( 并发编程 - 多线程问题)(持续更新迭代)
    目录一、线程的上下文切换问题1.简介2.多线程一定比单线程快?3.如何减少上下文切换二、线程安全问题1.什么是线程安全?2.java语言中的线程安全2.1.不可变2.2.绝对线程安全2.3.相对线程安全2.4.线程兼容2.5.线程对立3.java实现线程安全的方法?3.1.互斥同......
  • 主元素问题(C语言)
    主元素问题(C语言)题目参考代码#include<stdio.h>intmain(){//主元素问题intn,s[400002],num=1,max=0,maxNum=0;scanf("%d",&n);for(inti=0;i<n;i++)scanf("%d",&s[i]);for(inti=0;......
  • nginx服务器如何配置跨站请求
    在做app、小程序开发,会存在跨站资源调用的情况,这时就需要对服务器进行配置,让它允许跨站请求,现在以nginx服务器为例进行讲解。nginx服务器如何配置跨站请求,配置代码如下:server{#listen80;listen443;server_name你的域名;i......
  • pbshr80.dll丢失问题详解:识别症状、分析原因与采取行动
    在使用计算机的过程中,我们经常会遇到各种各样的问题,其中之一就是DLL文件丢失或损坏。本文将针对“pbshr80.dll”这个特定的DLL文件丢失的情况进行详细的分析,并提供几种有效的解决方案。pbshr80.dll文件简介pbshr80.dll是一个动态链接库文件(DynamicLinkLibrary),通常与某些游......
  • HidLampArray.dll文件丢失导致程序无法运行问题
    其实很多用户玩单机游戏或者安装软件的时候就出现过这种问题,如果是新手第一时间会认为是软件或游戏出错了,其实并不是这样,其主要原因就是你电脑系统的该dll文件丢失了或没有安装一些系统软件平台所需要的动态链接库,这时你可以下载这个HidLampArray.dll文件(挑选合适的版本文件)......