jQuery增量读取AJAX流?
Posted
技术标签:
【中文标题】jQuery增量读取AJAX流?【英文标题】:jQuery read AJAX stream incrementally? 【发布时间】:2011-12-06 03:21:42 【问题描述】:我已阅读this question,但它并不能完全回答我的问题。
不幸的是,自从我上次查看 AJAX 以来,XHR 对象中的情况似乎发生了变化,因此在完成填充之前不再可以直接访问 responseText
。
我必须编写一个使用 AJAX(最好是 jQuery,但我愿意接受建议)的页面,以通过 HTTP 从我无法控制的服务器检索 CSV 数据。响应数据可能非常大;一兆字节的文本并不少见。
服务器是流友好的。是否还有任何方法可以直接从 javascript 访问正在返回的数据流?
我确实可以选择编写一些位于中间并使用某种“彗星”技术(长轮询、EventSource 等)的 php 代码,但如果可能的话,我宁愿避免这样做。
如果相关,假设用户拥有最新版本的 Firefox/Chrome/Opera 并且旧浏览器兼容性不是问题。
【问题讨论】:
我知道这个已经回答了,我以前做过类似的事情,看看,如果你必须撕掉它jsfiddle.net/JmZCE/1 【参考方案1】:这在输出文本或HTML时非常简单。下面是一个例子。
(但是,如果尝试输出 JSON,您会遇到问题,我将进一步解决。)
PHP 文件
header('Content-type: text/html; charset=utf-8');
function output($val)
echo $val;
flush();
ob_flush();
usleep(500000);
output('Begin... (counting to 10)');
for( $i = 0 ; $i < 10 ; $i++ )
output($i+1);
output('End...');
HTML 文件
<!DOCTYPE>
<html>
<head>
<title>Flushed ajax test</title>
<meta charset="UTF-8" />
<script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.8.3/jquery.min.js"></script>
</head>
<body>
<script type="text/javascript">
var last_response_len = false;
$.ajax('./flushed-ajax.php',
xhrFields:
onprogress: function(e)
var this_response, response = e.currentTarget.response;
if(last_response_len === false)
this_response = response;
last_response_len = response.length;
else
this_response = response.substring(last_response_len);
last_response_len = response.length;
console.log(this_response);
)
.done(function(data)
console.log('Complete response = ' + data);
)
.fail(function(data)
console.log('Error: ', data);
);
console.log('Request Sent');
</script>
</body>
</html>
如果我需要使用 JSON 执行此操作怎么办?
实际上不可能增量加载单个 JSON 对象(在它完全加载之前),因为在您拥有完整的对象之前,语法将始终无效。
但是,如果您的响应有 多个 JSON 对象,一个接一个,则可以一次加载一个,因为它们从管道中下来。
所以我调整了上面的代码...
将 PHP 文件第 4 行从 echo $val;
更改为 echo '"name":"'.$val.'";'
。这会输出一系列 JSON 对象。
将 HTML FILE 第 24 行从 console.log(this_response);
更改为
this_response = JSON.parse(this_response);
console.log(this_response.name);
请注意,此基本代码假定进入浏览器的每个“块”都是有效的 JSON 对象。情况并非总是如此,因为您无法预测数据包将如何到达 - 您可能需要根据分号拆分字符串(或想出另一个分隔符)。
不要使用application/json
不将您的标题更改为application/json
- 我这样做了,它让我在谷歌上搜索了 3 天。当响应类型为application/json
时,浏览器会一直等待,直到响应完成,就像完全完成一样。然后解析完整的响应以检查它是否是 JSON。然而,我们的完整响应是 ...;...;...;
,它不是有效的 JSON。 jqXHR.done
方法假定存在错误,因为无法将完整的响应解析为 JSON。
如 cmets 中所述,您可以使用以下命令在客户端禁用此检查:
$.ajax(..., dataType: "text")
希望有些人觉得这很有用。
【讨论】:
哇,谢谢先生,这正是我想要的!很好的例子,说明如何将这种技术与 JSON 一起使用。 非常感谢,这花了我 1 分钟才成功实施。很棒的东西。 使用 dataType:"text" 调用 $.ajax,这将抑制智能猜测(参见api.jquery.com/jquery.ajax dataType) 是的,您可以使用双簧管 (oboejs.com) 等流式 JSON 解析器逐步读取 JSON。您不需要将 JSON 响应更改为具有多个 JSON 对象,从设计角度来看,最好不要这样做 关于 PHP 的注释:通过连接字符串(例如echo '"name":"'.$val.'";'
)在 PHP 端手动创建 JSON 通常是不好的做法。一些更好的代码可能是echo json_encode(["name"=>$val]).";";
。【参考方案2】:
使用 XMLHttpRequest.js
https://github.com/ilinsky/xmlhttprequest
http://code.google.com/p/xmlhttprequest
提供 XMLHttpRequest 1.0 对象的不显眼且符合标准 (W3C) 的跨浏览器实现 修复了在其原生 XMLHttpRequest 对象实现中观察到的所有浏览器怪癖 启用 XMLHttpRequest 对象活动的透明日志记录在 PHP 中使用长轮询:
输出.php:
<?php
header('Content-type: application/octet-stream');
// Turn off output buffering
ini_set('output_buffering', 'off');
// Turn off PHP output compression
ini_set('zlib.output_compression', false);
// Implicitly flush the buffer(s)
ini_set('implicit_flush', true);
ob_implicit_flush(true);
// Clear, and turn off output buffering
while (ob_get_level() > 0)
// Get the curent level
$level = ob_get_level();
// End the buffering
ob_end_clean();
// If the current level has not changed, abort
if (ob_get_level() == $level) break;
// Disable apache output buffering/compression
if (function_exists('apache_setenv'))
apache_setenv('no-gzip', '1');
apache_setenv('dont-vary', '1');
// Count to 20, outputting each second
for ($i = 0;$i < 20; $i++)
echo $i.str_repeat(' ', 2048).PHP_EOL;
flush();
sleep(1);
运行.php:
<script src="http://code.jquery.com/jquery-1.6.4.js"></script>
<script src="https://raw.github.com/ilinsky/xmlhttprequest/master/XMLHttpRequest.js"></script>
<script>
$(function()
var xhr = new XMLHttpRequest();
xhr.open('GET', '/longpoll/', true);
xhr.send(null);
var timer;
timer = window.setInterval(function()
if (xhr.readyState == XMLHttpRequest.DONE)
window.clearTimeout(timer);
$('body').append('done <br />');
$('body').append('state: ' + xhr.readyState + '<br />');
console.log(xhr.responseText);
$('body').append('data: ' + xhr.responseText + '<br />');
, 1000);
);
</script>
这应该输出:
state: 3
data: 0
state: 3
data: 0 1
state: 3
data: 0 1 2
state: 3
data: 0 1 2 3
state: 3
data: 0 1 2 3 4
...
...
...
state: 3
data: 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
state: 3
data: 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
state: 3
data: 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
done
state: 4
data: 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
对于 IE,你需要查看 XDomainRequest
http://blogs.msdn.com/b/ieinternals/archive/2010/04/06/comet-streaming-in-internet-explorer-with-xmlhttprequest-and-xdomainrequest.aspx
http://msdn.microsoft.com/en-us/library/cc288060(VS.85).aspx
【讨论】:
这似乎不支持 readystate 3,甚至在 Chrome 中也不支持 :( @Josh,是的。但是长轮询有各种怪癖。您需要在读取状态发生变化之前发送 2Kb 数据,并将内容类型设置为application/octet-stream
。有关 PHP 示例,请参阅我更新的帖子。
我会看看我能用这个做什么。我似乎不可避免地需要在中间添加一些 PHP,因为我无法控制原始响应的内容类型。但我也真的很希望能够支持 IE6/7(不幸的是)...
@xorinzor pastebin.com/3Dbt2mhQ 不过,根据您的需要,您可能需要实现自定义协议。这样读取所有数据直到;
。
@Bakalash 因为某些浏览器在发送 2kb 输出之前不允许流式传输。【参考方案3】:
您将要为此直接使用 javascript。原因是您将要连续轮询而不是等待回调触发。你不需要 jQuery,这很简单。他们有一些nice source code for this on the Ajax Patterns website。
基本上,您只想跟踪您在响应中的最后位置,并定期轮询该位置之后的更多文本。您的情况不同的是,您可以订阅完整的事件并停止投票。
【讨论】:
你能给我举个例子吗?您提供的链接说“XMLHttpRequest 的 responseText 属性始终包含已从服务器中清除的内容,即使连接仍处于打开状态。” .. 而且,根据我一直在阅读的内容,在较新的浏览器中不再是这种情况。 不就是在IE里吗?我认为 readyState 3 包含在其他浏览器中。 我主要是通过这个 jquery 插件中的注释:plugins.jquery.com/project/ajax-http-stream'注意:我注意到这不再适用于 Firefox 3.0.11(适用于 3.0.8 linux)、IE8 或最新版本的 Chrome。显然趋势是在请求完成之前禁止访问 xmlhttprequest.responseText(愚蠢的 imo)。抱歉,我无法解决这个问题' 事实证明,这实际上确实适用于直接的 javascript,在尝试之后(至少对于行为正常的浏览器)。仍然希望找到一个 jquery 版本,以便它在所有浏览器上都能正常工作,但现在这毕竟是最好的答案。 死链接让我难过【参考方案4】:既然你说你的服务器是流友好的(异步的)并且正在寻找一个 jquery 解决方案,你检查了jQuery Stream Plugin 吗?
它非常易于使用,让您不必担心任何事情。它也有pretty gooddocumentation。
【讨论】:
我当然可以看看这个。在 API 页面的快速浏览中,我看不到将 HTTP POST 和基本身份验证信息发送到服务器的方法,但我确信它一定在某个地方。也可能“流友好”是术语的错误选择。我不是指异步或双向。我的意思是它会随着时间的推移以流的形式发回大量数据,就像巨大的 HTTP 响应一样。同时,我发现了一个非 jquery 解决方案,对于我最初的目的来说应该“足够好”。 对于 http post 和基本身份验证来说,你还是会直接使用 jquery。 我如何将“直接 jquery”与 jquery 流插件集成?文档在这一点上还不清楚。有例子吗? +1 它现在变成了门户,看起来非常棒,包括 WebSockets 和所有东西。 github.com/flowersinthesand/portal @marsbard 门户已达到其End of Life 并且不再维护!使用Vibe。【参考方案5】:我必须提供一个带有大量 JSON 有效负载的网格,该有效负载一直运行到最大允许大小限制。我使用的是MVC和jquery,所以我改编了上面AlexMorley-Finch的解决方案。
服务器代码来自"Streaming data using Web API"。还有https://github.com/DblV/StreamingWebApi。
public class StreamingController : ApiController
[HttpGet]
[ActionName("GetGridDataStream")]
public HttpResponseMessage GetGridDataStream(string id)
var response = Request.CreateResponse();
DynamicData newData = new DynamicData();
var res = newData.GetDataRows(id);
response.Content = new PushStreamContent((stream, content, context) =>
foreach (var record in res)
var serializer = new JsonSerializer();
using (var writer = new StreamWriter(stream))
serializer.Serialize(writer, record);
stream.Flush();
// Thread.Sleep(100);
stream.Close();
);
return response;
这创建了一个 json objectjson objectjson object 流,需要用逗号分隔并围绕 [ ] 才能成功解析为 json。
因此向客户端代码提供了缺少的字符:
var jsonData = ;
$.ajax("api/Streaming/GetGridDataStream/" + viewName,
xhrFields:
onprogress: function (e)
// console.log(this_response);
, dataType: "text" ) //<== this is important for JSON data
.done(function (data)
data = "[" + data.replace(/\\/gi, ",") + "]";
jsonData["DataList"] = JSON.parse(data);
//more code follows to create grid
)
.fail(function (data)
console.log('Error: ', data);
);
我希望这对使用 .Net MVC 和 jQuery 的人有所帮助。
【讨论】:
【参考方案6】:这是使用 JQuery 实现此目的的一种直接方法(根据 OP 的要求):
首先,通过从https://gist.github.com/chrishow/3023092 运行以下代码(附加在此响应的底部),扩展 ajax 对象以支持 onreadystatechange。然后只需使用 onreadystatechange 函数调用 ajax,该函数将检查 xhr.responseText 是否有新文本。
如果你想变得更花哨,你可以在每次阅读 responseText 数据时清除它,例如描述here)。
例如,请参阅https://jsfiddle.net/g1jmwcmw/1/,它将从https://code.jquery.com/jquery-1.5.js 下载响应并在控制台窗口中以块的形式输出,使用下面的代码(您可以将其复制到 html 页面中,然后在浏览器中打开) ):
<!-- jquery >= 1.5. maybe earlier too but not sure -->
<script src=https://code.jquery.com/jquery-1.5.min.js></script>
<script>
/* One-time setup (run once before other code)
* adds onreadystatechange to $.ajax options
* from https://gist.github.com/chrishow/3023092)
* success etc will still fire if provided
*/
$.ajaxPrefilter(function( options, originalOptions, jqXHR )
if ( options.onreadystatechange )
var xhrFactory = options.xhr;
options.xhr = function()
var xhr = xhrFactory.apply( this, arguments );
function handler()
options.onreadystatechange( xhr, jqXHR );
if ( xhr.addEventListener )
xhr.addEventListener( "readystatechange", handler, false );
else
setTimeout( function()
var internal = xhr.onreadystatechange;
if ( internal )
xhr.onreadystatechange = function()
handler();
internal.apply( this, arguments );
;
, 0 );
return xhr;
;
);
// ----- myReadyStateChange(): this will do my incremental processing -----
var last_start = 0; // using global var for over-simplified example
function myReadyStateChange(xhr /*, jqxhr */)
if(xhr.readyState >= 3 && xhr.responseText.length > last_start)
var chunk = xhr.responseText.slice(last_start);
alert('Got chunk: ' + chunk);
console.log('Got chunk: ', chunk);
last_start += chunk.length;
// ----- call my url and process response incrementally -----
last_start = 0;
$.ajax(
url: "https://code.jquery.com/jquery-1.5.js", // whatever your target url is goes here
onreadystatechange: myReadyStateChange
);
</script>
【讨论】:
在这里操作。这个问题是 6 年前提出的。这在 2011/2012 年会起作用吗?我不再从事这个项目,所以我无法测试你的答案。 是的,它适用于 jquery 1.5(2011 年 1 月,code.jquery.com/jquery-1.5.min.js)。例如,您可以剪切/粘贴上面的代码。 我只需要相信你。我在多个浏览器中运行了您的确切代码,整个响应都在一个“块”中,所以它并没有真正证明任何事情。没有时间进一步摆弄它。 你应该可以看到它。我将上面的内容逐字保存到一个 test.html 文件并在 Chrome 中打开它,控制台窗口显示收到的响应分两块。以上是关于jQuery增量读取AJAX流?的主要内容,如果未能解决你的问题,请参考以下文章