Delphi Indy TIdHttp 和 multipart/x-mixed-replace 带有文本和 jpeg 图像
Delphi Indy TIdHttp and multipart/x-mixed-replace with Text and jpeg image
我正在使用大华面部终端,它有一个 API 类(CGI 风格)和一个 SDK。我问了一些关于 dll 转换的问题,但现在我也在尝试使用 de API。
监视 Facial 处理的事件的 API 是
是一个 multipart/x-mixed-replace 响应如何 return 第一个绑定作为 text/plain 与事件数据和一个带有 image/jpeg 与事件快照的绑定。
使用关于 Indy 的在线信息和 Lebeau 发表的一些有用的帖子,我使用 idHttp.IoHanlder.ReadLn(IndyTextEncoding_UTF8)
读取文本数据
我尝试使用 idHttp.IOHandler.ReadByte、ReadBytes、ReadStream 读取下一个 bondary(图像),但没有成功。
这是使用 idHttp.IoHandler.ReadLn
的响应
--myboundary
Content-Type: text/plain
Content-Length: 1035
Events[0].Alive=100
Events[0].CardName=Joao Test
Events[0].CardNo=0B8748EB
Events[0].CardType=0
Events[0].CreateTime=1616297700
Events[0].Door=0
Events[0].ErrorCode=0
Events[0].Method=15
Events[0].ReaderID=1
Events[0].Similarity=99
Events[0].SnapPath=/var/tmp/partsnap96.jpg
Events[0].Status=1
Events[0].Type=Entry
Events[0].UTC=1616297700
Events[0].UserID=1
Events[0].UserType=0
--myboundary
Content-Type: image/jpeg
Content-Length: 54235
����
(1#%(:3=<9387@H\N@DWE78PmQW_bghg>Mqypdx\egc//cB8Bcccccccccccccccccccccccccccccccccccccccccccccccccc//cB8Bcccccccccccccccccccccccccccccccccccccccccccccccccc��
%&'()*456789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz�������������������������������������������������������������������������
�%�&'()*56789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz��������������������������������������������������������������������������
�XQz �?m�c$[J��5"�Hz�~5���m+��b{�����{��n��*p�p*�8�bx��
`3mjLQ��9CB���T���D���'ޥAY\ų���>�*��c�U���h�R�%��je�W��*]�m�p��m?��c6�?��b��R��1N�.(��.)إ�
u\,8�
)�ny�ڌү
@
#��
L�rɳ���B�Ns�� S�K��
�i�F��Z�af�
������ޭ�T�
/l�i]Q��l������
C�RS�
ʛO^�ҫ���
h�
P)@�
pN��
\S���0�1K�
f2N�⫑S�-�LF-�Y�8?0��.�?[�nE[��t[��s�w�h��_���y�[�R�*��=je ������4�M��,?�x��Ƣ�Sל�t8�F�
ZJZ
(��R�E
)h�bR�K@����
p�S����)تb���x1�O��(��i�1����5��r=i��i�B�A��J�{���ںT5s��_��
Q@-�
Z1J
x�i�i�S�NU��
�Kf=9=Pƺ�s�ٿ��N����uS�Tj��#��d��-N-����gw�W5�0*z0��=&m��=T��S�����
x�`JpJxZpZ"��i
p���@
<S7
P���R�a�wP"���� %!�Q�
��`�M�6��wⵍfjJb�I�������{�h/���ڞ�r�Q��Ta�ϫ�7E��o�����5%���X��C�
�v�G�rO���
��徸�(�O�T�4�4��ц��j8�
��3sM�X�'�G��ڪξ\�/L|��i����I?+|�Z�
�?��K[M��L7�^
K�@d�Hb��q߯�V�'|a��C�E�Z�+�
f�&����`'�ϑ��E�r>�Ƶ'Q�7~��<r}��XT�+������pHQۭ
我正在创建一个 class 来实现 API 和 SDK,在这种情况下 (API) 来管理我写了一个线程 class 的事件使用事件 API 并在事件发生时同步数据,idHttp 由线程 class 拥有并且它在 TDispositivo class 中,所以我 运行 一个线程 class 对于主要 class.
中包含的每个 TDispositivo
这是线程中的执行代码。 (忽略图像数据,因为我现在不知道如何正确处理它。
procedure TMonitor.Execute;
var
Ret, URL, Chave, Valor : String;
I, isInteger : Integer;
Concluido : boolean;
Tentativas: Integer;
begin
Tentativas := 3;
fAtivo := True;
FContador := 0; //zerando contador
//executa enquanto fAtivo for True e terminated for False
while fAtivo and not self.Terminated do
begin
if fOwner.alive then
begin
//preparando URL
With fOwner.Config do
begin
Url := 'http://'+ IP + ifthen(PortaWeb>=0,':'+PortaWeb.ToString,'')+'/cgi-bin/snapManager.cgi?action=attachFileProc&Flags[0]=Event&Events=[AccessControl]';
end;
//Incrementando contado
Inc(fContador);
//Efetuando o GET dentro de um try except para evitar interrupcao na thread
Try
ret := fHttp.Get(Url);
except
End;
fOwner.doGet(fhttp.ResponseCode, fhttp.ResponseText);
//tratando erros http
if fHttp.ResponseCode <> 200 then
begin
case fHttp.ResponseCode of
400: Erro(740,'Chamada ao terminal mal formada');
401: Erro(741,'Chamada ao terminal não autorizada');
403: Erro(743,'Recurso do servidor proibido');
404: Erro(744,'Recurso do terminal não encontrado');
500: Erro(750,'Erro interno no processamento de chamadas do terminal');
501: Erro(751,'Recurso do terminal não implementado');
end;
if (fContador >= Tentativas) then
begin
Erro(700,'Falha ao conectar o dispositivo. Número de tentativas excedido');
fContador := 0;
//verificando se vai continuar tentando se reconectar
if fOwner.Config.Reconectar then
begin
Log('Aguardando para tentar reconectar o monitor ...');
Sleep(fOwner.Config.TempoEsperaReconectar);
end
else
begin
Log('Finalizando monitoramento por falha de comunicação');
Self.Stop;
end;
end;
end
else
begin
//zerando contador após uma conexão bem sucedida;
FContador := 0;
//para evitar final do chunk ou close gracefully
try
if IsHeaderMediaType(fHttp.Response.ContentType, 'multipart') then
begin
i := 0;
Concluido := False;
repeat
if (i >= 35) Or Concluido then
begin
//tirando snap antes de processar o evento para ver se diminui o delay
if FOwner.Config.SnapshotEvento then
fOwner.getSnapshot;
Synchronize(Evento);
Concluido := False;
i := 0;
end;
fLinha := fHttp.IOHandler.ReadLn(IndyTextEncoding_UTF8);
if Pos('Content-Length', fLinha) > 0 then
begin
i := 0;
Concluido := False;
fEvento.Limpar;
end
Else if Pos('Events[', fLinha) > 0 then
begin
if fOwner.Config.Depurar then
Log(fLinha);
Inc(I);
Chave := Trim(retPar(1,'=',retPar(2,'.',fLinha)));
Valor := Trim(retPar(2,'=',fLinha));
if Valor = '' then
Valor := '0';
With fEvento do
begin
if Chave = 'Alive' then
Alive := ifthen(Valor = '',0,Valor.ToInteger)
else if Chave = 'CardName' then
CardName := ifthen(Trim(Valor) = '0','Não identificado',trim(Valor))
else if Chave = 'CardNo' then
begin
if TryStrToInt(Valor, isInteger) then
CardNo := ifthen(Valor = '', '-1', Valor)
else
CardNo := WiegandHexToInt(Valor).ToString;
end
else if Chave = 'CardType' then
CardType := ifthen(Valor = '', -1, Valor.ToInteger)
else if Chave = 'CreateTime' then
CreateTime := UnixToDateTime(Valor.ToInt64, False)
else if Chave = 'Door' then
Door := ifThen(Valor = '',-1, Valor.ToInteger)
else if Chave = 'ErrorCode' then
ErrorCode := ifThen(Valor = '',-1, Valor.ToInteger)
else if Chave = 'EventBaseInfo.Action' then
EventBaseAction := Valor
else if Chave = 'EventBaseInfo.Code' then
EventBaseCode := Valor
else if Chave = 'EventBaseInfo.Index' then
EventBaseIndex := ifThen(Valor = '',-1, Valor.ToInteger)
else if Chave = 'Method' then
Metodo := ifThen(Valor = '',-1, Valor.ToInteger)
else if Chave = 'ReaderID' then
LeitorID := ifThen(Valor = '',-1, Valor.ToInteger)
else if Chave = 'Similarity' then
Similaridade := ifthen(Valor = '', 0, Valor.ToInteger)
else if Chave = 'SnapPath' then
snappath := Valor
else if Chave = 'Status' then
Status := ifthen(Valor = '', -1, Valor.ToInteger)
else if Chave = '.Type' then
TipoAcesso := Valor
else if Chave = 'UTC' then
DataHora := UnixToDateTime(Valor.ToInt64, False)
else if Chave = 'UserID' then
UserID := ifthen(Valor = '', -1, Valor.ToInteger)
else if Chave = 'UserType' then
begin
i := 34;
Concluido := True;
UserType := ifthen(Valor = '', -1, Valor.ToInteger);
end;
end;
end;
until fhttp.IOHandler.ClosedGracefully;
end;
except
end;
end;
inherited;
end
else
begin
//dispositivo não respondeu ao ping
//verificando se vai continuar tentando se reconectar
if fOwner.Config.Reconectar then
begin
Log('Aguardando para tentar reconectar o monitor ...');
Sleep(fOwner.Config.TempoEsperaReconectar);
end
else
begin
Log('Finalizando monitoramento por falha de comunicação');
Self.Stop;
end;
inherited;
end;
end;
//rotina de finalização da thread
if FHttp.Connected then
begin
try
Fhttp.Disconnect;
except
end;
end;
end;
第一个问题,我可以做吗? Indy 能做到吗?有什么建议吗?
第二个问题,当我在数据结束后尝试 readLn 时,当 GET 为 运行ning 时,我观察到循环以检索数据。在 Firefox 中,GET 在图像之后仍然 运行ning 并且仍在工作,在我的例子中,GET 完成并且 threand.execute 再次调用 GET。
是否可以保持 GET 运行ning 避免过早中断并在出现时处理新数据?
编辑:当这部分代码 运行 时,NewDecoder := TIdMessageDecoderMIME(Decoder).ReadBody(BodyStream, MsgEnd);
行优雅地结束,深入研究 Indy 代码我发现 TIOHandler.ReadLn
如何在 LTerm = -1
时执行 CheckForDisconnect(True,True)
并且这会在不检索数据的最后字节的情况下中断执行。
考虑到这一点,我怀疑与 Responde.KeepAlive
总是 False
以及 Connection
内容总是 close
有关系,这解释了为什么 Decoder.Readybody 引发 Close gracefully
并中断我的执行。
但是,如果我 运行 调用 api,例如。 Firefox,在 FF 获取图像后连接仍在 运行ning 并且仅在达到超时时中断。
是否有可能 headers 上的内容由终端发送?或者 idHttp 如何解析 headers 数据的方式?
//part of code after http.get
mcptAttachment: begin
BodyStream := TMemoryStream.Create;
try
BodyStream.Position := 0;
NewDecoder := TIdMessageDecoderMIME(Decoder).ReadBody(BodyStream, MsgEnd); //<-- here comes a close gracefully
jpg := TJPEGImage.Create;
try
BodyStream.Position := 0;
jpg.LoadFromStream(BodyStream);
jpg.SaveToFile('C:\Producao\API_SDK\Facial\teste.jpg');
finally
jpg.Free;
Decoder.Free;
Decoder := NewDecoder;
end;
finally
BodyStream.Free;
end;
end;
//part of the code of tIOHandler.ReadLn
// ReadFromSource blocks - do not call unless we need to
else if LTermPos = -1 then begin
// ReadLn needs to call this as data may exist in the buffer, but no EOL yet disconnected
CheckForDisconnect(True,True); //The execution stops here whiout return the last bytes and premature finishing the code above with a close gracefully
// Can only return -1 if timeout
FReadLnTimedOut := ReadFromSource(True, ATimeout, False) = -1;
if (not FReadLnTimedOut) and (ATimeout >= 0) then begin
if GetElapsedTicks(LReadLnStartTime) >= UInt32(ATimeout) then begin
FReadLnTimedOut := True;
end;
end;
if FReadLnTimedOut then begin
Result := '';
Exit;
end;
编辑:使用New TIdHTTP hoNoReadMultipartMIME flag示例,我编写了一个测试应用程序来尝试从边界获取图像
var
Boundary, Line: string;
TCPStream: TIdTCPStream;
NewDecoder, Decoder: TIdMessageDecoder;
MsgEnd: Boolean;
BodyStream: TStream;
Jpg: TJpegImage;
begin
{
The code below comes from Indy Project Blog by Remy Lebeau
This connection requires Digest Auth, so i set BasicAuth to False, put the flag hoInProcessAuth
And implement the OnAuthorization and OnSelectAuthorization, the select is needed to do something to the Authorization works,
do not know why.
}
HTTP.HTTPOptions := [hoInProcessAuth, hoForceEncodeParams, hoNoReadMultipartMIME];
HTTP.Get('http://192.168.1.205/cgi-bin/snapManager.cgi?action=attachFileProc&Flags[0]=Event&Events=[AccessControl]');
//Here the response always return HTTP.Response.KeepAlive FALSE, so i exclude it from test
if IsHeaderMediaType(HTTP.Response.ContentType, 'multipart') then //and HTTP.Response.KeepAlive
begin
Boundary := ExtractHeaderSubItem(HTTP.Response.ContentType, 'boundary', QuoteHTTP);
repeat
Line := HTTP.IOHandler.ReadLn;
until (Line = ('--' + Boundary)) or (Line = ('--' + Boundary + '--'));
TCPStream := TIdTCPStream.Create(HTTP);
try
Decoder := TIdMessageDecoderMIME.Create(nil);
try
TIdMessageDecoderMIME(Decoder).MIMEBoundary := Boundary;
MsgEnd := False;
repeat
TIdMessageDecoderMIME(Decoder).SourceStream := TCPStream;
TIdMessageDecoderMIME(Decoder).FreeSourceStream := False;
Decoder.ReadHeader;
case Decoder.PartType of
mcptText: begin
BodyStream := TMemoryStream.Create;
try
NewDecoder := TIdMessageDecoderMIME(Decoder).ReadBody(BodyStream, MsgEnd);
try
BodyStream.Position := 0;
mLog.Lines.LoadFromStream(BodyStream); //<-- OK i got the text data from body and showing in a memo
finally
Decoder.Free;
Decoder := NewDecoder;
end;
finally
BodyStream.Free;
end;
end;
mcptAttachment: begin
BodyStream := TMemoryStream.Create;
try
BodyStream.Position := 0;
NewDecoder := TIdMessageDecoderMIME(Decoder).ReadBody(BodyStream, MsgEnd); //<-- here comes a close gracefully,
//so i debug the ReadBody and found a exception on line 438 of idMessadeCoderMime
// LLine := ReadLnRFC(VMsgEnd, LF, '.', LEncoding{$IFDEF STRING_IS_ANSI}, LEncoding{$ENDIF}); {do not localize}
//When it try to read a exception occurs and the connection closes gracefully
//but it read a lot of data befora except, so i thinking is something about a end of msg missing or misformed
jpg := TJPEGImage.Create;
try
BodyStream.Position := 0;
jpg.LoadFromStream(BodyStream);
jpg.SaveToFile('C:\Producao\API_SDK\Facial\teste.jpg');
finally
jpg.Free;
Decoder.Free;
Decoder := NewDecoder;
end;
finally
BodyStream.Free;
end;
end;
mcptIgnore: begin
FreeAndNil(Decoder);
Decoder := TIdMessageDecoderMIME.Create(nil);
TIdMessageDecoderMIME(Decoder).MIMEBoundary := Boundary;
end;
mcptEOF: begin
FreeAndNil(Decoder);
MsgEnd := True;
end;
end;
until (Decoder = nil) or MsgEnd;
finally
Decoder.Free;
end;
finally
TCPStream.Free;
end;
end;
end;
代码mcptAttachment
的部分,当NewDecoder := Decoder.ReadBody(BodyStream, MsgEnd);
被调用时,readbody函数运行,读取字节直到它在达到'Content-Length'大小之前崩溃。
我试图在它崩溃时隔离它,但可以肯定的是,当它尝试读取下一个数据时会发生错误。但我还无法在 Indy 源上找到异常的触发器
编辑:得到图像但无法继续阅读,直到服务器关闭连接导致响应关闭。
工作代码
var
Boundary, Line: string;
TCPStream: TIdTCPStream;
NewDecoder, Decoder: TIdMessageDecoder;
MsgEnd: Boolean;
BodyStream: TStream;
Jpg: TJpegImage;
begin
HTTP.HTTPOptions := [hoInProcessAuth, hoForceEncodeParams, hoNoReadMultipartMIME];
HTTP.Get('http://192.168.1.205/cgi-bin/snapManager.cgi?action=attachFileProc&Flags[0]=Event&Events=[AccessControl]');
if IsHeaderMediaType(HTTP.Response.ContentType, 'multipart') then
begin
Boundary := ExtractHeaderSubItem(HTTP.Response.ContentType, 'boundary', QuoteHTTP);
repeat
Line := HTTP.IOHandler.ReadLn;
until (Line = ('--' + Boundary)) or (Line = ('--' + Boundary + '--'));
TCPStream := TIdTCPStream.Create(HTTP);
try
Decoder := TIdMessageDecoderMIME.Create(nil);
try
TIdMessageDecoderMIME(Decoder).MIMEBoundary := Boundary;
MsgEnd := False;
repeat
TIdMessageDecoderMIME(Decoder).SourceStream := TCPStream;
TIdMessageDecoderMIME(Decoder).FreeSourceStream := False;
if not http.IOHandler.InputBufferIsEmpty then
begin
Decoder.ReadHeader;
case Decoder.PartType of
mcptText: begin
BodyStream := TMemoryStream.Create;
try
NewDecoder := TIdMessageDecoderMIME(Decoder).ReadBody(BodyStream, MsgEnd);
try
BodyStream.Position := 0;
mEvent.Lines.LoadFromStream(BodyStream);
finally
Decoder.Free;
Decoder := NewDecoder;
end;
finally
BodyStream.Free;
end;
end;
mcptAttachment: begin
BodyStream := TMemoryStream.Create;
http.IOHandler.InputBuffer.ExtractToStream(BodyStream);
try
BodyStream.Position := 0;
jpg := TJPEGImage.Create;
try
jpg.LoadFromStream(BodyStream);
JvImage1.Picture.Assign(jpg);
jpg.SaveToFile('C:\Producao\API-SDK\Facial\teste.jpg');
finally
jpg.Free;
FreeAndNil(Decoder);
Decoder := TIdMessageDecoderMIME.Create(nil);
TIdMessageDecoderMIME(Decoder).MIMEBoundary := Boundary;
end;
finally
BodyStream.Free;
end;
end;
mcptIgnore: begin
mLog.Lines.Add('Ignore');
FreeAndNil(Decoder);
Decoder := TIdMessageDecoderMIME.Create(nil);
TIdMessageDecoderMIME(Decoder).MIMEBoundary := Boundary;
end;
mcptEOF: begin
mLog.Lines.Add('Fim');
FreeAndNil(Decoder);
MsgEnd := True;
end;
end;
end
else
begin
http.IOHandler.CheckForDisconnect(False,True);
msgend := True
end;
until (Decoder = nil) or MsgEnd;
finally
Decoder.Free;
end;
finally
TCPStream.Free;
end;
end;
end;
您所要求的可以用 TIdHTTP
完成,但需要一些额外的工作。详情请参阅 Indy 网站上的以下博客文章:
https://www.indyproject.org/2014/03/05/new-tidhttp-honoreadmultipartmime-flag/
简而言之,您需要在 TIdHTTP.HTTPOptions
属性 中启用 hoNoReadMultipartMIME
标志,这样 TIdHTTP.Get()
就不会尝试从中读取 MIME 数据TIdHTTP.IOHandler
收到 HTTP headers 后。这将允许您自己阅读 MIME 数据。您可以使用 Indy 的 TIdMessageDecoderMIME
class 来帮助阅读。博客文章中提供了一个代码示例。
我正在使用大华面部终端,它有一个 API 类(CGI 风格)和一个 SDK。我问了一些关于 dll 转换的问题,但现在我也在尝试使用 de API。
监视 Facial 处理的事件的 API 是
是一个 multipart/x-mixed-replace 响应如何 return 第一个绑定作为 text/plain 与事件数据和一个带有 image/jpeg 与事件快照的绑定。
使用关于 Indy 的在线信息和 Lebeau 发表的一些有用的帖子,我使用 idHttp.IoHanlder.ReadLn(IndyTextEncoding_UTF8)
读取文本数据我尝试使用 idHttp.IOHandler.ReadByte、ReadBytes、ReadStream 读取下一个 bondary(图像),但没有成功。
这是使用 idHttp.IoHandler.ReadLn
的响应
--myboundary
Content-Type: text/plain
Content-Length: 1035
Events[0].Alive=100
Events[0].CardName=Joao Test
Events[0].CardNo=0B8748EB
Events[0].CardType=0
Events[0].CreateTime=1616297700
Events[0].Door=0
Events[0].ErrorCode=0
Events[0].Method=15
Events[0].ReaderID=1
Events[0].Similarity=99
Events[0].SnapPath=/var/tmp/partsnap96.jpg
Events[0].Status=1
Events[0].Type=Entry
Events[0].UTC=1616297700
Events[0].UserID=1
Events[0].UserType=0
--myboundary
Content-Type: image/jpeg
Content-Length: 54235
����
(1#%(:3=<9387@H\N@DWE78PmQW_bghg>Mqypdx\egc//cB8Bcccccccccccccccccccccccccccccccccccccccccccccccccc//cB8Bcccccccccccccccccccccccccccccccccccccccccccccccccc��
%&'()*456789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz�������������������������������������������������������������������������
�%�&'()*56789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz��������������������������������������������������������������������������
�XQz �?m�c$[J��5"�Hz�~5���m+��b{�����{��n��*p�p*�8�bx��
`3mjLQ��9CB���T���D���'ޥAY\ų���>�*��c�U���h�R�%��je�W��*]�m�p��m?��c6�?��b��R��1N�.(��.)إ�
u\,8�
)�ny�ڌү
@
#��
L�rɳ���B�Ns�� S�K��
�i�F��Z�af�
������ޭ�T�
/l�i]Q��l������
C�RS�
ʛO^�ҫ���
h�
P)@�
pN��
\S���0�1K�
f2N�⫑S�-�LF-�Y�8?0��.�?[�nE[��t[��s�w�h��_���y�[�R�*��=je ������4�M��,?�x��Ƣ�Sל�t8�F�
ZJZ
(��R�E
)h�bR�K@����
p�S����)تb���x1�O��(��i�1����5��r=i��i�B�A��J�{���ںT5s��_��
Q@-�
Z1J
x�i�i�S�NU��
�Kf=9=Pƺ�s�ٿ��N����uS�Tj��#��d��-N-����gw�W5�0*z0��=&m��=T��S�����
x�`JpJxZpZ"��i
p���@
<S7
P���R�a�wP"���� %!�Q�
��`�M�6��wⵍfjJb�I�������{�h/���ڞ�r�Q��Ta�ϫ�7E��o�����5%���X��C�
�v�G�rO���
��徸�(�O�T�4�4��ц��j8�
��3sM�X�'�G��ڪξ\�/L|��i����I?+|�Z�
�?��K[M��L7�^
K�@d�Hb��q߯�V�'|a��C�E�Z�+�
f�&����`'�ϑ��E�r>�Ƶ'Q�7~��<r}��XT�+������pHQۭ
我正在创建一个 class 来实现 API 和 SDK,在这种情况下 (API) 来管理我写了一个线程 class 的事件使用事件 API 并在事件发生时同步数据,idHttp 由线程 class 拥有并且它在 TDispositivo class 中,所以我 运行 一个线程 class 对于主要 class.
中包含的每个 TDispositivo这是线程中的执行代码。 (忽略图像数据,因为我现在不知道如何正确处理它。
procedure TMonitor.Execute;
var
Ret, URL, Chave, Valor : String;
I, isInteger : Integer;
Concluido : boolean;
Tentativas: Integer;
begin
Tentativas := 3;
fAtivo := True;
FContador := 0; //zerando contador
//executa enquanto fAtivo for True e terminated for False
while fAtivo and not self.Terminated do
begin
if fOwner.alive then
begin
//preparando URL
With fOwner.Config do
begin
Url := 'http://'+ IP + ifthen(PortaWeb>=0,':'+PortaWeb.ToString,'')+'/cgi-bin/snapManager.cgi?action=attachFileProc&Flags[0]=Event&Events=[AccessControl]';
end;
//Incrementando contado
Inc(fContador);
//Efetuando o GET dentro de um try except para evitar interrupcao na thread
Try
ret := fHttp.Get(Url);
except
End;
fOwner.doGet(fhttp.ResponseCode, fhttp.ResponseText);
//tratando erros http
if fHttp.ResponseCode <> 200 then
begin
case fHttp.ResponseCode of
400: Erro(740,'Chamada ao terminal mal formada');
401: Erro(741,'Chamada ao terminal não autorizada');
403: Erro(743,'Recurso do servidor proibido');
404: Erro(744,'Recurso do terminal não encontrado');
500: Erro(750,'Erro interno no processamento de chamadas do terminal');
501: Erro(751,'Recurso do terminal não implementado');
end;
if (fContador >= Tentativas) then
begin
Erro(700,'Falha ao conectar o dispositivo. Número de tentativas excedido');
fContador := 0;
//verificando se vai continuar tentando se reconectar
if fOwner.Config.Reconectar then
begin
Log('Aguardando para tentar reconectar o monitor ...');
Sleep(fOwner.Config.TempoEsperaReconectar);
end
else
begin
Log('Finalizando monitoramento por falha de comunicação');
Self.Stop;
end;
end;
end
else
begin
//zerando contador após uma conexão bem sucedida;
FContador := 0;
//para evitar final do chunk ou close gracefully
try
if IsHeaderMediaType(fHttp.Response.ContentType, 'multipart') then
begin
i := 0;
Concluido := False;
repeat
if (i >= 35) Or Concluido then
begin
//tirando snap antes de processar o evento para ver se diminui o delay
if FOwner.Config.SnapshotEvento then
fOwner.getSnapshot;
Synchronize(Evento);
Concluido := False;
i := 0;
end;
fLinha := fHttp.IOHandler.ReadLn(IndyTextEncoding_UTF8);
if Pos('Content-Length', fLinha) > 0 then
begin
i := 0;
Concluido := False;
fEvento.Limpar;
end
Else if Pos('Events[', fLinha) > 0 then
begin
if fOwner.Config.Depurar then
Log(fLinha);
Inc(I);
Chave := Trim(retPar(1,'=',retPar(2,'.',fLinha)));
Valor := Trim(retPar(2,'=',fLinha));
if Valor = '' then
Valor := '0';
With fEvento do
begin
if Chave = 'Alive' then
Alive := ifthen(Valor = '',0,Valor.ToInteger)
else if Chave = 'CardName' then
CardName := ifthen(Trim(Valor) = '0','Não identificado',trim(Valor))
else if Chave = 'CardNo' then
begin
if TryStrToInt(Valor, isInteger) then
CardNo := ifthen(Valor = '', '-1', Valor)
else
CardNo := WiegandHexToInt(Valor).ToString;
end
else if Chave = 'CardType' then
CardType := ifthen(Valor = '', -1, Valor.ToInteger)
else if Chave = 'CreateTime' then
CreateTime := UnixToDateTime(Valor.ToInt64, False)
else if Chave = 'Door' then
Door := ifThen(Valor = '',-1, Valor.ToInteger)
else if Chave = 'ErrorCode' then
ErrorCode := ifThen(Valor = '',-1, Valor.ToInteger)
else if Chave = 'EventBaseInfo.Action' then
EventBaseAction := Valor
else if Chave = 'EventBaseInfo.Code' then
EventBaseCode := Valor
else if Chave = 'EventBaseInfo.Index' then
EventBaseIndex := ifThen(Valor = '',-1, Valor.ToInteger)
else if Chave = 'Method' then
Metodo := ifThen(Valor = '',-1, Valor.ToInteger)
else if Chave = 'ReaderID' then
LeitorID := ifThen(Valor = '',-1, Valor.ToInteger)
else if Chave = 'Similarity' then
Similaridade := ifthen(Valor = '', 0, Valor.ToInteger)
else if Chave = 'SnapPath' then
snappath := Valor
else if Chave = 'Status' then
Status := ifthen(Valor = '', -1, Valor.ToInteger)
else if Chave = '.Type' then
TipoAcesso := Valor
else if Chave = 'UTC' then
DataHora := UnixToDateTime(Valor.ToInt64, False)
else if Chave = 'UserID' then
UserID := ifthen(Valor = '', -1, Valor.ToInteger)
else if Chave = 'UserType' then
begin
i := 34;
Concluido := True;
UserType := ifthen(Valor = '', -1, Valor.ToInteger);
end;
end;
end;
until fhttp.IOHandler.ClosedGracefully;
end;
except
end;
end;
inherited;
end
else
begin
//dispositivo não respondeu ao ping
//verificando se vai continuar tentando se reconectar
if fOwner.Config.Reconectar then
begin
Log('Aguardando para tentar reconectar o monitor ...');
Sleep(fOwner.Config.TempoEsperaReconectar);
end
else
begin
Log('Finalizando monitoramento por falha de comunicação');
Self.Stop;
end;
inherited;
end;
end;
//rotina de finalização da thread
if FHttp.Connected then
begin
try
Fhttp.Disconnect;
except
end;
end;
end;
第一个问题,我可以做吗? Indy 能做到吗?有什么建议吗?
第二个问题,当我在数据结束后尝试 readLn 时,当 GET 为 运行ning 时,我观察到循环以检索数据。在 Firefox 中,GET 在图像之后仍然 运行ning 并且仍在工作,在我的例子中,GET 完成并且 threand.execute 再次调用 GET。
是否可以保持 GET 运行ning 避免过早中断并在出现时处理新数据?
编辑:当这部分代码 运行 时,NewDecoder := TIdMessageDecoderMIME(Decoder).ReadBody(BodyStream, MsgEnd);
行优雅地结束,深入研究 Indy 代码我发现 TIOHandler.ReadLn
如何在 LTerm = -1
时执行 CheckForDisconnect(True,True)
并且这会在不检索数据的最后字节的情况下中断执行。
考虑到这一点,我怀疑与 Responde.KeepAlive
总是 False
以及 Connection
内容总是 close
有关系,这解释了为什么 Decoder.Readybody 引发 Close gracefully
并中断我的执行。
但是,如果我 运行 调用 api,例如。 Firefox,在 FF 获取图像后连接仍在 运行ning 并且仅在达到超时时中断。
是否有可能 headers 上的内容由终端发送?或者 idHttp 如何解析 headers 数据的方式?
//part of code after http.get
mcptAttachment: begin
BodyStream := TMemoryStream.Create;
try
BodyStream.Position := 0;
NewDecoder := TIdMessageDecoderMIME(Decoder).ReadBody(BodyStream, MsgEnd); //<-- here comes a close gracefully
jpg := TJPEGImage.Create;
try
BodyStream.Position := 0;
jpg.LoadFromStream(BodyStream);
jpg.SaveToFile('C:\Producao\API_SDK\Facial\teste.jpg');
finally
jpg.Free;
Decoder.Free;
Decoder := NewDecoder;
end;
finally
BodyStream.Free;
end;
end;
//part of the code of tIOHandler.ReadLn
// ReadFromSource blocks - do not call unless we need to
else if LTermPos = -1 then begin
// ReadLn needs to call this as data may exist in the buffer, but no EOL yet disconnected
CheckForDisconnect(True,True); //The execution stops here whiout return the last bytes and premature finishing the code above with a close gracefully
// Can only return -1 if timeout
FReadLnTimedOut := ReadFromSource(True, ATimeout, False) = -1;
if (not FReadLnTimedOut) and (ATimeout >= 0) then begin
if GetElapsedTicks(LReadLnStartTime) >= UInt32(ATimeout) then begin
FReadLnTimedOut := True;
end;
end;
if FReadLnTimedOut then begin
Result := '';
Exit;
end;
编辑:使用New TIdHTTP hoNoReadMultipartMIME flag示例,我编写了一个测试应用程序来尝试从边界获取图像
var
Boundary, Line: string;
TCPStream: TIdTCPStream;
NewDecoder, Decoder: TIdMessageDecoder;
MsgEnd: Boolean;
BodyStream: TStream;
Jpg: TJpegImage;
begin
{
The code below comes from Indy Project Blog by Remy Lebeau
This connection requires Digest Auth, so i set BasicAuth to False, put the flag hoInProcessAuth
And implement the OnAuthorization and OnSelectAuthorization, the select is needed to do something to the Authorization works,
do not know why.
}
HTTP.HTTPOptions := [hoInProcessAuth, hoForceEncodeParams, hoNoReadMultipartMIME];
HTTP.Get('http://192.168.1.205/cgi-bin/snapManager.cgi?action=attachFileProc&Flags[0]=Event&Events=[AccessControl]');
//Here the response always return HTTP.Response.KeepAlive FALSE, so i exclude it from test
if IsHeaderMediaType(HTTP.Response.ContentType, 'multipart') then //and HTTP.Response.KeepAlive
begin
Boundary := ExtractHeaderSubItem(HTTP.Response.ContentType, 'boundary', QuoteHTTP);
repeat
Line := HTTP.IOHandler.ReadLn;
until (Line = ('--' + Boundary)) or (Line = ('--' + Boundary + '--'));
TCPStream := TIdTCPStream.Create(HTTP);
try
Decoder := TIdMessageDecoderMIME.Create(nil);
try
TIdMessageDecoderMIME(Decoder).MIMEBoundary := Boundary;
MsgEnd := False;
repeat
TIdMessageDecoderMIME(Decoder).SourceStream := TCPStream;
TIdMessageDecoderMIME(Decoder).FreeSourceStream := False;
Decoder.ReadHeader;
case Decoder.PartType of
mcptText: begin
BodyStream := TMemoryStream.Create;
try
NewDecoder := TIdMessageDecoderMIME(Decoder).ReadBody(BodyStream, MsgEnd);
try
BodyStream.Position := 0;
mLog.Lines.LoadFromStream(BodyStream); //<-- OK i got the text data from body and showing in a memo
finally
Decoder.Free;
Decoder := NewDecoder;
end;
finally
BodyStream.Free;
end;
end;
mcptAttachment: begin
BodyStream := TMemoryStream.Create;
try
BodyStream.Position := 0;
NewDecoder := TIdMessageDecoderMIME(Decoder).ReadBody(BodyStream, MsgEnd); //<-- here comes a close gracefully,
//so i debug the ReadBody and found a exception on line 438 of idMessadeCoderMime
// LLine := ReadLnRFC(VMsgEnd, LF, '.', LEncoding{$IFDEF STRING_IS_ANSI}, LEncoding{$ENDIF}); {do not localize}
//When it try to read a exception occurs and the connection closes gracefully
//but it read a lot of data befora except, so i thinking is something about a end of msg missing or misformed
jpg := TJPEGImage.Create;
try
BodyStream.Position := 0;
jpg.LoadFromStream(BodyStream);
jpg.SaveToFile('C:\Producao\API_SDK\Facial\teste.jpg');
finally
jpg.Free;
Decoder.Free;
Decoder := NewDecoder;
end;
finally
BodyStream.Free;
end;
end;
mcptIgnore: begin
FreeAndNil(Decoder);
Decoder := TIdMessageDecoderMIME.Create(nil);
TIdMessageDecoderMIME(Decoder).MIMEBoundary := Boundary;
end;
mcptEOF: begin
FreeAndNil(Decoder);
MsgEnd := True;
end;
end;
until (Decoder = nil) or MsgEnd;
finally
Decoder.Free;
end;
finally
TCPStream.Free;
end;
end;
end;
代码mcptAttachment
的部分,当NewDecoder := Decoder.ReadBody(BodyStream, MsgEnd);
被调用时,readbody函数运行,读取字节直到它在达到'Content-Length'大小之前崩溃。
我试图在它崩溃时隔离它,但可以肯定的是,当它尝试读取下一个数据时会发生错误。但我还无法在 Indy 源上找到异常的触发器
编辑:得到图像但无法继续阅读,直到服务器关闭连接导致响应关闭。
工作代码
var
Boundary, Line: string;
TCPStream: TIdTCPStream;
NewDecoder, Decoder: TIdMessageDecoder;
MsgEnd: Boolean;
BodyStream: TStream;
Jpg: TJpegImage;
begin
HTTP.HTTPOptions := [hoInProcessAuth, hoForceEncodeParams, hoNoReadMultipartMIME];
HTTP.Get('http://192.168.1.205/cgi-bin/snapManager.cgi?action=attachFileProc&Flags[0]=Event&Events=[AccessControl]');
if IsHeaderMediaType(HTTP.Response.ContentType, 'multipart') then
begin
Boundary := ExtractHeaderSubItem(HTTP.Response.ContentType, 'boundary', QuoteHTTP);
repeat
Line := HTTP.IOHandler.ReadLn;
until (Line = ('--' + Boundary)) or (Line = ('--' + Boundary + '--'));
TCPStream := TIdTCPStream.Create(HTTP);
try
Decoder := TIdMessageDecoderMIME.Create(nil);
try
TIdMessageDecoderMIME(Decoder).MIMEBoundary := Boundary;
MsgEnd := False;
repeat
TIdMessageDecoderMIME(Decoder).SourceStream := TCPStream;
TIdMessageDecoderMIME(Decoder).FreeSourceStream := False;
if not http.IOHandler.InputBufferIsEmpty then
begin
Decoder.ReadHeader;
case Decoder.PartType of
mcptText: begin
BodyStream := TMemoryStream.Create;
try
NewDecoder := TIdMessageDecoderMIME(Decoder).ReadBody(BodyStream, MsgEnd);
try
BodyStream.Position := 0;
mEvent.Lines.LoadFromStream(BodyStream);
finally
Decoder.Free;
Decoder := NewDecoder;
end;
finally
BodyStream.Free;
end;
end;
mcptAttachment: begin
BodyStream := TMemoryStream.Create;
http.IOHandler.InputBuffer.ExtractToStream(BodyStream);
try
BodyStream.Position := 0;
jpg := TJPEGImage.Create;
try
jpg.LoadFromStream(BodyStream);
JvImage1.Picture.Assign(jpg);
jpg.SaveToFile('C:\Producao\API-SDK\Facial\teste.jpg');
finally
jpg.Free;
FreeAndNil(Decoder);
Decoder := TIdMessageDecoderMIME.Create(nil);
TIdMessageDecoderMIME(Decoder).MIMEBoundary := Boundary;
end;
finally
BodyStream.Free;
end;
end;
mcptIgnore: begin
mLog.Lines.Add('Ignore');
FreeAndNil(Decoder);
Decoder := TIdMessageDecoderMIME.Create(nil);
TIdMessageDecoderMIME(Decoder).MIMEBoundary := Boundary;
end;
mcptEOF: begin
mLog.Lines.Add('Fim');
FreeAndNil(Decoder);
MsgEnd := True;
end;
end;
end
else
begin
http.IOHandler.CheckForDisconnect(False,True);
msgend := True
end;
until (Decoder = nil) or MsgEnd;
finally
Decoder.Free;
end;
finally
TCPStream.Free;
end;
end;
end;
您所要求的可以用 TIdHTTP
完成,但需要一些额外的工作。详情请参阅 Indy 网站上的以下博客文章:
https://www.indyproject.org/2014/03/05/new-tidhttp-honoreadmultipartmime-flag/
简而言之,您需要在 TIdHTTP.HTTPOptions
属性 中启用 hoNoReadMultipartMIME
标志,这样 TIdHTTP.Get()
就不会尝试从中读取 MIME 数据TIdHTTP.IOHandler
收到 HTTP headers 后。这将允许您自己阅读 MIME 数据。您可以使用 Indy 的 TIdMessageDecoderMIME
class 来帮助阅读。博客文章中提供了一个代码示例。