2013-03-25 13 views
6

リクエストの1つでJSONオブジェクトの連続ストリームを返すJSON-RPCサービスがあります。IndyのHTTP連続パケットストリーム

I.e. :

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

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

私はカスタムTStream子孫を使ってデータを受け取り、解析しています。しかし、内部でTIdHttpはデータをバッファし、RecvBufferSizeバイトが受信されるまで私に渡されません。

これは、その結果:30分前大事メッセージは、30分前に配信されているはずなので、これはしないだろう

明らか
{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 

私はIndyが何を行うのかをしたいと思っています:利用可能なデータがある場合はRecvBufferSize以下に読み上げ、すぐに戻ります。

私は2005年からthis discussionを見つけましたが、Indyの開発者に問題を説明しようとしたが、彼は理解しませんでした。

とにかく、彼はカスタムIOHandlerの子孫を書くことでこれを回避しましたが、それは2005年に戻っています。今日はいくつかの解決策がありますか?

答えて

2

TCPストリームを使用するのはオプションでしたが、最終的に私はカスタムTIdIOHandlerStackのオリジナルの解決策を行っていました。

TIdHTTPを使用した場合、何がうまくいかず、それを修正する必要があることがわかりました。低レベルのTCPに切り替えると、新しい問題が発生する可能性があります。

Here's the code that I'm usingここで重要な点について説明します。

新しいTIdStreamIoHandlerは、TIdIOHandlerStackから継承しなければなりません。

2つの関数は、書き換えが必要:ReadBytesReadStream:両方がIdIOHandler.TIdIOHandlerに見出すことができるインディ機能が変更され

function TryReadBytes(var VBuffer: TIdBytes; AByteCount: Integer; 
    AAppend: Boolean = True): integer; virtual; 
procedure ReadStream(AStream: TStream; AByteCount: TIdStreamSize = -1; 
    AReadUntilDisconnect: Boolean = False); override; 

ReadByteswhile節では、ReadFromSource()要求に置き換えなければならないため、TryReadBytesはAByteCountバイトまで読み込んだ後に一度に戻ります。これに基づき

ReadStreamは周期的に読み、その後、ソケットから到着したデータのチャンクをストリームに書き込むために、すべてのAByteCountの組み合わせ(> 0、< 0)とReadUntilDisconnect(真、偽)を処理する必要があります。

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

+0

Indyがオープンソースであるため、修正されたソースが公表される可能性があります(また、他の人に役立つ場合もあります)。 – mjn

+0

@mjn:それを知らなかった、ありがとう。コードを追加しました。 – himself

2

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

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


更新:Reading data from an open HTTP stream

4
: 'ストリーミング' HTTP JSON Webサービスのための(.NET用)と同様のStackOverflowの質問で、最もupvoted解決策ではなく、HTTPクライアントの下位レベルのTCPクライアントを使用しました

私の場合はWebSocketのように聞こえます。あなたの接続は、それ以上の指向のHTTP質問/回答ではなく、コンテンツの流れです。

一部のコードではWebSocket server implementations for Delphiを参照してください。

AsmProfilerの著者のat least one based on Indyがあります。

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

long-poolingなどの古いプロトコルを使用することもできます。これは、WebSocketモードに切り替えると標準HTTPではなくなり、いくつかの「合理的な」パケット検査ツール(企業ネットワーク)は、それをセキュリティ攻撃(例えばDoS)と認識し、接続を停止する可能性があります。

+0

私はそれが正しいとすれば、どちらのソリューションもサービスの書き換えが必要ですか?私はそれにアクセスできないからです。 – himself

+0

@himselfあなたの要求が接続を開いて、Content-Lengthヘッダーを使用しないことになっている場合、これはもはやHTTPではないので、サービス側を変更する必要があります。 –

+0

Mhm、サービス側が何を言いますか? 「HTTP標準では、HTTPミドルウェアはデータを長時間バッファリングできると言っているので、私たちのサービスは問題ありません.HTTPクライアントコードを修正する必要があると思います」振り出しに戻って。 – himself

0

実際には、チャンク符号化転送モードで転送されたパケットの内容の前に長さデータがあります。 idhttpのIOhandlerは、この長さのデータを使用して、1パケットを1パケット読み込みストリームします。意味のある最小単位はパケットなので、パケットから文字を1つずつ読み込む必要はなく、IOHandlerの機能を変更する必要はありません。唯一の問題は、ストリームデータの無限のためにストリームデータを次のステップに回すのをidhttpが止めないということです。終了パケットはありません。そう溶液は、ストリームからの読み出し及びこの.likeオーバーフローを回避するために、ゼロにストリーム位置を設定するトリガするonworkイベントをidhttp使用している:

//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にカスタムライト()関数があってもよい使用しますこの種の要件に対してはより良い解決策です。

関連する問題