SHOW:
|
|
- or go back to the newest paste.
| 1 | type | |
| 2 | {
| |
| 3 | Default TIdIOHandler implementation hangs in ReadBytes until it reads at least | |
| 4 | RecvBufferSize, see: | |
| 5 | http://stackoverflow.com/questions/15615347/http-continuous-packeted-stream-with-indy | |
| 6 | This is unacceptable with event streams as events have to be handled timely | |
| 7 | as they received. | |
| 8 | } | |
| 9 | TIdStreamIoHandler = class(TIdIOHandlerStack) | |
| 10 | public | |
| 11 | function TryReadBytes(var VBuffer: TIdBytes; AByteCount: Integer; | |
| 12 | AAppend: Boolean = True): integer; virtual; | |
| 13 | procedure ReadStream(AStream: TStream; AByteCount: TIdStreamSize = -1; | |
| 14 | AReadUntilDisconnect: Boolean = False); override; | |
| 15 | end; | |
| 16 | ||
| 17 | ||
| 18 | //Reads up to AByteCount bytes in one go. Returns the number of bytes read, >0. | |
| 19 | //Copied from ReadBytes with some modifications. | |
| 20 | function TIdStreamIOHandler.TryReadBytes(var VBuffer: TIdBytes; AByteCount: Integer; | |
| 21 | AAppend: Boolean = True): integer; | |
| 22 | begin | |
| 23 | Assert(FInputBuffer<>nil); | |
| 24 | if AByteCount > 0 then begin | |
| 25 | // Read from stack if we have not enough data | |
| 26 | if FInputBuffer.Size < AByteCount then begin | |
| 27 | ReadFromSource(False); //whatever we can | |
| 28 | CheckForDisconnect(True, True); | |
| 29 | end; | |
| 30 | Result := IndyMin(AByteCount, FInputBuffer.Size); | |
| 31 | FInputBuffer.ExtractToBytes(VBuffer, Result, AAppend); | |
| 32 | end else if AByteCount < 0 then begin | |
| 33 | ReadFromSource(False, ReadTimeout, False); | |
| 34 | CheckForDisconnect(True, True); | |
| 35 | Result := FInputBuffer.Size; | |
| 36 | FInputBuffer.ExtractToBytes(VBuffer, Result, AAppend); | |
| 37 | end else | |
| 38 | Result := 0; | |
| 39 | end; | |
| 40 | ||
| 41 | { Copied from TIdIOHandler.ReadStream with adjustments and simplifications.
| |
| 42 | ReadUntilDisconnect: Read all data until the connection closes. | |
| 43 | Data is processed in RecvBufferSize chunks, or in how much is available. | |
| 44 | AByteCount>0 + !ReadUntilDisconnect: Read up to AByteCount in one go. | |
| 45 | AByteCount<0 + !ReadUntilDisconnect: Read all available data in one go. | |
| 46 | } | |
| 47 | procedure TIdStreamIOHandler.ReadStream(AStream: TStream; AByteCount: TIdStreamSize; | |
| 48 | AReadUntilDisconnect: Boolean); | |
| 49 | var | |
| 50 | i: Integer; | |
| 51 | LBuf: TIdBytes; | |
| 52 | LByteCount: TIdStreamSize; | |
| 53 | begin | |
| 54 | Assert(AStream<>nil); | |
| 55 | LByteCount := AByteCount; | |
| 56 | ||
| 57 | if AReadUntilDisconnect and (AByteCount>0) then | |
| 58 | AByteCount := -1; | |
| 59 | ||
| 60 | if AReadUntilDisconnect then begin | |
| 61 | BeginWork(wmRead); | |
| 62 | end else begin | |
| 63 | BeginWork(wmRead, LByteCount); | |
| 64 | end; | |
| 65 | ||
| 66 | try | |
| 67 | // If data already exists in the buffer, write it out first. | |
| 68 | // should this loop for all data in buffer up to workcount? not just one block? | |
| 69 | if FInputBuffer.Size > 0 then begin | |
| 70 | if AByteCount<0 then begin | |
| 71 | i := FInputBuffer.Size; | |
| 72 | end else begin | |
| 73 | i := IndyMin(FInputBuffer.Size, LByteCount); | |
| 74 | Dec(LByteCount, i); | |
| 75 | end; | |
| 76 | FInputBuffer.ExtractToStream(AStream, i); | |
| 77 | end; | |
| 78 | ||
| 79 | // RLebeau - don't call Connected() here! ReadBytes() already | |
| 80 | // does that internally. Calling Connected() here can cause an | |
| 81 | // EIdConnClosedGracefully exception that breaks the loop | |
| 82 | // prematurely and thus leave unread bytes in the InputBuffer. | |
| 83 | // Let the loop catch the exception before exiting... | |
| 84 | ||
| 85 | SetLength(LBuf, RecvBufferSize); // preallocate the buffer | |
| 86 | repeat | |
| 87 | ||
| 88 | if AByteCount<0 then begin | |
| 89 | i := -1; | |
| 90 | end else begin | |
| 91 | i := IndyMin(LByteCount, RecvBufferSize); | |
| 92 | if i < 1 then break; | |
| 93 | end; | |
| 94 | ||
| 95 | try | |
| 96 | try | |
| 97 | i := TryReadBytes(LBuf, i, False); | |
| 98 | except | |
| 99 | on E: Exception do begin | |
| 100 | // RLebeau - ReadFromSource() inside of ReadBytes() | |
| 101 | // could have filled the InputBuffer with more bytes | |
| 102 | // than actually requested, so don't extract too | |
| 103 | // many bytes here... | |
| 104 | if i>0 then //else it's already -1 or 0 | |
| 105 | i := IndyMin(i, FInputBuffer.Size); | |
| 106 | FInputBuffer.ExtractToBytes(LBuf, i, False); | |
| 107 | if (E is EIdConnClosedGracefully) and AReadUntilDisconnect then | |
| 108 | break | |
| 109 | else | |
| 110 | raise; | |
| 111 | end; | |
| 112 | end; | |
| 113 | TIdAntiFreezeBase.DoProcess; | |
| 114 | finally | |
| 115 | if i > 0 then begin //how much was read | |
| 116 | TIdStreamHelper.Write(AStream, LBuf, i); | |
| 117 | if not AReadUntilDisconnect then | |
| 118 | Dec(LByteCount, i); | |
| 119 | end; | |
| 120 | end; | |
| 121 | ||
| 122 | until false; | |
| 123 | ||
| 124 | finally | |
| 125 | EndWork(wmRead); | |
| 126 | LBuf := nil; | |
| 127 | end; | |
| 128 | end; |