我有一个JSON-RPC服务,其中一个请求返回连续的JSON对象流。
即。 :
{id:'1'}
{id:'2'}
//30 minutes of no data
{id:'3'}
//...
当然,没有内容长度,因为流是无穷无尽的。
我正在使用自定义TStream后代来接收和解析数据。但是内部TIdHttp
缓冲数据,并且在收到RecvBufferSize
字节之前不会将其传递给我。
这导致:
{id:'1'} //received
{id:'2'} //buffered by Indy but not received
//30 minutes of no data
{id:'3'} //this is where Indy commits {id:'2'} to me
显然这不行,因为30分钟前重要的信息应该在30分钟前送达。
我希望Indy做套接字的工作:如果有可用的数据,请立即读取RecvBufferSize并立即返回。
我从2005年发现了this discussion,其中一些可怜的灵魂试图向Indy开发者解释这个问题,但他们并不理解他。 (阅读它;这是一个悲伤的景象)
无论如何,他通过编写自定义IOHandler后代来解决这个问题,但那是在2005年,也许今天有一些现成的解决方案?
答案 0 :(得分:4)
听起来像是一个 WebSocket 任务,因为你的连接不再是面向HTTP的问题/答案,而是一个内容流。
有些代码,请参阅WebSocket server implementations for Delphi。
来自AsmProfiler的作者有at least one based on Indy。
AFAIK在websockets中有两种流:二进制和文本。我怀疑你的JSON流是一些文本内容,从websocket的角度来看。
另一种选择是使用long-pooling或一些更加根本友好的旧协议 - 当连接切换到websockets模式时,它不再是标准的HTTP,所以一些“明智的”数据包检查工具(在公司网络上)可能将其识别为安全攻击(例如DoS),因此可能会停止连接。
答案 1 :(得分:2)
您不需要编写IOHandler后代,已经可以使用TIdTCPClient
类。它公开了一个TIdIOHandler
对象,该对象具有从套接字读取的方法。这些ReadXXX方法将一直阻塞,直到读取请求的数据或发生超时。只要连接存在,ReadXXX就可以在循环中执行,只要它收到一个新的JSON对象,就把它传递给应用程序逻辑。
您的示例看起来所有JSON对象只有一行。然而,JSON对象可以是多行的,在这种情况下,客户端代码需要知道它们是如何分开的。
更新:对于'流'HTTP JSON Web服务的类似Stackoverflow问题(针对.Net),最受欢迎的解决方案使用较低级别的TCP客户端而不是HTTP客户端:Reading data from an open HTTP stream
答案 2 :(得分:2)
虽然使用TCP流是一种选择,但最后我选择了编写自定义TIdIOHandlerStack
后代的原始解决方案。
我的动机是TIdHTTP,我知道哪些不起作用,只需要解决这个问题,而切换到较低级别的TCP意味着可能会出现新的问题。
Here's the code that I'm using ,我将在此讨论关键点。
新TIdStreamIoHandler
必须从TIdIOHandlerStack
继承。
需要重写两个函数:ReadBytes
和ReadStream
:
function TryReadBytes(var VBuffer: TIdBytes; AByteCount: Integer;
AAppend: Boolean = True): integer; virtual;
procedure ReadStream(AStream: TStream; AByteCount: TIdStreamSize = -1;
AReadUntilDisconnect: Boolean = False); override;
两者都是经过修改的Indy函数,可以在IdIOHandler.TIdIOHandler
中找到。在ReadBytes
中,while
子句必须替换为单ReadFromSource()
个请求,以便在一次读取AByteCount字节后TryReadBytes
返回。
基于此,ReadStream
必须处理AByteCount(> 0,< 0)和ReadUntilDisconnect(true,false)的所有组合,以循环读取然后写入从套接字到达的流的数据块
请注意,如果套接字中只有部分请求的数据可用,ReadStream
即使在此流版本中也不需要提前终止。它只需要立即将该部分写入流而不是在FInputBuffer
中缓存它,然后阻塞并等待下一部分数据。
答案 3 :(得分:0)
//add a event handler to idhttp
IdHTTP.OnWork := IdHTTPWork;
procedure TRatesStreamWorker.IdHTTPWork(ASender: TObject; AWorkMode: TWorkMode; AWorkCount: Int64);
begin
.....
ResponseStringStream.Position :=0;
s:=ResponseStringStream.ReadString(ResponseStringStream.Size) ;//this is the packet conten
ResponseStringStream.Clear;
...
end;
procedure TForm1.ButtonGetStreamPricesClick(Sender: TObject);
var
begin
.....
source := RatesWorker.RatesURL+'EUR_USD';
RatesWorker.IdHTTP.Get(source,RatesWorker.ResponseStringStream);
end;
然而,使用Tstream的自定义write()函数可能是满足此类要求的更好解决方案。