6

リクエストの1つに対してJSONオブジェクトの連続ストリームを返すJSON-RPCサービスがあります。

すなわち:

{id:'1'}
{id:'2'}
//30 minutes of no data
{id:'3'}
//...

もちろん、ストリームは無限であるため、Content-Lengthはありません。

カスタム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年からこの議論を見つけました。そこでは、貧しい人々がインディの開発者に問題を説明しようとしましたが、彼らは彼を理解していませんでした。(それを読んでください;それは悲しい光景です)

とにかく、彼はカスタムIOHandlerの子孫を作成することでこれを回避しましたが、それは2005年にさかのぼります。おそらく、今日、いくつかの準備が整ったソリューションがありますか?

4

4 に答える 4

4

あなたの接続はもはや単純なHTTPの質問/回答指向ではなく、コンテンツのストリームであるため、私にはWebSocketタスクのように聞こえます。

一部のコードについては、DelphiのWebSocketサーバーの実装を参照してください。

AsmProfilerの作者によるIndyに基づくものが少なくとも1つあります。

AFAIKには、WebSocketにはバイナリとテキストの2種類のストリームがあります。WebSocketの観点からすると、JSONストリームはテキストコンテンツであると思われます。

もう1つのオプションは、ロングプーリングまたはよりルートに適した古いプロトコルを使用することです。接続がWebSocketモードに切り替わると、標準のHTTPではなくなるため、いくつかの「賢明な」パケット検査ツール(企業ネットワーク上) )セキュリティ攻撃(DoSなど)として識別される可能性があるため、接続を停止する可能性があります。

于 2013-03-25T14:54:56.420 に答える
2

IOHandlerの子孫を作成する必要はありません。これは、TIdTCPClientクラスですでに可能です。TIdIOHandlerソケットから読み取るメソッドを持つオブジェクトを公開します。これらのReadXXXメソッドは、要求されたデータが読み取られるか、タイムアウトが発生するまでブロックされます。接続が存在する限り、ReadXXXはループで実行でき、新しいJSONオブジェクトを受信するたびに、それをアプリケーションロジックに渡します。

あなたの例は、すべてのJSONオブジェクトに1行しかないように見えます。ただし、JSONオブジェクトは複数行にすることができます。この場合、クライアントコードはそれらがどのように分離されているかを知る必要があります。


更新:「ストリーミング」HTTP JSON Webサービスに関する同様のStackoverflowの質問(.Netの場合)で、最も賛成されたソリューションは、HTTPクライアントの代わりに低レベルのTCPクライアントを使用しました:開いているHTTPストリームからデータを読み取る

于 2013-03-25T14:25:16.620 に答える
2

TCPストリームを使用することはオプションでしたが、最終的には、カスタムTIdIOHandlerStackの子孫を作成するという独自のソリューションを使用しました。

動機は、TIdHTTPを使用すると、何が機能しないかを知っており、それを修正するだけでよいのに対し、低レベルのTCPに切り替えると、新しい問題が発生する可能性があるということでした。

これが私が使用しているコードであり、ここで重要なポイントについて説明します。

新規TIdStreamIoHandlerはから継承する必要がありTIdIOHandlerStackます。

2つの関数を書き直す必要があります:ReadBytesReadStream

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れます。

これに基づいて、ReadStreamAByteCount(> 0、<0)とReadUntilDisconnect(true、false)のすべての組み合わせを処理して、ソケットから到着するデータのチャンクを循環的に読み取り、書き込みする必要があります。

ReadStream要求されたデータの一部のみがソケットで使用可能である場合は、このストリームバージョンでも途中で終了する必要はないことに注意してください。その部分をキャッシュするのではなく、即座にストリームに書き込んでから、FInputBufferデータの次の部分をブロックして待機する必要があります。

于 2013-04-24T07:22:35.860 に答える
0

実際には、チャンクエンコーディング転送モードで転送されたパケットの内容の直前に長さデータがあります。この長さのデータを使用して、idhttpのIOhandlerは1パケットずつ1パケットずつ読み取ってストリーミングします。意味のある最小単位はパケットであるため、パケットから文字を1つずつ読み取る必要はなく、IOHandlerの機能を変更する必要もありません。唯一の問題は、ストリームデータが無限にあるため、idhttpがストリームデータの次のステップへのターンを停止しないことです。終了パケットがありません。したがって、解決策は、idhttp onworkイベントを使用してストリームからの読み取りをトリガーし、オーバーフローを回避するためにストリーム位置をゼロに設定することです。

    //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()関数を使用することは、この種の要件に対するより良い解決策かもしれません。

于 2016-03-01T03:15:26.523 に答える