2013-03-25 3 views
6

요청 중 하나에 대해 JSON 객체의 연속 스트림을 반환하는 JSON-RPC 서비스가 있습니다.Indy가있는 HTTP 연속 패킷 스트림

e.e. :

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

물론 스트림이 무한하기 때문에 Content-Length가 없습니다.

데이터를 수신하고 구문 분석하기 위해 사용자 정의 TStream 자손을 사용하고 있습니다. 그러나 내부적으로 TIdHttp은 데이터를 버퍼링하고 RecvBufferSize 바이트를받을 때까지 데이터를 전달하지 않습니다. 분 전 중요 메시지 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 이하로 읽습니다.

나는 가난한 영혼이이 문제를 Indy 개발자에게 설명하려고했지만 2005 년에 그를 이해하지 못했던 2005 년에 this discussion을 발견했습니다. (읽을 거리가 슬픈 광경)

어쨌든, 그는 Iohandler 자손을 작성하여이 문제를 해결했지만, 2005 년에 다시 돌아왔다.

답변

2

TCP 스트림을 사용하는 것은 옵션 이었지만 결국에는 사용자 지정 TIdIOHandlerStack 자손 작성의 원래 솔루션을 사용했습니다.

동기 부여는 TIdHTTP를 사용하면 작동하지 않는 것이 무엇인지 알았고이를 수정하기 만하면되는 반면 낮은 수준의 TCP로 전환하면 새로운 문제가 발생할 수 있습니다.

Here's the code that I'm using 여기에서 요점을 논의 할 것입니다.

TIdStreamIoHandlerTIdIOHandlerStack에서 상속되어야합니다.

두 기능을 다시 작성해야합니다 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; 

. ReadBytes에서 while 절은 ReadFromSource() 요청으로 바꿔야하므로 TryReadBytes은 한 번에 최대 AByteCount 바이트까지 읽은 후 반환됩니다. 이것을 기초

ReadStream가 AByteCount의 모든 조합을 처리한다 (> 0 < 0) 및 (참, 거짓) ReadUntilDisconnect 주기적으로 판독하고 소켓에서 도착 된 데이터의 청크를 스트림에 기록 할.

요청 된 데이터의 일부만 소켓에서 사용할 수있는 경우 ReadStream은이 스트림 버전에서도 조기에 종료 할 필요가 없습니다. FInputBuffer에 캐싱하지 않고 즉시 해당 부분을 스트림에 쓰고 차단 한 다음 데이터의 다음 부분을 기다려야합니다.

+0

인디가 오픈 소스이기 때문에 수정 된 소스가 공개 될 수 있습니다. (다른 사람들에게 도움이된다면 공개해야합니다) – mjn

+0

@mjn : 그 사실을 모릅니다. 고마워요. 코드가 추가되었습니다. – himself

2

IOHandler 자손을 작성할 필요가 없습니다. TIdTCPClient 클래스로 이미 가능합니다. 소켓에서 읽을 방법이있는 TIdIOHandler 개체를 노출합니다. 이러한 ReadXXX 메서드는 요청 된 데이터가 읽혀 지거나 시간 초과가 발생할 때까지 차단합니다. 연결이 존재하는 한, ReadXXX는 루프에서 실행될 수 있으며 새 JSON 객체를 수신 할 때마다이를 응용 프로그램 논리로 전달합니다.

예제는 모든 JSON 개체가 한 줄만있는 것처럼 보입니다. 그러나 JSON 객체는 여러 줄로 묶을 수 있습니다.이 경우 클라이언트 코드는 어떻게 구분되는지 알아야합니다.


업데이트 : Reading data from an open HTTP stream

4
:는 '스트리밍'HTTP JSON 웹 서비스 (닷넷) 비슷한 유래 질문에, 가장 upvoted 솔루션 대신 HTTP 클라이언트의 낮은 수준의 TCP 클라이언트를 사용

나에게 들리는 소리는 WebSocket 작업입니다. 연결은 더 이상 일반적인 HTTP 질문/답변이 아니며 콘텐츠 스트림이기 때문에.

일부 코드는 WebSocket server implementations for Delphi을 참조하십시오.

AsmProfiler의 저자 인 at least one based on Indy이 있습니다.

AFAIK 웹 소켓에는 바이너리와 텍스트의 두 가지 종류가 있습니다. JSON 스트림은 websocket 관점에서 볼 때 일부 텍스트 콘텐츠라고 의심됩니다.

또 다른 옵션은 long-pooling 또는 좀 더 루터 친화적 인 이전 프로토콜을 사용하는 것입니다. 웹 소켓 모드로 전환하면 더 이상 표준 HTTP가 없으므로 일부 "현명한"패킷 검사 도구 (회사 네트워크)는이를 보안 공격 (예 : DoS)으로 식별 할 수 있으므로 연결을 중지 할 수 있습니다.

+0

맞다면 두 솔루션 모두 서비스를 다시 작성해야합니까? 나는 그것에 접근 할 수 없기 때문에. – himself

+0

@himself 귀하의 요청이 연결을 열어서 Content-Length 헤더를 사용하지 않는 것이라면, 이것은 HTTP가 아니므로 서비스 측을 변경해야한다고 생각합니다! –

+0

흠, 서비스 측면에서 무엇을 말할까요? "HTTP 표준에서 아무 것도 HTTP 미들웨어가 오랜 시간 동안 데이터를 버퍼링 할 수 있다고 말합니다. 따라서 우리의 서비스는 괜찮습니다. HTTP 클라이언트 코드를 수정해야 할 것입니다." 원점으로 돌아가다. – himself

0

실제로 청크 인코딩 전송 모드에서 전송 된 패킷의 내용 바로 전에 길이 데이터가 있습니다. 이 길이 데이터를 사용하여 idhttp의 IOhandler는 하나의 패킷을 하나의 패킷으로 읽어 들여 스트림을 보냅니다. 최소의 의미있는 단위는 패킷입니다. 따라서 패킷에서 하나씩 문자를 읽을 필요가 없으며 IOHandler의 기능을 변경할 필요가 없습니다. 유일한 문제는 idhttp가 스트림 데이터의 끝이 없기 때문에 스트림 데이터를 다음 단계로 돌리는 것을 멈추지 않는다는 것입니다. 종료 패킷이 없습니다. 따라서 용액 스트림으로부터 판독을 트리거 onwork 이벤트 idhttp 사용이 .like 오버 플로우를 방지하기 위해 제로 스트림 위치 설정된다

//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의 함수가 될 수있다 이런 종류의 요구 사항에 대한 더 나은 해결책.